Source: https://docs.any.org/scheduling/cron.html # Cron A `cron` trigger runs a program on a repeating schedule. The spec is either a cron expression or a fixed interval; the scheduler arms the next occurrence *forward from now* on adoption and after every fire, so occurrences missed while the owner was stopped are not replayed. `$ANCHOR` and `$TRIGGERS` come from [Scheduling](https://docs.any.org/scheduling/index.md.txt#before-you-write-a-trigger); `mailWatch@v1` is your own deployed program, sketched below. ## Spec | Spec | Meaning | | --- | --- | | `{"cron": "0 8 * * *"}` | five-field expression (minute hour day month weekday), UTC; a leading seconds field is added for you. Six- or seven-field expressions are accepted as written | | `{"every_s": 900}` | fire every n seconds, measured from the previous fire | A cron expression that yields no next occurrence is an inert definition: the health pass stamps `lastStatus: "invalid_spec"` on the record until the spec is fixed. ## Create one ```sh 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\": \"inbox-watch\", \"fields\": { \"name\": \"inbox watch\", \"kind\": \"cron\", \"spec\": {\"every_s\": 900}, \"program\": \"mailWatch@v1\", \"args\": {\"space\": \"$SPACE\", \"chatId\": \"$CHAT\"}, \"enabled\": true, \"maxConsecutiveFailures\": 3}}]}" ``` `$ANCHOR` and `$TRIGGERS` (the anchor id and its storage collection name) come from [Scheduling](https://docs.any.org/scheduling/index.md.txt). From a program the same write is one call on the any client, which takes the store key: ```python c = use("agent:any@v1") anchor = c.bundle_child(space, "bao/v1", "bao/triggers/v1")["objectId"] c.upsert_record(space, anchor, "agent_triggers", "inbox-watch", { "name": "inbox watch", "kind": "cron", "spec": {"every_s": 900}, "program": "mailWatch@v1", "args": {"space": space, "chatId": chat_id}, "enabled": True, }) ``` `program` can be anything resolvable from the agent space: a shipped program under its alias (`agent:rollup@v1`), a connector, or a program the agent authored into the working space (unqualified `mailWatch@v1`). A 40-line program on a cron beats scheduling a full reasoning turn every five minutes. ## The program side The program's `main(args)` receives the record's `args` verbatim. A cron job is a normal run: full trace, fuel budget, wall deadline. Long jobs checkpoint their own cursor in the space and exit before the fuel cliff — the pattern is on [Limits](https://docs.any.org/programs/limits.md.txt) — so a periodic trigger drains a backlog one budgeted slice at a time. ```python """Summarize new mail every quarter hour; posts only when something matters.""" __any_tool__ = False def main(args): c = use("agent:any@v1") gmail = use("connectors:gmail@v1") # a connector from the connectors overlay new = fetch_new(gmail) # your own helper: pages the connector, # keeps a cursor in the space for m in new: if looks_important(m): c.chat_send(args["space"], args["chatId"], {"text": f"📬 {m['snippet']}", "agent": {"name": "bao", "done": True}}) return {"ok": True, "checked": len(new)} ``` ## Missed occurrences do not exist The scheduler computes the next due time from *now* — when the owning device adopts the record, after each fire, whenever the definition is edited, and, for the standing jobs, on an election takeover. A device that was off for a night fires the 8:00 digest tomorrow, not eight times on wake. This is a deliberate guard against the wake-and-replay burst, and it is the opposite of the [once](https://docs.any.org/scheduling/once.md.txt) rule. Consequences: - Editing `spec`, `program`, `args`, `name`, `limits` or `maxConsecutiveFailures` rebuilds the entry and re-arms it forward. - Flipping `enabled` false → true resets the circuit breaker and re-arms forward; other `enabled` edits are honored in place. - `every_s` counts from the previous fire, so a job that takes a while drifts; use a `cron` expression when the wall-clock time matters. > **Note.** A claimed cron runs only on its owner, so an election handover never double-fires it. The exposure is the ≤ one-poll window in which two devices both believe they are active: both may claim a brand-new unassigned record before the record converges on one owner, and the standing jobs can overlap. Make programs idempotent where a duplicate run would hurt — most jobs that write through `upsert` already are. See [Device pins](https://docs.any.org/scheduling/device-pins.md.txt). ## Pause, edit, delete Pausing is `enabled: false` on the record; deleting the record evicts it from the registry within a tick. A deleted id stays tombstoned server-side, so recreating a trigger means a new id. The running agent's control API enables, disables and edits `spec` on triggers it has already adopted and writes those through to the record; creating and deleting are record writes only — see [Runs and monitoring](https://docs.any.org/scheduling/runs-and-monitoring.md.txt). ## The standing jobs The shipped agent runs a small set of built-in crons that are code-owned: they follow the active-instance election rather than a record pin, and the reconcile never adopts, edits or evicts them. The active device writes their records, so they appear in the trigger list like any other row — but edits to those records are ignored, and every boot restores the definitions below. | id | Program | Every | Ships | | --- | --- | --- | --- | | `rollup` | `agent:rollup@v1` | 1 h | enabled | | `extraction` | `agent:extraction@v1` | 15 min | enabled | | `linkgen` | `agent:linkgen@v1` | 1 h | enabled | | `evolution` | `agent:evolution@v1` | 6 h | disabled | | `decay` | `agent:decay@v1` | 24 h | disabled | | `reflection` | `agent:reflection@v1` | 24 h | disabled |