Spec to a compiled plan — represented as a Petri net — running multiple agents
Brunch orchestrates multi-agent work: it turns a spec into a compiled plan, represents that plan as a Petri net, and runs multiple coding agents against it. The goal is to build code in parallel in a more predictable way — making explicit which steps can run at once, which must wait, and when a step counts as done.
Dependencies in the plan become arcs in the net, so the net already knows what waits for what.
Spec
What you produced — meaning, requirements, criteria.
Plan
Epics & slices with a depends_on DAG.
Net
Places, transitions, tokens. Deps become arcs.
Build
Each transition fires an agent in an isolated worktree.
Tokens carry pointers, not code: what each step actually did goes to a log on disk, which keeps the net itself small and fast to reason about.
A plan is a list of epics and slices. Each slice names an exact change, a real test target, and what it depends on:
slices:
- id: types
definition: "Todo types in src/types.ts"
verification: [{ kind: unit-test, target: tests/types.test.ts }]
depends_on: []
When the plan compiles, each depends_on becomes an input arc in the net. A step's transition can only fire once a token has arrived on every input arc — so a service slice that depends on both store and validation waits until both have produced their tokens. The dependency graph you wrote in YAML becomes the firing rules that govern the run.
service is a join — its transition fires only once both store and validation hold a token.
Every slice compiles to the same little subnet, with two lanes. One lane writes and runs the tests; the other checks the result is actually what the spec asked for. A step is completed only when both lanes are happy.
Mechanical lane · does it run?
↺ fail · rewrite · ↑ pass · re-check
Semantic lane · is it the right thing?
↩ rejected · back to tests · ✓ satisfied · completed
The semantic lane judges intent against the spec — not just green tests. A retry budget stops the step if it runs out.
The mechanical lane asks does it run? — it writes tests, writes code, and runs the tests, looping on failure within a retry budget. The semantic lane asks is it the right thing? — it judges the result against the spec's intent (criteria, integration, reachability), not just a green test suite.
A step is done when the tests pass and the work matches what the spec asked for. If the tests are green but the result isn't what the spec described, the semantic lane sends it back to the tests rather than marking it complete. The run-tests step spends from a retry budget; when the budget is exhausted, the step stops with a reason instead of looping forever.
Take the bundled layered-todo demo. Its slices form a small diamond and a downstream epic:
types has no dependencies, so it can fire first.store and validation both depend on types, so they run in parallel once it's done.service is a join — it can't fire until both store and validation have finished.cli epic (add, list, done, e2e) waits behind a cross-epic gate until the whole core is complete.Watching the run in Petrinaut, you see tokens flow left to right: parallel where the plan allows it, holding at each join until its inputs are ready.
The link to the live view is one-way: cook runs the build and streams it; Petrinaut subscribes and renders.
execution authority
Runs the net, dispatches one agent per step, owns all state.
localhost
Ephemeral SSE server with a replay buffer, so a late joiner re-reads the whole run.
browser canvas
Folds each event onto the marking and animates the live net.
cook stays the single execution authority — Petrinaut only renders, and can never drive the run. Because every firing is also written to petrinaut-events.jsonl on disk, the same run replays later with no server running at all.
Brunch is an active research prototype. The current direction includes:
To see all of this in action, follow the Run guide.