GoClaw features an integrated scheduler that allows agents to create, manage, and execute automated periodic tasks (Cron Jobs). This enables agents to perform routine health checks, generate daily reports, or pull data feeds at specified intervals.
Using the built-in scheduler, agents can register automated tasks. A new job runs a Standard Operating Procedure (SOP) workflow, named by its sop_id, or one of two built-in commands:
notify channel=<channel> target=<id> text=<message> sends the message to that channel.agent [channel=<channel>] [target=<id>] text=<prompt> has the agent answer the prompt and posts the answer to that channel.When target= is left out, the job goes to the conversation it was created from: its ID becomes the target and, unless the command names a channel=, its channel the channel. A job with a target= and no channel= goes to Discord. Creating a job sends nothing: a malformed notify or agent command (no text=, or no target) is refused when the job is created. Raw shell commands are refused for new jobs (raw shell commands are not allowed for new cron jobs), and so is a job with neither a sop_id nor a built-in command. A job with a shell command made before that rule runs only if the command passes the same lists as the shell tool (tools.shell.allow / deny and the built-in deny list); otherwise it is recorded as not run, with the reason blocked. One that runs is stopped after 10 minutes, with everything it started.
flowchart LR
A["<b>Agent</b>"] --> T["<b>Cron Tool</b>"]
T --> S["<b>Cron Scheduler</b>"]
S --> W["<b>SOP Workflow / Built-in Command</b>"]
S --> DB[("<b>Database</b><br/>Persisted")]
You or the agent can manage scheduled jobs using the cron tool:
create: Register a new scheduled job.list: Display all scheduled jobs.delete: Remove a scheduled job by its unique ID.history: Show a job's schedule, run count, last run, last error and next run.Creating and deleting a job take an administrator (or the system acting on its own); anyone else is refused.
The Cron page of the Web UI pauses and resumes a job, runs it now, and deletes it. A schedule is changed through POST /api/cron with {"action": "update", "id": "cron-3", "schedule": "0 0 8 * * *"} and the gateway token, by the agent's sop_save with a new cron_schedule for the SOP's linked job, or in the SOP's Cron schedule field on the SOPs page.
Two maintenance jobs are built in: sys-memory-consolidate (daily at 03:00) and sys-trace-prune (daily at 02:30, keeps the newest 5,000 traces). Neither the Cron page nor the cron tool deletes them (a deleted one would be created again, with its default schedule, at the next start); pause one instead.
GoClaw supports a precise 6-field cron syntax (which includes seconds for granular scheduling):
┌───────── second (0 - 59)
│ ┌─────── minute (0 - 59)
│ │ ┌───── hour (0 - 23)
│ │ │ ┌─── day of month (1 - 31)
│ │ │ │ ┌─ month (1 - 12)
│ │ │ │ │ ┌ day of week (0 - 6, Sunday = 0)
│ │ │ │ │ │
* * * * * *
| Shortcut | Description | Equivalent Cron |
|---|---|---|
@every 30s |
Runs every 30 seconds | None: the interval counts from when the job was scheduled (created, or the gateway started), not from the clock |
@every 1h |
Runs every hour | None, as above: not at minute 0 |
@hourly |
Runs once an hour at minute 0 | 0 0 * * * * |
@daily |
Runs once a day at midnight | 0 0 0 * * * |
A five-field expression such as 0 9 * * * is read as firing at second 0 and stored as 0 0 9 * * *.
All scheduled tasks are persisted to the local database, ensuring they are automatically restored if the GoClaw gateway restarts. Only the gateway runs them. A job created or deleted from another process (goclaw tools exec cron, or the agent in goclaw chat) is picked up by the running gateway within 30 seconds.
cron-1).notify …, agent …; the two maintenance jobs run memory_consolidate and trace_prune).forbid (the default) skips a run while the previous one is still running; allow lets runs overlap.subject_ref): The person the job runs for. With it, a SOP job's run, or an agent … job's turn, runs as that person, at user level, and can never take admin or system actions; an agent … turn has a conversation of its own with its target (p2p:<target>:<agent>:for:<subject>), on the web channel too. Without it, a SOP job runs as the system (cron-system) and an agent … job as its creator (see Created By). Leave it empty only for maintenance jobs, and for an agent … job that should act with its creator's authority.agent … command job without a subject runs its turn as this user, with their pairing role (an admin's job runs as that admin), except on the web channel, where it runs as the system in the admin's chat (admin-session), whoever created it. For SOP jobs, and jobs with a subject, it is a record only.The scheduler automatically classifies execution failures to help administrators diagnose issue patterns:
| Error Category | Trigger |
|---|---|
sop_failure |
The job's SOP was not found, or its run ended failed, partial, escalated or interrupted. |
panic |
The job crashed inside the gateway. |
blocked |
A shell-command job was not run: its command is not on the shell allowlist, or is on a deny list. |
timeout |
A command job's error mentions a timeout, or a shell-command job ran past 10 minutes. |
auth |
A command job was refused: permission or access denied. |
not_found |
Something a command job needed was not found. |
connection_refused |
A command job's network request failed to connect. |
unknown |
Any other command job error. |
Here is how an agent interacts with the scheduler during a chat session:
User: "Run the disk-check SOP every hour."
Agent: "I will schedule the disk-check SOP hourly."
cron(action="create", schedule="0 0 * * * *", sop_id="disk-check")Created job cron-1
Schedule: 0 0 * * * *
SOP: disk-check
Next run: 2026-10-08 15:00:00
User: "Remind this channel about stand-up at 09:50 on weekdays."
cron(action="create", schedule="0 50 9 * * 1-5", command="notify channel=discord text=Stand-up in 10 minutes")Created job cron-2 with its schedule, the command (with this conversation filled in as target=) and the next run.User: "Show all my scheduled jobs."
Agent:
Tool Call: cron(action="list")
Response:
4 job(s):
| ID | Schedule | Command | Runs | Last Run | Next Run |
|---|---|---|---|---|---|
| cron-1 | 0 0 * * * * | | 3 | 14:00:00 | 15:00:00 |
| cron-2 | 0 50 9 * * 1-5 | notify target=123456789 channe... | 0 | — | 09:50:00 |
| sys-memory-consolidate | 0 0 3 * * * | memory_consolidate | 12 | 03:00:00 | 03:00:00 |
| sys-trace-prune | 0 30 2 * * * | trace_prune | 12 | 02:30:00 | 02:30:00 |
A job that runs a SOP shows no command. The command is cut at 30 characters,
and a ⚠ after the run count marks a job whose last run failed.