Skip to content
AGH RuntimeAutomation

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...

Scheduled jobs, manual job triggers, run history, and retry attempts share the same dispatcher path.

Job fields

FieldRequiredMeaning
nameYesHuman-readable job name.
scopeYesglobal or workspace.
workspace / workspace_idWorkspace jobs onlyTOML uses workspace; API payloads and stored jobs use workspace_id. Global jobs must leave it empty.
agent / agent_nameYes unless task is configuredTOML uses agent; API payloads use agent_name.
promptYes unless task is configuredPrompt submitted to the agent when the job fires.
scheduleYesOne of cron, every, or at.
enabledNoDefaults to true for parsed config definitions. Disabled jobs stay stored but are not registered with the scheduler.
retryNoDefaults to { strategy = "none" }.
fire_limitNoDefaults 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.

ModeRequired fieldFormatUse it for
cronexprStandard five-field cron: minute, hour, day of month, month, day of weekCalendar-based runs such as weekday review or nightly cleanup
everyintervalPositive Go duration such as 15m, 1h, or 2h30mFixed interval work such as polling or periodic health checks
attimeRFC3339 timestamp such as 2026-04-17T15:00:00ZOne-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-5

The 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-local next_run, last_scheduled_at, last_fire_id, catch_up_policy, and misfire_count
  • agh observe health -o json at health.automation.scheduled_jobs
  • the HTTP job payload, where job.scheduler mirrors 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.

Fieldnonebackoff
strategynonebackoff
max_retriesMust be 0 or omittedRequired positive integer
base_delayMust be empty or omittedRequired 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:2s

Use --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:

FieldFormat
maxPositive integer
windowPositive 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.

StatusMeaning
scheduledAGH accepted the dispatch and created a run record.
runningA session is actively processing the prompt.
delegatedThe job materialized a task run instead of a direct agent session.
completedThe agent session completed successfully.
failedThe run failed. A backoff retry may create another attempt.
canceledA pre-fire hook canceled the run or shutdown canceled the work.

List recent runs:

agh automation runs --job-id <job-id> --last 20

Read one run:

agh automation runs get <run-id>

Read job-specific history:

agh automation jobs history <job-id> --last 20

The 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.

On this page