Source: https://docs.any.org/scheduling/index.html # Scheduling A trigger is a record in the `agent_triggers` dataset of your agent space: a program spec, a schedule or event, arguments, and an `owner` naming the device that runs it. The running agent converges its scheduler on those records every tick, so creating, editing, pausing and moving a job is just writing a record — from the UI, from a program, or from `curl`. The record syncs; the execution does not. A trigger fires only on a device where `anyrt` is running, and a stored trigger keeps no stopped device alive. The pinned device runs local work offline; a program that calls a provider needs that provider reachable. ## Before you write a trigger The server and `anyrt serve` are up ([runtime quickstart](https://docs.any.org/quickstart/anyrt.md.txt)); the runtime provisions the trigger store on boot. The program you name resolves from the working space or a configured overlay. The shell examples use `$SPACE` (the agent space id) and `$CHAT` (the target chat's object id). `anyrt run --from-space` runs the same program once by hand — the way to check its arguments before scheduling it. ## A trigger record ```json { "name": "daily history rollup", "kind": "cron", "spec": {"cron": "0 8 * * *"}, "program": "agent:rollup@v1", "args": {"space": "bao", "chatId": ""}, "owner": "", "enabled": true, "maxConsecutiveFailures": 3 } ``` | Field | Meaning | | --- | --- | | `kind` | `cron`, `once` or `event` | | `spec` | `{"cron": ""}` or `{"every_s": n}` · `{"at": }` · `{"dataset": "chat_messages", "objectId", "spaceId?", "filter?"}` | | `program` | a program spec — `agent:remind@v1`, a connector, or one you authored | | `args` | the JSON passed to `main(args)` | | `owner` | the device pin — a peer id, or `""` for unassigned (the active device claims it) | | `enabled` | pause without deleting (absent = `true`) | | `limits` | `{fuelPerRun?, timeoutS?, maxCostPerRun?}` — part of the definition, not enforced; every run gets the runtime's fixed budget ([Limits](https://docs.any.org/programs/limits.md.txt)) | | `maxConsecutiveFailures` | circuit-breaker threshold (default 3) | The runtime stamps three fields back onto the record — `lastRunAt`, `lastStatus` and `consecutiveFailures` — which is scheduler state, not history. Every run's history is a synced summary in the `agent_runs` dataset, keyed by `triggerId`; see [Runs and monitoring](https://docs.any.org/scheduling/runs-and-monitoring.md.txt). ## Where the records live Triggers sit on the **trigger anchor**: the `bao/triggers/v1` child of the agent space's `bao/v1` bundle, which `anyrt serve` provisions on boot with the hidden harness type `agent_trigger`. Resolve it by seed, never by name; the child route requires that `type`. The records live in a storage collection namespaced to the harness type (`_agent_triggers`); read its name from the space's dataset listing: ```sh TRG=$(curl -s "http://127.0.0.1:7001/v1/spaces/$SPACE/types?includeHidden=true" \ | jq -er '.types[] | select(.xKey=="agent_trigger") | .id') ANCHOR=$(curl -s -X POST http://127.0.0.1:7001/v1/spaces/$SPACE/bundles/bao%2Fv1/children \ -H 'Content-Type: application/json' \ -d "{\"seed\": \"bao/triggers/v1\", \"type\": \"$TRG\"}" | jq -er .objectId) TRIGGERS=$(curl -s http://127.0.0.1:7001/v1/spaces/$SPACE/datasets \ | jq -r '.datasets[].name | select(endswith("_agent_triggers"))') curl -s -X POST http://127.0.0.1:7001/v1/spaces/$SPACE/upsert \ -H 'Content-Type: application/json' \ -d "{\"objectId\": \"$ANCHOR\", \"dataset\": \"$TRIGGERS\", \"records\": [{\"id\": \"daily-digest\", \"fields\": { \"name\": \"daily history rollup\", \"kind\": \"cron\", \"spec\": {\"cron\": \"0 8 * * *\"}, \"program\": \"agent:rollup@v1\", \"args\": {\"space\": \"$SPACE\", \"chatId\": \"$CHAT\"}, \"enabled\": true}}]}" ``` The record id is yours to choose (a slug). Guest code passes the store key instead — `c.upsert_record(space, anchor, "agent_triggers", …)` — and the `any@v1` client resolves the collection. The owning device adopts the record within one 5-second tick. This example schedules the shipped history rollup at 08:00 UTC. Adoption does not run it immediately: a cron arms its next future occurrence. Look for `daily-digest` in the [trigger registry](https://docs.any.org/scheduling/runs-and-monitoring.md.txt#the-control-api), then read its `agent_runs` summaries after it fires. ## How a tick works ``` every 5 s, on each running device: reconcile ── read agent_triggers ──▶ adopt / claim / refresh / evict health ── mark inert definitions (invalid_spec, unsupported_source) events ── one chat watch per watched (space, chat) fire ── due entries → run program → agent_runs summary + record stamp ``` The dataset is the source of truth. A registry-only edit would be reverted by the next reconcile, which is why the control-plane routes write through to the record. > **Why it matters.** There is no scheduler service. The schedule syncs with your data, encrypted, to every device you own; whichever device holds the pin fires it, and the run summary lands in the same space. A laptop without a network still fires its pinned jobs; shut it down and they pause, and repinning them to a machine that is running moves them within a tick. ## Semantics in one table | | cron | once | event | | --- | --- | --- | --- | | fires when | `now ≥ next occurrence`, armed strictly forward from adoption | `now ≥ at` and never ran | a new message lands in the watched chat | | missed while owner down | does not exist — no catch-up burst | fires late on the next tick | does not exist — live only | | after firing | re-arms forward | auto-disables, stays as its audit trail | keeps watching | | failure | counts toward the breaker | consumes the shot — no retry | counts toward the breaker | Moving the active agent to another device does not move an already pinned user trigger. Change its `owner` to move it. For work that spans several executions, save a cursor or checkpoint in the database and resume it on the next run; each invocation still has the [runtime's limits](https://docs.any.org/programs/limits.md.txt). [**Cron** — Recurring schedules — five-field expressions or every\_s, strictly-forward arming](https://docs.any.org/scheduling/cron.md.txt) [**Once** — One-shot triggers for reminders and delayed work — late beats lost](https://docs.any.org/scheduling/once.md.txt) [**Event triggers** — Fire a program on every new chat message, and the chat responder record](https://docs.any.org/scheduling/event-triggers.md.txt) [**Device pins** — Which device runs a trigger: owner, claiming and the active-instance election](https://docs.any.org/scheduling/device-pins.md.txt) [**Runs and monitoring** — Run summaries, the circuit breaker, health markers and the control API](https://docs.any.org/scheduling/runs-and-monitoring.md.txt)