Cron tool
A single action-dispatch cron tool lets any personality schedule recurring work. The action field dispatches to create, list, get, read_run, update, pause, resume, run, or remove. The tool registers against the same CronScheduler instance the operator-driven ethos cron CLI uses, so jobs created via either path land in the same store and fire through the same engine.
Source
Factory: extensions/tools-cron/src/index.ts (createCronTool(scheduler)). Scheduler implementation: @ethosagent/cron. Wiring at packages/wiring/src/index.ts — registers the tool when cronScheduler is passed in via CreateAgentLoopOptions.
Availability
The tool is registered on the agent's tool registry only when a CronScheduler is wired through to createAgentLoop. Two callers do this today:
| Caller | Behaviour |
|---|---|
ethos gateway | Always wires a scheduler. Every personality bound to a configured bot has the cron tool available if it lists cron in toolset.yaml. |
ethos serve | Always wires a scheduler. The web Cron tab and agent-callable cron tool share the same scheduler instance. |
ethos chat / CLI one-off | No scheduler — cron jobs created in an ephemeral process can't persist past exit. The tool isn't registered. |
A personality that lists cron in toolset.yaml but runs in a context without a scheduler gets an "unknown tool" error at call time. That's intentional — calling cron from a CLI chat would silently create jobs that never fire.
Personality opt-in
Add the tool to the personality's toolset.yaml:
# ~/.ethos/personalities/<id>/toolset.yaml
- cron
Actions
create
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Human-readable label (e.g. "Morning Briefing"). |
schedule | string | yes | Standard 5-field cron expression — minute hour day month weekday. All times are local. Validated via isValidCronExpression. |
prompt | string | yes | The prompt the agent runs on each firing. |
missed_run_policy | 'run-once' | 'skip' | no | What to do if the scheduler was down when the job's scheduled time passed. run-once fires the missed slot on next start; skip (default) waits for the next normal occurrence and records the skip in the run history. A slot is missed only when it fell due before the scheduler's previous tick or before the process started (CronScheduler.tick). |
active_hours | string | no | Daily window HH:MM-HH:MM (e.g. 09:00-21:00; 22:00-06:00 crosses midnight) on the host clock the schedule uses. An occurrence outside it is skipped before the turn, audited as inactive-hours-skip, and not counted as a run. run ignores it. Refused (input_invalid) when malformed or on a one-shot schedule. Default: always active. |
Always pins the job to the caller's personality (ctx.personalityId). Returns an error if no personality context is available.
list
No fields. Returns the calling personality's own jobs with id, name, schedule, status, next-run timestamp, and prompt summary. There is no filter argument: another personality's jobs are never listed.
Ownership
Every action other than create acts only on a job owned by the calling personality (ctx.personalityId). A job owned by another personality is reported exactly as a nonexistent id would be (Job not found: <id>), so the tool cannot be used to discover which job ids exist. update, pause, resume, run, and remove also refuse source: 'system' jobs, which belong to operator config. Enforced by loadOwnedJob in extensions/tools-cron/src/index.ts; pinned by extensions/tools-cron/src/__tests__/ownership.test.ts. The operator's ethos cron CLI and the web cron routes call the scheduler directly and are not scoped.
get
| Field | Type | Required |
|---|---|---|
id | string | yes |
Returns full job detail plus recent run timestamps.
read_run
| Field | Type | Required |
|---|---|---|
id | string | yes |
at | string | yes |
Reads the output of a specific historical run, identified by the ISO-8601 timestamp from get.
update
| Field | Type | Required |
|---|---|---|
id | string | yes |
name | string | no |
schedule | string | no |
prompt | string | no |
script_file / timeout_seconds | string / number | no |
precheck_file / precheck_timeout_seconds | string / number | no |
active_hours | string | no |
At least one of name, schedule, prompt, script_file, precheck_file or active_hours must be provided. active_hours: "off" clears the window. A window is refused when the job's schedule, after the update, is a one-shot.
pause
| Field | Type | Required |
|---|---|---|
id | string | yes |
Marks the job paused. The scheduler stops firing it but the row stays in the store.
resume
| Field | Type | Required |
|---|---|---|
id | string | yes |
Unpauses. The next-run time is recomputed from the schedule.
run
| Field | Type | Required |
|---|---|---|
id | string | yes |
Fires the job's prompt immediately, outside its normal schedule. Returns the output. The next scheduled firing is not affected.
remove
| Field | Type | Required |
|---|---|---|
id | string | yes |
Permanently removes the job. Cannot be undone. Use pause for a reversible stop.
Recursion guard
When a cron job fires, the runJob closure passes toolsetOverride to AgentLoop.run(), filtering out the cron tool from the effective toolset. This prevents cron-spawned sessions from scheduling further cron jobs (infinite recursion).
Wiring contract
Callers thread the scheduler through CreateAgentLoopOptions.cronScheduler:
// packages/wiring/src/index.ts CreateAgentLoopOptions
cronScheduler?: CronScheduler;
When present, the wiring registers createCronTool(scheduler) on the AgentLoop's tool registry. The tool registry's per-personality filter then exposes the tool only if the personality's toolset.yaml includes cron.
Capability rationale
capabilities: {} — no framework-level gate. The scheduler is operator-injected; without one, no tool registration happens. The store is per state dir: <ethos home>/cron/, which is ~/.ethos/cron/ unless ETHOS_STATE_DIR is set (resolved by ethosCronDir() in packages/config/src/index.ts). Jobs created by the agent are visible to the operator via ethos cron list immediately.
Cron creates durable side effects (recurring runs cost provider tokens; output may notify channels). Treat the personality opt-in as the safety boundary.
Errors
code | When | Operator fix |
|---|---|---|
not_available | Tool listed in toolset but no scheduler wired (e.g. ethos chat profile) | Run from ethos gateway or ethos serve |
input_invalid | active_hours is not HH:MM-HH:MM or has equal start and end, or create sets it on a one-shot schedule | Use a window like 09:00-21:00, or a recurring schedule |
execution_failed | update leaves a window on a one-shot schedule (the scheduler refuses it) | Clear the window with active_hours: "off", or keep a recurring schedule |
input_invalid | Cron expression failed isValidCronExpression | Fix the 5-field syntax |
input_invalid | Missing required field for the action | Provide it |
input_invalid | Any action called without personality context | Ensure a personality is active |
input_invalid | Job not found: <id> — the id does not exist or belongs to another personality | Use an id from list |
input_invalid | update/pause/resume/run/remove on a source: 'system' job | Manage it in ~/.ethos/config.yaml |
execution_failed | run and the agent's run produces an error | Investigate the prompt or model |
Examples
Morning briefing
cron({
action: "create",
name: "Morning briefing",
schedule: "0 8 * * 1-5",
prompt: "Summarise overnight Slack alerts, the deploy log, and pending PRs."
})
List all jobs
cron({ action: "list" })
Get job detail with run history
cron({ action: "get", id: "morning-briefing" })
Read a specific run's output
cron({ action: "read_run", id: "morning-briefing", at: "2026-05-21T08:00:00.000Z" })
Pause and resume
cron({ action: "pause", id: "morning-briefing" })
cron({ action: "resume", id: "morning-briefing" })
Run immediately
cron({ action: "run", id: "morning-briefing" })
Remove
cron({ action: "remove", id: "morning-briefing" })
See also
@ethosagent/cronpackage — the scheduler implementation.send_messagereference — pair withcronto schedule cross-channel notifications.- Watcher tools — act on a change instead of a timer, with a fire budget and expiry.
- CLI reference —
ethos cron list / pause / resume / delete / run / createfor the operator-driven side of the same store.