Flows
A flow is a typed, durable graph of work. Each node is an agent, a reviewed action, a pure data step or another flow; edges carry typed values between named ports. The daemon compiles the graph, runs it, checkpoints it, and can stop, resume or replay it after a restart, so long jobs (plan a feature, implement every bead, land the PRs) run without someone babysitting them.
The example, in full
This is the flow in the diagram. It compiles as written with caco flow validate --file triage.flow.yaml.
flow: lighthouse.triage
imports:
t: ./types.jtd.yaml
map: caco://std.map
in:
request: t.Request
out:
summary: {type: t.Summary, checkpoint: true}
report: {artifact: text/markdown, checkpoint: true}
nodes:
collect:
action: {name: lighthouse.collect-errors, node: vega}
in: {request: t.Request}
out: {groups: t.ErrorGroupList}
fix:
agent: {mode: ephemeral, timeout: 1h}
instructions: Reproduce and fix one error group in your own worktree.
in: {group: t.ErrorGroup}
out: {result: t.FixResult}
review:
action: lighthouse.review-fixes
in:
results: t.FixResultList
decision: t.Decision
out:
request: t.ReviewRequest
summary: t.Summary
report: {artifact: text/markdown}
suspend: {request: request, resume: decision, policy: park, ttl: 3d}
edges:
- from: in.request
to: collect.request
- use: map.map
from: collect.groups
through: {node: fix, in: group, out: result}
concurrency: 3
all: review.results
- from: review.summary
to: out.summary
- from: review.report
to: out.report
Types are JSON Type Definition in a separate module (types.jtd.yaml), imported under a short alias. A map source must be a named list type (ErrorGroupList: {elements: {ref: ErrorGroup}}), and all: gathers the results into another one.
Nodes
Every node has named in and out ports and exactly one executor:
| Executor | What runs | Use it for |
|---|---|---|
agent | An ordinary managed agent with its own worktree, profile and lifecycle. It returns values with caco flow emit. | Judgement: planning, implementing, reviewing. mode: ephemeral for one turn; persistent/session for a long-lived worker reused across items or iterations. |
action | A reviewed configured action, optionally pinned to a node ({name, node}), with durable receipts and idempotence. | I/O and deterministic computation: fetch, publish, join a merge queue. |
data | A pure operation: identity, literal, select, merge or digest. No shell, files, network, clock or randomness. | Reshaping values and stable hashes without starting an agent. |
flow | Another flow, imported by name; its ports must match exactly. | Composing larger pipelines from tested pieces. |
loop | An acyclic iteration flow run repeatedly over a typed state. | Repeat-until-done work with explicit bounds (see loops). |
A node can also suspend: it emits a request port and waits, up to a TTL, for a typed response, which arrives through the Operator Inbox or caco flow input. An agent node can be bound to a bead with assignment, which claims the bead when the agent starts and withholds its outputs until normal reintegration succeeds; recurring repository work without a bead uses lifecycle: {completion: reintegrated} instead.
Edges and values
- Direct:
{from: producer.port, to: consumer.port}. Flow inputs arein.name; outputs areout.name. - Map:
use: map.mapruns one node per list element, at mostconcurrencyat a time, then gathers withall:or streams each result witheach:. - Collections: a collection port receives items one by one;
collection: eachstreams them into the next collection,collection: sealedwaits for the complete set. - Ordering only:
requiresruns a node after another without passing a value. - Checkpoints:
checkpoint: trueon a port makes it a durable replay boundary.artifact: text/markdownmakes it a content-addressed file, downloadable withcaco flow artifact.
A real pipeline: std.feature.delivery
The standard library ships whole journeys built from these parts. std.feature.delivery takes a feature request to merged pull requests: plan, review, create beads, implement them layer by layer, and land the results through Cara.
Nothing mutates the board until you approve the plan: a revise or cancel decision is rejected before any bead is created. Workers claim their beads natively, so they show up in Agents and Beads like any other work, and the target project's reintegration policy (not the flow) decides how each change lands.
Loops
Edges are always acyclic. Iteration is a loop node around an acyclic flow that takes and returns the same typed state plus a decision; the engine starts a new child run for each continue and emits the state on done.
Loops are bounded by max_iterations and ttl. A long-lived service instead sets until_cancelled: true with an interval, so an empty queue waits for the next round rather than busy-polling. Iteration count, state, child runs and decisions are checkpointed, so a loop resumes where it was after a restart.
Runs
A run is one execution of a flow. The daemon on its owner node compiles the exact revision, stores the run and its events in SQLite, and checkpoints every accepted value, so a run survives restarts and can be inspected long after it finishes.
- Starting:
caco flow run --flow NAME(or--filefor an uncommitted flow) with--input-file,--set port=valueor--prompt. Automation should add a stable--request-idand--owner-node: an identical retry returns the same run, a different payload under the same ID is refused, and--revisionpins a reviewed revision. - Agents: agent nodes start ordinary managed agents, so they appear with the rest of your agents and follow their profile. They return values with
caco flow emit; the daemon refuses a value that doesn't match the port's type. - Actions: each invocation records a durable receipt. After a restart, a started action is replayed only when its contract says the exact request is idempotent; otherwise the run fails rather than doing the work twice.
- Your decisions: a suspension appears in the Operator Inbox on every surface; answer it there or with
caco flow input --run … --node … --value-file ….
Run and node states
Scheduled and event-triggered runs
A flow can declare activations that start runs for it: cron on a schedule, or feed_event when a matching event appears in the feed (for example a critical log_error).
activations:
nightly:
kind: cron
schedule: "0 7 * * *"
node: atlas
input: activation
policy: {overlap: skip, replay: retry, max_concurrency: 1}
Each activation names one exact node, so replicated registries never start the same run twice, and the daemon records which event or schedule minute started which run against which compiled revision. caco flow activations --project NAME lists those receipts. To wait for something inside a run that already exists, use std.event.await instead.
Writing a flow
caco flow scaffold --name team.task --output-dir /tmp/taskwrites a minimal flow and type module.caco flow validate --file /tmp/task/task.flow.yamltype-checks ports, imports and topology; fix every reported path rather than loosening a type.caco flow explain --file …lists executors, ports and suspensions;caco flow graph --file … --format mermaid(ordot) draws it.caco flow simulate --request-file sim.jsonruns it in memory with fixture outputs: the real compiler and runtime, but agents and actions only record what they would do.caco flow run --file … --project NAMEruns it for real. Move it under.cacophony/flows/once it should become a reviewed project flow.
The caco binary embeds the Cacophony project's .cacophony/flows/ and its flow rollout inventory at build time, so a node without the Cacophony checkout still has those flows, governed by the embedded inventory.
When a checkout's flow root exists it replaces the embedded set as a whole, so one registry never mixes revisions.
Before running a generated flow, check that every map is bounded, every loop has limits (or a paced until_cancelled), every human decision has a TTL, and every action exists in reviewed configuration. Agents can also propose follow-up flows for review with caco flow continuation propose; nothing starts until you approve that exact revision.
Command reference
| Command | Purpose |
|---|---|
caco flow list · show · compile | Registered flows with their revisions, one resolved graph, or its compiled IR and JSON Schemas. |
caco flow validate · explain · graph · simulate · scaffold | Author and check a flow without running it. |
caco flow run | Start a run (keyed starts: --request-id + --owner-node). |
caco flow runs · status · watch · detail | List runs, show one, stream its events (--until-terminal), or fetch its timestamps, compiled definition and source. |
caco flow stop · resume · cancel | Pause and resume with the same agents, or end the run. |
caco flow input · emit | Answer a suspended node; return a value from an agent node. |
caco flow artifact | Download an artifact referenced by a run, verified against its digest. |
caco flow activations · reports | Activation receipts; maintenance reports from the standard maintenance flows. |
caco flow continuation … | Propose, review, approve and start agent-generated follow-up flows. |
Every command is also an MCP tool. Flows and live run graphs appear in the TUI (Project › Flows), the web dashboard, the Android app and the macOS app.