Skip to main content

Humans as a first-class actor

A human decision in Swarm is a typed, durable object — not a chat message and not a webhook callback. You choose per transition where a human belongs; everything else stays autonomous — autonomy is a dial, not a switch.

Decision gates: pause a stage for a verdict

The primary surface is the stage gate — a stage that waits for a typed decision, with every outcome declared:
schema.yaml
When an entity enters the stage, the platform creates a decision card — a typed object carrying the context you declared, the possible verdicts, and any input an outcome requires. Channels (Telegram ships first; any renderer you attach) display the card; the decision itself is committed through one API — which means a decision made on your phone and a decision made through the CLI or API are the same object, and the flow resumes identically. If the facts change after a card is shown, the old card stops being valid: an approval based on stale information is rejected rather than applied to the new situation. Every outcome is a declared lifecycle edge: advances_to and emit are verified statically, so “what happens if they reject?” has an answer at boot, not at 2am. Two rules to know before your first gate:
  • Gate emits carry only literals and decision.<field> values — they cannot read the entity. To emit a business event enriched with entity data after an approval, advance to a small relay stage and let its handler build the event (gate → awarding → handler enriches from entity → terminal). Budget one relay stage per outcome that needs entity data.
  • Declare the platform as the producer of any event only a gate emits: swarm: {producer: mailbox_human} on the event declaration — otherwise verify reports event_producer_exists (gate emits do not count as ordinary producers). The platform itself never decides — no timeout auto-approves; an expired card escalates or defers, loudly.

The mailbox

Gates pause a stage until someone answers; the mailbox is a queue of work items that do not block a stage. Gates produce decision cards; mailbox items carry decision sheets. Alongside gates, the mailbox is the persisted queue for human tasks — work items that are not a stage verdict (review this draft, investigate this alert). A handler writes an item with the mailbox_write action, declaring the item type, severity (normal, urgent, critical), a summary, and an explicit payload. If the same event is delivered twice, you still get one item, not two. Each item carries a server-built decision sheet: the entity context and a preview of what happens downstream, so an operator can decide without reconstructing the situation by hand.
nodes.yaml
To make the human step conditional (auto-approve under a limit, human review above it), put the action on a rules: branch. The matched rule owns its action; the unmatched branch does not fire. Both branches run as part of the same atomic handler transaction:
nodes.yaml

Decisions

An operator acts on a pending item in one of three ways:
  • Approve: emits the configured downstream event, resuming the flow.
  • Reject: records a reason; no downstream event.
  • Defer: postpones the item until a given time.
A decided item cannot be decided again (a second attempt returns a conflict). Approvals on review, approval, and operational-decision items emit mailbox.item_decided, which the flow subscribes to in order to continue. mailbox.item_decided is platform-emitted and auto-registered: subscribe to it directly. Don’t declare it in your own events.yaml — the platform owns that name. The payload carries mailbox_id, decision (approved | rejected | deferred | expired), decision_payload, and source_entity_id, plus provenance fields; the full payload is in the API reference.
nodes.yaml
Branch on payload.decision in a rules: block (or in a guard) to handle approvals, rejections, and deferrals differently. The handler above shows the approve path only.

Budget escalations

Cost control surfaces through the same channel. Token usage is tracked per entity and per actor; when spend crosses a declared threshold the platform emits platform.budget_threshold_crossed with a level (warning, throttle, emergency), and an emergency raises a mailbox item for a human.

Operate the mailbox

The CLI and API for acting on pending decisions.