Overview
The scheduler tool converts natural language scheduling phrases into standard cron expressions and manages scheduled tasks. Uses a rule-based parser (no LLM call) for instant conversion.
scheduler
Manage scheduled tasks with natural language (‘every day at 9am’) or cron expressions.
Action to perform: create, list, or delete
Natural language or cron expression (for create action)
Name/description of the scheduled task (for create action)
Command or message to execute on schedule (for create action)
Session key to deliver results to (e.g. ‘telegram:12345’). Required for channel delivery.
Task ID to delete (for delete action)
Actions
Create Task
result = await scheduler(
action="create",
schedule="every day at 9am",
task_name="Daily standup reminder",
command="Send standup reminder to team",
reply_to="telegram:123456789"
)
Returns:
Scheduled task created:
ID: cron_a3f8b2c1
Name: Daily standup reminder
Cron: 0 9 * * *
Schedule: every day at 9am
Prompt: Send standup reminder to team
Reply to: telegram:123456789
List Tasks
result = await scheduler(action="list")
Returns:
## Scheduled Tasks
- **Daily standup reminder** (ID: cron_a3f8b2c1) [enabled]
Schedule: `0 9 * * *` | Prompt: Send standup reminder to team
- **Weekly report** (ID: cron_7f2e9d4a) [enabled]
Schedule: `0 17 * * 5` | Prompt: Generate and send weekly report
Delete Task
result = await scheduler(
action="delete",
task_id="cron_a3f8b2c1"
)
Returns:
Deleted scheduled task: cron_a3f8b2c1
Natural Language Patterns
Time-Based
Every N minutes/hours:
"every 5 minutes" → */5 * * * *
"every 30 minutes" → */30 * * * *
"every 2 hours" → 0 */2 * * *
"every hour" → 0 * * * *
"every minute" → * * * * *
Daily at specific time:
"every day at 9am" → 0 9 * * *
"every day at 5pm" → 0 17 * * *
"every day at 23" → 0 23 * * * (24-hour format)
Weekly on specific day:
"every Monday at 9am" → 0 9 * * 1
"every Friday at 5pm" → 0 17 * * 5
"every Wednesday at 12pm" → 0 12 * * 3
Supported days: Monday (1), Tuesday (2), Wednesday (3), Thursday (4), Friday (5), Saturday (6), Sunday (0)
Weekdays:
"every weekday at 9am" → 0 9 * * 1-5
"every weekday at 6pm" → 0 18 * * 1-5
Monthly:
"every month on the 1st" → 0 0 1 * *
"every month on the 15th" → 0 0 15 * *
Raw Cron Expressions
You can also pass raw cron expressions:
"*/15 * * * *" → Every 15 minutes
"0 0 * * 0" → Every Sunday at midnight
"0 */6 * * *" → Every 6 hours
"0 0 1 1 *" → Every January 1st at midnight
Storage
Scheduled tasks are stored in workspace/cron/jobs.json:
[
{
"id": "cron_a3f8b2c1",
"name": "Daily standup reminder",
"schedule": "0 9 * * *",
"prompt": "Send standup reminder to team",
"enabled": true,
"last_run": null,
"created_at": "2026-02-28T10:30:00Z",
"reply_to": "telegram:123456789"
}
]
The reply_to parameter specifies where task results should be delivered:
Format: channel:chat_id
Examples:
reply_to="telegram:123456789" # Telegram chat
reply_to="discord:987654321" # Discord channel
reply_to="slack:C01ABC123" # Slack channel
If omitted, results are logged to console only.
Execution
Scheduled tasks are executed by the cron daemon (separate from the tool). The daemon:
- Reads
workspace/cron/jobs.json every minute
- Evaluates cron expressions against current time
- Executes matching tasks by spawning an agent with the
prompt
- Sends results to the
reply_to channel via send_message
- Updates
last_run timestamp
Error Handling
Invalid Schedule
result = await scheduler(
action="create",
schedule="every bluesday at 25pm",
task_name="Test",
command="Test"
)
# Returns:
# Error: could not parse schedule 'every bluesday at 25pm'.
# Try formats like: 'every 5 minutes', 'every day at 9am',
# 'every Monday at 3pm', or a raw cron expression like '*/5 * * * *'.
result = await scheduler(
action="create",
schedule="every day at 9am",
task_name="Test",
command="Test",
reply_to="invalid"
)
# Returns:
# Error: invalid reply_to format 'invalid'.
# Expected 'channel:chat_id' (e.g. 'telegram:12345').
Task Not Found
result = await scheduler(
action="delete",
task_id="nonexistent"
)
# Returns: "Error: no scheduled task found with ID 'nonexistent'."
Best Practices
- Use natural language for readability (converted to cron internally)
- Provide descriptive task names for easier identification
- Set reply_to for channel delivery — otherwise results only logged
- Test schedules with
list action before relying on them
- Delete unused tasks to keep
jobs.json clean
- Use 24-hour format for clarity (9am, 17pm, 23)
Limitations
- No date-specific scheduling (e.g. “every February 14th”) — use raw cron
- No timezone support — runs in server’s local timezone
- Minute granularity only — cannot schedule for specific seconds
- No task editing — delete and recreate to change
- No execution history — only stores
last_run timestamp
Implementation
Defined in grip/tools/scheduler.py. Uses:
- Rule-based pattern matching with regex for NL parsing
- 12/24-hour time conversion for “am/pm” support
- Day name to cron number mapping (Monday=1, Sunday=0)
- Atomic JSON writes to
workspace/cron/jobs.json
- Migration system for legacy individual
.json files