Skip to main content
reference

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:

CallerBehaviour
ethos gatewayAlways wires a scheduler. Every personality bound to a configured bot has the cron tool available if it lists cron in toolset.yaml.
ethos serveAlways wires a scheduler. The web Cron tab and agent-callable cron tool share the same scheduler instance.
ethos chat / CLI one-offNo 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​

FieldTypeRequiredDescription
namestringyesHuman-readable label (e.g. "Morning Briefing").
schedulestringyesStandard 5-field cron expression — minute hour day month weekday. All times are local. Validated via isValidCronExpression.
promptstringyesThe prompt the agent runs on each firing.
missed_run_policy'run-once' | 'skip'noWhat 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_hoursstringnoDaily 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​

FieldTypeRequired
idstringyes

Returns full job detail plus recent run timestamps.

read_run​

FieldTypeRequired
idstringyes
atstringyes

Reads the output of a specific historical run, identified by the ISO-8601 timestamp from get.

update​

FieldTypeRequired
idstringyes
namestringno
schedulestringno
promptstringno
script_file / timeout_secondsstring / numberno
precheck_file / precheck_timeout_secondsstring / numberno
active_hoursstringno

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​

FieldTypeRequired
idstringyes

Marks the job paused. The scheduler stops firing it but the row stays in the store.

resume​

FieldTypeRequired
idstringyes

Unpauses. The next-run time is recomputed from the schedule.

run​

FieldTypeRequired
idstringyes

Fires the job's prompt immediately, outside its normal schedule. Returns the output. The next scheduled firing is not affected.

remove​

FieldTypeRequired
idstringyes

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​

codeWhenOperator fix
not_availableTool listed in toolset but no scheduler wired (e.g. ethos chat profile)Run from ethos gateway or ethos serve
input_invalidactive_hours is not HH:MM-HH:MM or has equal start and end, or create sets it on a one-shot scheduleUse a window like 09:00-21:00, or a recurring schedule
execution_failedupdate 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_invalidCron expression failed isValidCronExpressionFix the 5-field syntax
input_invalidMissing required field for the actionProvide it
input_invalidAny action called without personality contextEnsure a personality is active
input_invalidJob not found: <id> — the id does not exist or belongs to another personalityUse an id from list
input_invalidupdate/pause/resume/run/remove on a source: 'system' jobManage it in ~/.ethos/config.yaml
execution_failedrun and the agent's run produces an errorInvestigate 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​