Skip to main content
Say twenty startups arrive in one event and each needs its own agent to investigate it. You fan out one event per startup, each startup gets its own flow instance with its own agent, and a join collects the results and says when they’re all in. You declare how to split and what counts as done; the platform owns dispatch, duplicates, and deadlines. No counters, no polling, no shared state.

Fan out: one event per item

scout/nodes.yaml
identity names the stable per-item key; max_items bounds the fan-out. Record the member list (and a window key, such as a batch id) in entity fields — the join below reads them. A window groups arrivals into one batch — here, every result carrying the same batch_id counts toward the same join.

One instance per item

The receiving flow declares its identity once, and instances mint themselves on first delivery — there is no creation call:
flows/matcher/schema.yaml
Every selecting input pin must carry a field named after instance:; the pin’s instance key derives from it. Each instance materializes its own copy of the flow’s declared agents — one live agent per (agent declaration, instance) pair — so one instance finishing never affects a sibling’s agent, and memory: true agents get one conversation per instance.

Fan in: the barrier join

Convergence is owned by two declarations on the collecting flow: a fan-in resolution pin (which events count, how they deduplicate, which window they belong to) and a staged join: row (membership, completion, timeout):
scout/schema.yaml (input pin)
scout/nodes.yaml (join row)
The join arms when the entity enters the waiting stage. It settles exactly once per window — on completion or on timeout, whichever comes first. Two aggregation modes exist:
Do not hand-roll this with accumulate and an expected_count comparison: that pattern has no dedup, no timeout, and deadlocks when the last result arrives before the expected count is written.
The canonical runnable version of this whole shape is examples/routing/fan-in/barrier.

The singleton coordinator

When many template instances need shared state — a rate budget, an aggregate score, a cross-instance learning table — that state gets one owner: a mode: singleton coordinator flow whose entity holds it as typed contained map/list fields. Instances report to the coordinator through ordinary events (often the fan-in pin above); the coordinator’s handlers update the contained state deterministically.
Two rules:
  • Items in that contained state have no lifecycle of their own — no timers, retries, or terminal stage. If an item needs those, promote it to a template instance.
  • The coordinator role is explicit — a plain mode: static flow does not qualify.

Bounded loops: declared revision cycles

When work legitimately cycles — draft, review, revise, review again — the cycle is declared, capped, and given an explicit exit. Declare the cycle with loops: rather than hand-rolling a counter in a guard:
nodes.yaml
Four operations cover a loop’s life: start creates the loop’s durable record at attempt 1, admit accepts work for the current revision, repeat increments the attempt and mints a new revision (or, at the cap, atomically takes the declared escape), and close exits the region. max_attempts is evaluated once at start and stamped into the activation — editing policy mid-run never changes an active loop’s cap, and replay sees the same cap the run did. Every operation declares its exact from: stage; the runtime accepts no inferred source. Verify checks the loop’s shape statically: one start from outside the cycle, at least one repeat and close, an escape target outside the cycle, and every handler inside the cycle declaring its loop operation. Nested and unbounded loops are rejected.

Stage timers: time as a lifecycle edge

Both shapes above compose with stage timers — one-shot temporal rows declared on the stage itself:
schema.yaml
A timer arms when the entity enters the stage and cancels when it leaves. A row with advances_to is a real lifecycle edge — reachability analysis and describe --graph see it.