docs

Scheduling

Runs and monitoring

Every fire is a normal program run: it writes a trace, a row in the agent_trigger_runs dataset, and a rollup onto the trigger record itself. "Did it run, how long did it take, what did it cost, why did it stop" are answered from records — no log grepping.

The run record

After each fire the runtime upserts one row into agent_trigger_runs on the trigger anchor, id <triggerId>:<timestamp-ms>:

{
  "triggerId": "daily-digest",
  "ts": 1756108800.4,
  "status": "ok",
  "durationMs": 2311,
  "error": null,
  "traceRef": "run_9f3c…",
  "fuel": 184223,
  "costUsd": null
}
status Meaning
ok main returned
error the program raised, or the run could not start
interrupted wall timeout, fuel exhaustion or a hard break

traceRef names the device-local trace: anyrt trace show run_9f3c… renders it — every effect, every mutation marked. Traces stay on the device that ran the job; the run record is what syncs.

curl -s -X POST http://127.0.0.1:7001/v1/spaces/$SPACE/query \
  -H 'Content-Type: application/json' \
  -d "{\"objectId\": \"$ANCHOR\", \"dataset\": \"agent_trigger_runs\",
       \"filter\": {\"triggerId\": \"daily-digest\"}, \"sort\": [\"-ts\"], \"limit\": 20}" \
  | jq '.records[] | {ts, status, durationMs, error, traceRef}'

The rollup on the trigger

The same fire updates the trigger record, so a single list query monitors everything:

Field Meaning
lastRunAt time of the last fire
lastStatus ok / error / interrupted, or a marker (below)
lastDurationMs, lastFuel, lastCostUsd the last run's resource use
runCount fires since the record was created
consecutiveFailures reset to 0 by an ok run
lastRunRef the last run's trace id

The circuit breaker

maxConsecutiveFailures (default 3): when consecutiveFailures reaches it, the trigger auto-disables — enabled: false, lastStatus: "auto_disabled", the reason in the last run record. Re-enabling is a manual act, and it resets the counter and re-arms the schedule forward. A background program is budgeted per run, observable per query, and self-quarantining on repeated failure.

Health markers

An enabled trigger that can never fire is marked, not silent. Each tick the owning device stamps lastStatus on its own inert entries:

Marker Cause
invalid_spec a cron expression with no next occurrence; a once without a numeric at
unsupported_kind kind: "event" — reserved in the shape, no evaluator yet

Markers clear once the definition is fixed and are overwritten by the first real run status. The ticker's other silent paths — overlays still syncing, a failed reconcile query — log on transitions, never per tick, so "why isn't it firing" is answerable from the record or one log line.

Why it matters. The record is the monitoring surface. Any client that can query a dataset — the desktop UI, curl, a program on another device — sees the same rollup, offline, without a dashboard service. The trace behind lastRunRef is the full story when the summary isn't enough.

The control API

A running anyrt serve exposes a loopback control API (default 127.0.0.1:7010, [agent].control_port). It reads the live registry and writes mutations through to the dataset — the record is the source of truth, and a registry-only edit would be reverted by the next reconcile tick.

Route Effect
GET /triggers every registered trigger as a rollup row: definition, owner, enabled, last-run fields, failureRate, limits
GET /triggers/:id the full record
GET /triggers/:id/runs the 20 newest run records
PATCH /triggers/:id {spec?, enabled?} — a new spec re-arms forward
POST /triggers/:id/enable enable, reset the breaker, re-arm
POST /triggers/:id/disable pause
POST /run {program, args?} or {source, args?, program?} — a one-shot run through the serve resolver, returning {status, value, error, traceRef, durationMs, fuelUsed}
GET /election {app, enabled, active, peerId, winner}
curl -s http://127.0.0.1:7010/triggers | jq '.[] | {id, enabled, lastStatus, runCount, failureRate}'
curl -s -X POST http://127.0.0.1:7010/triggers/daily-digest/disable
curl -s -X PATCH http://127.0.0.1:7010/triggers/daily-digest \
  -H 'Content-Type: application/json' -d '{"spec": {"cron": "0 9 * * 1-5"}}'
curl -s -X POST http://127.0.0.1:7010/run \
  -H 'Content-Type: application/json' -d '{"program": "agent:rollup@v1", "args": {"space": "bao", "chatId": "…"}}'

Creating and deleting triggers is done on the record (see Scheduling); the control API operates on ids the reconcile has already adopted here. POST /run runs inline on the control thread and is not gated by the election — an explicit invocation, same as the CLI.

Reading a run's trace

anyrt trace ls --program rollup        # runs of one program, newest first
anyrt trace show run_<id>              # turns, cells, effects (* = mutate)
anyrt trace show run_<id> --stats      # tokens, cache, cost per turn
anyrt trace stats traces/              # fuel/duration/token distributions over all runs

Cron runs typically outnumber conversations many times over in a traces directory — filter with --program. Details on the format and the viewers: Traces and replay.

Progress for long runs

A run that takes more than a few seconds reports through the progress@v1 tool — start / tick / done / fail — which rides the server's process registry and renders as a global bar in the UI; done(..., notify=…) posts the outcome into the chat so the agent reports it. See Processes and Progress and UI.