Jobs and Scheduling
Define scheduled AGH automation jobs with cron, interval, and one-time schedules, then monitor runs and retries.
- Audience
- Operators running durable agent work
- Focus
- Automation guidance shaped for scanability, day-two clarity, and operator context.
Jobs run agents without an interactive operator. A job answers three questions:
- which agent should run
- what prompt should be submitted
- when AGH should dispatch the run
Jobs can live in TOML config, in package-provided definitions, or in the dynamic store created through the CLI or API. Config and package jobs are managed definitions: AGH keeps them in sync from their source, and only their enabled state can be overlaid at runtime. Dynamic jobs can be created, updated, and deleted through the automation API.
Execution flow
Rendering diagram...
Job fields
| Field | Required | Meaning |
|---|---|---|
name | Yes | Human-readable job name. |
scope | Yes | global or workspace. |
workspace / workspace_id | Workspace jobs only | TOML uses workspace; API payloads and stored jobs use workspace_id. Global jobs must leave it empty. |
agent / agent_name | Yes unless task is configured | TOML uses agent; API payloads use agent_name. |
prompt | Yes unless task is configured | Prompt submitted to the agent when the job fires. |
schedule | Yes | One of cron, every, or at. |
enabled | No | Defaults to true for parsed config definitions. Disabled jobs stay stored but are not registered with the scheduler. |
retry | No | Defaults to { strategy = "none" }. |
fire_limit | No | Defaults to max = 12, window = "1h" unless the automation default is changed. |
Schedule modes
Each schedule mode accepts exactly the fields it needs. Supplying fields from another mode is a validation error.
| Mode | Required field | Format | Use it for |
|---|---|---|---|
cron | expr | Standard five-field cron: minute, hour, day of month, month, day of week | Calendar-based runs such as weekday review or nightly cleanup |
every | interval | Positive Go duration such as 15m, 1h, or 2h30m | Fixed interval work such as polling or periodic health checks |
at | time | RFC3339 timestamp such as 2026-04-17T15:00:00Z | One-time future execution |
Cron expressions use five fields. AGH does not accept a seconds field.
# minute hour day-of-month month day-of-week
0 9 * * 1-5The scheduler skips a one-time at job if its timestamp is already in the past when the job is
registered. After an at job fires, AGH unregisters it from the scheduler.
Durable scheduler state
AGH stores one durable scheduler cursor per scheduled job. The cursor records the next scheduled fire time, the last scheduled fire time, the last fire ID, the catch-up policy, and misfire counters. When a scheduled fire is due, AGH advances this cursor and creates the run reservation before dispatching work to the agent. If the daemon stops after the cursor advances, restart resumes from the next cursor and does not dispatch the already claimed fire again.
The current catch-up policy is skip_missed. On boot, if the durable cursor points to a fire time
that passed while the daemon was down, AGH records the missed fire as a misfire and advances the
cursor to the next future fire. It does not dispatch stale scheduled work during boot
reconciliation.
Inspect scheduler state through:
agh automation jobs get <job-id>for the job-localnext_run,last_scheduled_at,last_fire_id,catch_up_policy, andmisfire_countagh observe health -o jsonathealth.automation.scheduled_jobs- the HTTP job payload, where
job.schedulermirrors the durable scheduler cursor
Cron
[[automation.jobs]]
scope = "workspace"
workspace = "/Users/you/src/checkout-api"
name = "weekday-review"
agent = "reviewer"
prompt = "Review the current worktree and write a risk report."
schedule = { mode = "cron", expr = "0 9 * * 1-5" }Create the same dynamic job with the CLI:
agh automation jobs create \
--name weekday-review \
--scope workspace \
--workspace /Users/you/src/checkout-api \
--schedule "0 9 * * 1-5" \
--agent reviewer \
--prompt "Review the current worktree and write a risk report."Every
[[automation.jobs]]
scope = "workspace"
workspace = "/Users/you/src/checkout-api"
name = "half-hour-health-check"
agent = "operator"
prompt = "Check repository health signals and report anything that needs attention."
schedule = { mode = "every", interval = "30m" }CLI schedules use the every: prefix for interval jobs:
agh automation jobs create \
--name half-hour-health-check \
--scope workspace \
--workspace /Users/you/src/checkout-api \
--schedule every:30m \
--agent operator \
--prompt "Check repository health signals and report anything that needs attention."At
[[automation.jobs]]
scope = "workspace"
workspace = "/Users/you/src/checkout-api"
name = "release-readiness-check"
agent = "release"
prompt = "Run the release readiness checklist and summarize blockers."
schedule = { mode = "at", time = "2026-04-17T15:00:00Z" }CLI schedules use the at: prefix. The CLI accepts RFC3339 and also normalizes local
YYYY-MM-DDTHH:MM or YYYY-MM-DDTHH:MM:SS input to UTC.
agh automation jobs create \
--name release-readiness-check \
--scope workspace \
--workspace /Users/you/src/checkout-api \
--schedule at:2026-04-17T15:00:00Z \
--agent release \
--prompt "Run the release readiness checklist and summarize blockers."Retry policy
Retries only happen after a persisted run reaches failed. AGH does not retry validation errors,
concurrency rejections, fire-limit rejections, canceled runs, or delegated task runs.
| Field | none | backoff |
|---|---|---|
strategy | none | backoff |
max_retries | Must be 0 or omitted | Required positive integer |
base_delay | Must be empty or omitted | Required positive Go duration |
backoff uses exponential delay: base_delay, then twice the base, then four times the base, and
so on until max_retries is reached. The built-in helper default for a backoff policy is
max_retries = 3 and base_delay = "2s", but config is explicit when you write the fields.
retry = { strategy = "backoff", max_retries = 3, base_delay = "2s" }With the CLI:
agh automation jobs create \
--name daily-code-review \
--scope workspace \
--workspace /Users/you/src/checkout-api \
--schedule "0 9 * * 1-5" \
--agent reviewer \
--prompt "Review the changed files and write a concise risk report." \
--retry backoff:3:2sUse --retry none to disable retries explicitly.
Fire limits
Fire limits cap how often a job can dispatch within a rolling window. They protect the daemon from accidental schedule floods and retry loops.
fire_limit = { max = 1, window = "24h" }Both fields are required when a fire limit is provided:
| Field | Format |
|---|---|
max | Positive integer |
window | Positive Go duration such as 1h, 12h, or 24h |
The default fire limit is 12 fires per 1h. You can change the default for all automation
definitions:
[automation]
default_fire_limit = { max = 6, window = "1h" }Worked example: daily code review
This workspace job asks the reviewer agent to inspect the repository every weekday at 09:00 UTC,
allows one run per day, and retries failed runs with exponential backoff.
[automation]
enabled = true
timezone = "UTC"
max_concurrent_jobs = 5
default_fire_limit = { max = 12, window = "1h" }
[[automation.jobs]]
scope = "workspace"
workspace = "/Users/you/src/checkout-api"
name = "daily-code-review"
agent = "reviewer"
prompt = "Review the changed files and write a concise risk report."
schedule = { mode = "cron", expr = "0 9 * * 1-5" }
retry = { strategy = "backoff", max_retries = 3, base_delay = "2s" }
fire_limit = { max = 1, window = "24h" }Run it on demand without waiting for the next scheduled fire:
agh automation jobs trigger <job-id>Monitoring runs
Every accepted dispatch writes an automation_runs row. Runs are listed newest first.
Scheduled runs include a stable fire_id and scheduled_at timestamp so operators can connect run
history back to the scheduler cursor. Delivery failures are recorded as delivery_error and
delivery_error_at; they do not roll back or rewrite scheduler cursor state.
| Status | Meaning |
|---|---|
scheduled | AGH accepted the dispatch and created a run record. |
running | A session is actively processing the prompt. |
delegated | The job materialized a task run instead of a direct agent session. |
completed | The agent session completed successfully. |
failed | The run failed. A backoff retry may create another attempt. |
canceled | A pre-fire hook canceled the run or shutdown canceled the work. |
List recent runs:
agh automation runs --job-id <job-id> --last 20Read one run:
agh automation runs get <run-id>Read job-specific history:
agh automation jobs history <job-id> --last 20The HTTP API exposes the same history:
curl -sS "http://localhost:2123/api/automation/jobs/<job-id>/runs?limit=20"Job responses include scheduler metadata such as next_run when a job is currently registered.