diff --git a/apps/graphs/patterns/deterministic-commit.md b/apps/graphs/patterns/deterministic-commit.md index da4958a..98344a9 100644 --- a/apps/graphs/patterns/deterministic-commit.md +++ b/apps/graphs/patterns/deterministic-commit.md @@ -53,10 +53,10 @@ the graph should publish the branch. In `adversarial-developer` the documenter leaves its docs uncommitted, so `Commit` lands the code and the docs together under the one subject. -`canopy-builder` commits twice the same way: `Commit spec` lands the -spec's own message after the completeness review, and `Commit auditor -fixes` lands `fix(): audit fixes for ` after the auditor's -repairs, and both push the branch when they succeed. +`canopy-builder` commits once per spec the same way: `Commit spec` lands +the spec's own message after the auditor's repairs pass the gates again, +and pushes the branch when it succeeds. Its documenter commits the docs +separately. A second check can prove the commit landed — that HEAD moved since the spec started — but neither loop needs one: the gates and the review diff --git a/apps/graphs/src/patterns-data.generated.ts b/apps/graphs/src/patterns-data.generated.ts index c0e61ea..87a8322 100644 --- a/apps/graphs/src/patterns-data.generated.ts +++ b/apps/graphs/src/patterns-data.generated.ts @@ -14,7 +14,7 @@ export interface BundledPattern { } /** When the bundled payload last changed, ISO-8601. */ -export const PATTERNS_BUILT_AT = "2026-10-31T04:12:27.082Z"; +export const PATTERNS_BUILT_AT = "2026-11-04T09:53:54.271Z"; /** Every bundled page, in slug order. */ -export const BUNDLED_PATTERNS: BundledPattern[] = JSON.parse("[{\"slug\":\"adversarial-review\",\"page\":\"---\\ntitle: Adversarial review\\nsummary: Review the work with models of different families, one told to break it, and back the verdict with deterministic checks.\\nstatus: standard\\nimplements:\\n - graph:adversarial-developer\\n - node:reviewer\\nfigure: adversarial-review.figure.json\\nsources:\\n - title: Building effective agents (Anthropic), evaluator-optimizer workflow\\n url: https://www.anthropic.com/engineering/building-effective-agents\\n---\\n\\n# Adversarial review\\n\\n## The problem\\n\\nA model that wrote a change is a poor judge of it, and a model of the same family as the author tends to share its blind spots. A friendly review passes work that a hostile one would reject. On cheap models, where a single pass is unreliable, that gap costs real runs.\\n\\n## The pattern\\n\\nPut at least two reviewers behind the implementer. One reads constructively. The other is told to assume the diff is wrong until every line earns its trust. Both must pass.\\n\\nRun the reviewers on models from different families, so they do not fail in the same way. Then put deterministic checks (formatter, linter, tests) in front of them, so no reviewer is asked to judge what a command can decide.\\n\\nAnthropic describes the shape in its evaluator-optimizer workflow: \\\"one LLM call generates a response while another provides evaluation and feedback in a loop.\\\" The lab's version adds a second, adversarial evaluator and mixes model families.\\n\\n## The pair\\n\\nA reviewer asks \\\"is this right?\\\". An adversary asks \\\"how do I break this?\\\". Those are different jobs and they produce different findings.\\n\\nThe figure shows the serial pair: implement, then the gate, then the adversary, then the review that commits. Note the inverted edges. The adversary reports fail when it succeeds at breaking the change, and that routes back to implement. It reports pass when it could not break it, and that routes on to the review.\\n\\nThe adversary's prompt must demand a concrete reproduction: an input, a sequence, a race. Not an opinion, not a \\\"this could be risky\\\". An adversary that cannot produce one has not found anything, and saying so must be a legitimate, unpunished outcome, or it will invent findings to look useful.\\n\\nThe pair fits work that arrives from outside. An issue filed by a stranger is exactly the case where something should try to falsify the fix before it lands.\\n\\n## What we saw\\n\\nEarly in Canopy, a bug defeated Opus 4.6. The operator spent many tokens on it without a fix. MiMo 2.5 solved it on the first try. The operator no longer remembers which bug it was.\\n\\nThis is one anecdote, and it proves nothing about which model is better. The lesson the lab took from it is that the models are different: MiMo's training may have covered a problem that Opus's did not. A reviewer from another family may see what the author's family cannot.\\n\\nSince then, the lab's standard is adversarial review by models of different families, plus deterministic checks.\\n\\n## In Canopy\\n\\nA parallel ensemble holds the reviewers. Its `min_pass` is 2, so both members must pass. Member 1 uses the shared constructive prompt. Member 2 overrides it with the adversarial one. No member names a platform or model, so whoever installs the graph binds a different family to each.\\n\\nThe shape you add, with the prompt fields left out:\\n\\n```json\\n{\\n \\\"name\\\": \\\"reviewers\\\",\\n \\\"kind\\\": \\\"parallel\\\",\\n \\\"commit_rights\\\": false,\\n \\\"entry_from_node\\\": \\\"gate\\\",\\n \\\"entry_condition\\\": \\\"pass\\\",\\n \\\"on_pass_to\\\": \\\"commit\\\",\\n \\\"on_fail_to\\\": \\\"implement\\\",\\n \\\"min_pass\\\": 2,\\n \\\"timeout_minutes\\\": 30\\n}\\n```\\n\\nThe reviewers have no commit rights. A single committer lands the work only after both pass, and a failure routes back to the implementer.\\n\\nThe `adversarial-developer` graph is the baseline implementation of this pair: the reviewer fails bad diffs back, the auditor repairs what it finds, and deterministic gates judge what a command can decide. The standalone breaker agent — report FAIL with a concrete reproduction, or PASS — stays opt-in for work that arrives from outside, and is added to that loop as one more ensemble when it is wanted; the baseline ships no dedicated breaker.\\n\\n## Try it\\n\\n```sh\\ncanopy graph install canopy-builder\\n```\\n\\nBind a different model family to each reviewer member of the pair you add.\\n\",\"figureName\":\"adversarial-review.figure.json\",\"figure\":\"{\\\"edges\\\": [{\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"implement\\\", \\\"to_node\\\": \\\"gate\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"gate\\\", \\\"to_node\\\": \\\"adversary\\\"}, {\\\"condition\\\": \\\"fail\\\", \\\"from_node\\\": \\\"adversary\\\", \\\"to_node\\\": \\\"implement\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"adversary\\\", \\\"to_node\\\": \\\"review\\\"}], \\\"format_version\\\": 6, \\\"name\\\": \\\"adversarial-review\\\", \\\"nodes\\\": [{\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"implement\\\", \\\"position\\\": 1}, {\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"gate\\\", \\\"position\\\": 2}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"adversary\\\", \\\"position\\\": 3}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"review\\\", \\\"position\\\": 4}]}\\n\"},{\"slug\":\"cross-platform-check\",\"page\":\"---\\ntitle: Cross-platform check\\nsummary: The reviewer runs on a different vendor from the implementer, so one family's blind spots and one quota ceiling cannot stall the graph.\\nstatus: in use\\nimplements:\\n - graph:canopy-builder\\nfigure: cross-platform-check.figure.json\\nsources:\\n - title: Building effective agents (Anthropic), evaluator-optimizer workflow\\n url: https://www.anthropic.com/engineering/building-effective-agents\\n---\\n\\n# Cross-platform check\\n\\n## The problem\\n\\nA model reviewing its own family's output shares its blind spots. It learned the same habits, trips on the same cases, and reads the spec through the same lens as the author. A second opinion from the same family is barely a second opinion.\\n\\nIt also shares the author's quota ceiling. When the vendor subscription runs out, the writer and the judge go dark together, and the graph stalls with work waiting.\\n\\n## The pattern\\n\\nKeep the gated shape, but put the reviewer on a different vendor from the implementer. One family writes, another family judges.\\n\\nSplitting the two buys independent judgment and means one exhausted subscription does not stall the whole graph. The implementer keeps drafting on its vendor while the reviewer keeps checking on another, and a failure of judgment in one family is caught by eyes trained elsewhere.\\n\\nKeep the cross-check prompt narrow: the diff, the spec, and an instruction to find what is wrong. A cross-checker invited to re-implement will re-implement, and then there are two authors and no reviewer.\\n\\nUse it for security-sensitive changes, and anywhere the fix looks overfit to one model's reading of the spec.\\n\\n## What we saw\\n\\nThe lab runs its reviews across vendors as standard practice. When the implementer and the reviewer come from different families, each covers readings the other misses, and a single vendor outage or quota ceiling leaves half the loop still standing.\\n\\nThe lab has not measured how many defects this catches that a same-family review would miss. It is a design choice, not a benchmark.\\n\\n## In Canopy\\n\\nNo catalogue document is dedicated to this pattern, because it is a binding choice, not a new shape. Install `canopy-builder` and bind its role ensembles across vendors: the Implementer on one, the Designer on a second, and the reviewer and the auditor on a third. Every ensemble in that document arrives without a platform or a model, so the split is yours to make at install time.\\n\\nEverything else stays as the loop defines it. The gate still sits before the reviewer, failures still route back to the implementer, and the documenter's own check still proves the tree is clean. Only the vendors differ.\\n\\nThat loop already reviews twice behind one gate — the reviewer must locate every requirement, then the auditor repairs what it finds — so a vendor split there puts two families on the same diff without adding a node.\\n\\n## Try it\\n\\n```sh\\ncanopy graph install canopy-builder\\n```\\n\\nBind the implementer and the reviewer to different vendors at install time, and keep the review prompt to diff plus spec plus \\\"find what is wrong\\\".\\n\",\"figureName\":\"cross-platform-check.figure.json\",\"figure\":\"{\\\"edges\\\": [{\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"implement\\\", \\\"to_node\\\": \\\"gate\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"gate\\\", \\\"to_node\\\": \\\"crosscheck\\\"}, {\\\"condition\\\": \\\"fail\\\", \\\"from_node\\\": \\\"gate\\\", \\\"to_node\\\": \\\"implement\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"crosscheck\\\", \\\"to_node\\\": \\\"finish\\\"}, {\\\"condition\\\": \\\"fail\\\", \\\"from_node\\\": \\\"crosscheck\\\", \\\"to_node\\\": \\\"implement\\\"}], \\\"format_version\\\": 6, \\\"name\\\": \\\"cross-platform-check\\\", \\\"nodes\\\": [{\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"implement\\\", \\\"position\\\": 1}, {\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"gate\\\", \\\"position\\\": 2}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"crosscheck\\\", \\\"position\\\": 3}, {\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"finish\\\", \\\"position\\\": 4}]}\\n\"},{\"slug\":\"deterministic-commit\",\"page\":\"---\\ntitle: Deterministic commit\\nsummary: A check node commits with the spec's own Conventional Commit subject, and a model never writes the commit.\\nstatus: in use\\nimplements:\\n - graph:canopy-builder\\n - graph:adversarial-developer\\nfigure: deterministic-commit.figure.json\\nsources:\\n - title: Building effective agents (Anthropic), ground truth from the environment\\n url: https://www.anthropic.com/engineering/building-effective-agents\\n---\\n\\n# Deterministic commit\\n\\n## The problem\\n\\nA commit message written by a model drifts. It invents trailers, rewords the intent, or describes what the model thinks it did. The history then no longer matches the work that was asked for.\\n\\nDrift compounds. Each commit that paraphrases its spec makes the log a worse record of what was decided, and a later reader cannot tell which wording was the instruction and which was the model's summary of itself.\\n\\n## The pattern\\n\\nLet the spec name its own commit. The spec's name is a Conventional Commit subject, `(): `, and a deterministic check node runs the commit with that text verbatim. A model never writes it.\\n\\nA second check proves the commit landed. Both are facts about the repo, so neither needs a judge.\\n\\nVerbatim matters because the history is then an audit trail, not a retelling. The commit says what was asked, the diff says what was done, and any gap between them is visible instead of smoothed over by a helpful rewrite.\\n\\n## What we saw\\n\\nThe lab names every spec as the subject its commit will carry, and keeps its graphs' commits verbatim from the spec name. The lab has no recorded origin story for it beyond that convention.\\n\\nWhat the convention buys is boring history. No trailers to strip, no paraphrases to decode, no commit whose message describes work that is not in the diff.\\n\\n## In Canopy\\n\\nBoth loop graphs carry this pattern as a check node with commit rights.\\nIn `adversarial-developer` it is the `Commit` node, whose command stages\\neverything and commits with the spec's own message verbatim. From\\n`graphs/adversarial-developer/graph.json`:\\n\\n```json\\n\\\"config\\\": {\\n \\\"command\\\": \\\"if [ -z \\\\\\\"$(git status --porcelain)\\\\\\\" ]; then echo 'tree clean: nothing to commit'; exit 0; fi; if [ -z \\\\\\\"$CANOPY_SPEC_COMMIT_MESSAGE\\\\\\\" ]; then echo 'CANOPY_SPEC_COMMIT_MESSAGE is empty'; exit 1; fi; git add -A && git commit -q -m \\\\\\\"$CANOPY_SPEC_COMMIT_MESSAGE\\\\\\\" && echo \\\\\\\"committed $(git log -1 --oneline)\\\\\\\"\\\",\\n \\\"commit_rights\\\": true\\n}\\n```\\n\\nWhen the tree is already clean, the command passes without committing.\\nPushing to the work branch is optional: add the push to the command if\\nthe graph should publish the branch.\\n\\nIn `adversarial-developer` the documenter leaves its docs uncommitted, so\\n`Commit` lands the code and the docs together under the one subject.\\n`canopy-builder` commits twice the same way: `Commit spec` lands the\\nspec's own message after the completeness review, and `Commit auditor\\nfixes` lands `fix(): audit fixes for ` after the auditor's\\nrepairs, and both push the branch when they succeed.\\n\\nA second check can prove the commit landed — that HEAD moved since the\\nspec started — but neither loop needs one: the gates and the review\\nhave already read the same change before it is committed, and the\\nhistory carries the spec's own subject. Add a proof check only where a\\nconcurrent commit from outside the run could be mistaken for this one.\\n\\nThe model-committed version this pattern replaces is a model that\\nreviews the diff and then commits, with a concise message it writes\\nitself. Pick the deterministic commit when the message must match the\\nspec exactly, and the model-committer when the message needs judgment\\nthe spec cannot carry.\\n\\n## Try it\\n\\n```sh\\ncanopy graph install canopy-builder\\ncanopy graph install adversarial-developer\\n```\\n\",\"figureName\":\"deterministic-commit.figure.json\",\"figure\":\"{\\\"edges\\\": [{\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"commit\\\", \\\"to_node\\\": \\\"proof\\\"}], \\\"format_version\\\": 6, \\\"name\\\": \\\"deterministic-commit\\\", \\\"nodes\\\": [{\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"commit\\\", \\\"position\\\": 1}, {\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"proof\\\", \\\"position\\\": 2}]}\\n\"},{\"slug\":\"deterministic-gates\",\"page\":\"---\\ntitle: Deterministic gates\\nsummary: Shell checks decide pass or fail, never an LLM judge, and a failure carries its reason into the next prompt.\\nstatus: standard\\nimplements:\\n - graph:canopy-builder\\n - graph:adversarial-developer\\n - node:full-gates-cargo\\n - node:full-gates-web\\nfigure: deterministic-gates.figure.json\\nsources:\\n - title: Building effective agents (Anthropic), ground truth from the environment\\n url: https://www.anthropic.com/engineering/building-effective-agents\\n - title: How we built our multi-agent research system (Anthropic), deterministic safeguards\\n url: https://www.anthropic.com/engineering/multi-agent-research-system\\n---\\n\\n# Deterministic gates\\n\\n## The problem\\n\\nAn LLM asked \\\"does this pass?\\\" can be wrong, flattered, or tired. On a cheap model it is also another paid call. Whether the formatter is clean and the tests are green is a fact about the repo, not an opinion.\\n\\n## The pattern\\n\\nLet a shell command decide. The gate runs the formatter, the linter and the tests, and its exit code is the verdict. No model sits in the judging seat.\\n\\nA gate that only says \\\"fail\\\" teaches the next attempt nothing. So the failure's output goes into the next prompt, and the implementer sees why it was sent back.\\n\\nAnthropic's agent guidance makes the same point about grounding: agents should \\\"gain 'ground truth' from the environment at each step.\\\" Its multi-agent write-up also pairs model judgment with deterministic safeguards such as retries and checkpoints.\\n\\nThree rules make this shape work, and each was learned by watching it not work.\\n\\n**The implement edge is `pass`, never `always`.** An `always` edge routes a failed implement into the reviewer, which then approves work that was never written. A real run marked a spec completed with no commit behind it that way.\\n\\n**The check sits before the reviewer.** Code that does not build never reaches review. Without that order, the reviewer writes up the breakage and the spec is marked completed anyway, because routing keys on the run's status, not on its prose.\\n\\n**Auto-fixing commands must not bounce work.** Run the formatter in rewrite mode, which fixes, not in check mode, which fails. Only genuine breakage should route back to implement.\\n\\nUse it for essentially everything. Start here and add shape only when a specific failure justifies it.\\n\\n## What we saw\\n\\nThe lab runs cheap, unreliable models with tight quota. Its standard loop puts the gates before any reviewer, so a reviewer's quota is never spent on code that does not compile.\\n\\nThe lab has not measured how much this saves. It is a design choice, not a benchmark.\\n\\n## Handing plans down\\n\\nA plan from one node to the next travels through spec notes, not files. A designer node writes its plan with `spec_note_write` instead of a temp file no later node can find, and a verify step checks the notes file for the plan's heading. Every agent prompt already carries the notes, so nothing needs deleting and nothing goes stale.\\n\\n## In Canopy\\n\\nA `check` node holds the command. Edges route `pass` forward and `fail` back to the implementer. In `adversarial-developer`, the gate that follows the implementer and its two edges look like this:\\n\\n```json\\n{\\n \\\"from_node\\\": \\\"Full gates\\\",\\n \\\"to_node\\\": \\\"Reviewer\\\",\\n \\\"condition\\\": \\\"pass\\\"\\n},\\n{\\n \\\"from_node\\\": \\\"Full gates\\\",\\n \\\"to_node\\\": \\\"Implementer\\\",\\n \\\"condition\\\": \\\"fail\\\"\\n}\\n```\\n\\nThe implementer's prompt reads `{{previous_feedback}}` and must address every point in it, which is how a failure's reason reaches the next attempt.\\n\\nThe `full-gates-cargo` node is the Rust version of the gate, from `nodes/full-gates-cargo/node.json`:\\n\\n```json\\n{\\n \\\"format_version\\\": 1,\\n \\\"name\\\": \\\"full-gates-cargo\\\",\\n \\\"kind\\\": \\\"check\\\",\\n \\\"description\\\": \\\"The Rust full gate: one check command runs `cargo fmt --all -- --check`, `cargo clippy --all-targets --all-features -- -D warnings` and `cargo test --all-features`. The project should add its dependency audit and coverage floor to the command.\\\",\\n \\\"config\\\": {\\n \\\"command\\\": \\\"cargo fmt --all -- --check && cargo clippy --all-targets --all-features -- -D warnings && cargo test --all-features\\\",\\n \\\"mutex\\\": [\\\"cpu\\\"],\\n \\\"timeout_seconds\\\": 6000\\n }\\n}\\n```\\n\\nA TypeScript or web project installs `full-gates-web` instead: the same shape, with a command that runs the install, the lint, the typecheck, the unit tests, the build and the end-to-end tests.\\n\\nIn the catalogue the gate command is the `` placeholder. Replace it with the command that runs your own formatter, linter, dependency audit, coverage floor and test suite. Until you do, the placeholder fails, so a graph cannot pass its gates by doing nothing.\\n\\n## Keep hardening the gate\\n\\nAn earlier catalogue carried a second graph whose whole job was to cover a finished change with tests. The lab dropped that shape. A separate test-review graph inspects one change at a time, while the gate every change must pass stays exactly as weak as it was, and work that nobody pointed the second graph at goes on shipping untested.\\n\\nHarden the one gate instead, so every spec meets the same bar. Put the test budget inside it: a cap on added-plus-removed lines, so a \\\"fix\\\" that rewrites the suite is refused. Keep the strict lint thresholds there, the dependency audit, and the coverage floor. Then a suite that ballooned, a suppressed lint and a stale dependency all fail the same command as a broken build, on every spec, without anyone deciding to run a review that day.\\n\\n## Try it\\n\\n```sh\\ncanopy graph install canopy-builder\\ncanopy node install full-gates-cargo\\ncanopy node install full-gates-web\\n```\\n\",\"figureName\":\"deterministic-gates.figure.json\",\"figure\":\"{\\\"edges\\\": [{\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"implement\\\", \\\"to_node\\\": \\\"gate\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"gate\\\", \\\"to_node\\\": \\\"review\\\"}, {\\\"condition\\\": \\\"fail\\\", \\\"from_node\\\": \\\"gate\\\", \\\"to_node\\\": \\\"implement\\\"}, {\\\"condition\\\": \\\"fail\\\", \\\"from_node\\\": \\\"review\\\", \\\"to_node\\\": \\\"implement\\\"}], \\\"format_version\\\": 6, \\\"name\\\": \\\"deterministic-gates\\\", \\\"nodes\\\": [{\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"implement\\\", \\\"position\\\": 1}, {\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"gate\\\", \\\"position\\\": 2}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"review\\\", \\\"position\\\": 3}]}\\n\"},{\"slug\":\"event-intake\",\"page\":\"---\\ntitle: Event intake\\nsummary: A graph that fires on a watched file, turning outside events into specs while nobody is watching.\\nstatus: in use\\nimplements: []\\nfigure: event-intake.figure.json\\nsources:\\n - title: GitHub webhooks documentation\\n url: https://docs.github.com/en/webhooks\\n---\\n\\n# Event intake\\n\\n## The problem\\n\\nWork arrives while nobody is watching. Issues are filed at night, feeds update on their own schedule, and a graph that only starts by hand waits for a human to notice. The issue sits until morning, and morning has its own work.\\n\\n## The pattern\\n\\nA graph does not have to be started by hand. It can fire on a cron slot or a filesystem watch, which makes it the back half of an event pipeline whose front half is anything that can write a file.\\n\\nThe front half does not need a public endpoint. A real webhook needs a tunnel, a forwarding service, something listening. For work that can wait five minutes, one cron line polling for new items and writing a file is strictly simpler and stays local. Use a real webhook only when the latency actually matters.\\n\\nThe intake node is a normal agent node whose job is to turn the file into a spec and queue it. Once it does, the rest of the graph is the gated loop and knows nothing about where the work came from.\\n\\n**The gap**: a watch-triggered graph is told that it fired, never what fired it. The watcher logs the changed path and then calls the engine with only the graph id. If the script writes to one well-known path, the intake node just reads that path and the gap costs nothing, which is why this is a solved pattern today. It bites the moment a directory is watched and the intake needs to know which file arrived.\\n\\n## What we saw\\n\\nThe lab runs intake against a single well-known path: a poll script writes one file, the graph fires on it, the intake node reads it. Within that shape the missing payload has never cost a run. Directory watch stays out of reach until the engine passes the path along.\\n\\n## In Canopy\\n\\nThe figure shows the full pipeline: the outside event, the script that writes the inbox file, the watch, the intake node, and then the gated loop of implement, gate, and review.\\n\\nNo catalogue document implements this pattern yet. The back half is a trigger watching that file plus the intake prompt below; the front half is one script in front of them.\\n\\n## Try it\\n\\nAn issue labeled for automation gets a branch and a proposed fix by morning. The front half is one cron line running a script like this every ten minutes:\\n\\n```bash\\n#!/usr/bin/env bash\\n# ~/bin/canopy-issue-inbox.sh — run from cron every 10 minutes\\nset -euo pipefail\\nINBOX=\\\"$HOME/.canopy/inbox/github-issue.json\\\"\\nmkdir -p \\\"$(dirname \\\"$INBOX\\\")\\\"\\n\\ngh issue list --repo you/yourrepo --label auto --state open \\\\\\n --limit 1 --json number,title,body > \\\"$INBOX.tmp\\\"\\n\\n# Only publish when there is actually an issue, and write atomically so the\\n# watcher never reads a half-written file.\\nif [ \\\"$(jq 'length' < \\\"$INBOX.tmp\\\")\\\" -gt 0 ]; then\\n mv \\\"$INBOX.tmp\\\" \\\"$INBOX\\\"\\nelse\\n rm -f \\\"$INBOX.tmp\\\"\\nfi\\n```\\n\\nThe atomic move matters. A watcher fires on the first write event, and a graph that starts reading a file another process is still writing will read half an issue.\\n\\nThe back half is a graph watching that file. The intake node turns the file into work and stops:\\n\\n```text\\n# [ROLE]\\nTurn one GitHub issue into one spec. You do not fix anything.\\n\\n# [HOW]\\n1. Read the inbox file.\\n2. Reproduce the problem well enough to state it precisely. An issue is a\\n report, not a specification.\\n3. Write the spec: objective, functional requirements, in scope, out of\\n scope, acceptance. Where the issue is ambiguous, choose the narrowest\\n reading and say so in OUT OF SCOPE.\\n4. Queue it for the graph.\\n5. Report pass with the spec id.\\n```\\n\\nFrom there it is the gated loop, plus an adversary before the commit:\\n\\n```text\\n# [ROLE]\\nYour job is to BREAK the change in the diff below. You succeed by finding a\\nconcrete way it fails.\\n\\n# [HOW]\\n- Produce a reproduction: an input, a sequence, a race, a boundary. Not an\\n opinion, not a \\\"this could be risky\\\".\\n- Report FAIL if you broke it — include the reproduction.\\n- Report PASS if you could not. That is a real result and an expected one.\\n- Do not fix anything. Breaking and repairing are different jobs.\\n```\\n\\nThe adversary's fail means it succeeded. Wire its fail edge back to implement and its pass edge on to the commit.\\n\\nThree things to keep an eye on. The graph never learns which file woke it, so the intake reads a fixed path and watching a directory does not work today. The script must be idempotent, or the same issue rewritten produces the same spec twice. And nothing here pushes: the graph commits on a branch, and opening the pull request stays a human decision, which is the correct default for work done unattended.\\n\",\"figureName\":\"event-intake.figure.json\",\"figure\":\"{\\\"edges\\\": [{\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"github-issue\\\", \\\"to_node\\\": \\\"inbox-script\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"inbox-script\\\", \\\"to_node\\\": \\\"watch\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"watch\\\", \\\"to_node\\\": \\\"intake\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"intake\\\", \\\"to_node\\\": \\\"implement\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"implement\\\", \\\"to_node\\\": \\\"gate\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"gate\\\", \\\"to_node\\\": \\\"review\\\"}], \\\"format_version\\\": 6, \\\"name\\\": \\\"event-intake\\\", \\\"nodes\\\": [{\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"github-issue\\\", \\\"position\\\": 1}, {\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"inbox-script\\\", \\\"position\\\": 2}, {\\\"config\\\": {}, \\\"kind\\\": \\\"gate\\\", \\\"name\\\": \\\"watch\\\", \\\"position\\\": 3}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"intake\\\", \\\"position\\\": 4}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"implement\\\", \\\"position\\\": 5}, {\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"gate\\\", \\\"position\\\": 6}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"review\\\", \\\"position\\\": 7}]}\\n\"},{\"slug\":\"parallel-proposers\",\"page\":\"---\\ntitle: Parallel proposers\\nsummary: Several cheap models draft the same spec in parallel, then one implementer builds on the best ideas.\\nstatus: in use\\nimplements: []\\nfigure: parallel-proposers.figure.json\\nsources:\\n - title: Mixture-of-Agents Enhances Large Language Model Capabilities (Wang et al.)\\n url: https://arxiv.org/abs/2406.04692\\n - title: Fusion Router (OpenRouter documentation)\\n url: https://openrouter.ai/docs/guides/routing/routers/fusion-router\\n - title: Building effective agents (Anthropic), parallelization\\n url: https://www.anthropic.com/engineering/building-effective-agents\\n---\\n\\n# Parallel proposers\\n\\n## The problem\\n\\nOne cheap model, one attempt, one set of blind spots. Spending a strong model on every spec is not affordable under tight quota, and a blank spec is the hardest thing to hand to a weak implementer.\\n\\n## The pattern\\n\\nAsk several models for a draft of the same spec at the same time. Then give one implementer the drafts and let it build on the best ideas. The implementer never faces the blank spec.\\n\\nThe canonical use spends the expensive model once, on a pre-digested problem. Three cheap drafts go in, the quorum consolidates them into one document, and the paid implementer writes the code. That is one expensive run instead of one expensive exploration plus one expensive implementation.\\n\\nThe idea has published precedent. The Mixture-of-Agents paper by Wang et al. arranges several LLMs in layers, where each agent uses the previous layer's outputs as additional context. Anthropic lists parallelization as a workflow: \\\"running the same task multiple times to get diverse outputs.\\\"\\n\\nUse it for problems where the approach is the hard part and the typing is not.\\n\\n## The mirror image\\n\\nThe same unit works in reverse: two or three cheap reviewers read the same diff, and the quorum consolidates their findings into one verdict. Drafting and reviewing are both pre-digestion; only the direction differs.\\n\\n## What we saw\\n\\nThe lab took the idea from OpenRouter's fusion mode. OpenRouter documents it as a panel of models answering the same prompt in parallel, with an analyst model comparing the answers. It states that the analyst \\\"doesn't merge them.\\\" The lab's version is simpler: the drafts go straight to an implementer.\\n\\nWith a similar technique, the operator got reasoning from cheaper models comparable to Fable 5. This is an observation from the lab's own use. It is not a benchmark, and the lab has no controlled comparison.\\n\\nParallel drafts cost more calls. OpenRouter estimates a three-model panel at roughly four to five times a single completion, and the lab does not claim the trade is free.\\n\\n## In Canopy\\n\\nAn ensemble is one unit, not several hand-wired nodes. A parallel ensemble takes two to eight members, and the sequential kinds take more, because they dispatch one member at a time. Both caps can be raised in settings.\\n\\nCreate it with a single call that makes the members, the quorum, and the entry and exit edges together. Wiring several parallel `pass` edges out of one node by hand is the ambiguous-outgoing-edges mistake, and it aborts the run.\\n\\nA parallel ensemble node holds the proposers. All members share one prompt template, and each member is an empty object so that nobody's platform or model is baked in. The catalogue ships no proposers node: build the ensemble in the graph you own, give it the draft prompt, set `min_pass` to the member count so every draft must pass, and bind a different model to each member at install time. The figure on this page carries the same shape, with the wiring left out:\\n\\n```json\\n{\\n \\\"name\\\": \\\"drafters\\\",\\n \\\"kind\\\": \\\"parallel\\\",\\n \\\"min_pass\\\": 3,\\n \\\"members\\\": [\\n {},\\n {},\\n {}\\n ],\\n \\\"timeout_minutes\\\": 30\\n}\\n```\\n\\nThree drafters run at once and their outputs go straight to one implementer, which is the point: the drafts are pre-digestion, and the paid model only writes.\\n\",\"figureName\":\"parallel-proposers.figure.json\",\"figure\":\"{\\\"edges\\\": [], \\\"ensembles\\\": [{\\\"commit_rights\\\": false, \\\"entry_condition\\\": \\\"pass\\\", \\\"entry_from_node\\\": \\\"start\\\", \\\"kind\\\": \\\"parallel\\\", \\\"members\\\": [{}, {}, {}], \\\"min_pass\\\": 3, \\\"name\\\": \\\"drafters\\\", \\\"on_pass_to\\\": \\\"implement\\\", \\\"prompt_template\\\": \\\"\\\", \\\"timeout_minutes\\\": 30}], \\\"format_version\\\": 6, \\\"name\\\": \\\"parallel-proposers\\\", \\\"nodes\\\": [{\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"start\\\", \\\"position\\\": 1}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"implement\\\", \\\"position\\\": 2}]}\\n\"},{\"slug\":\"resilient-members\",\"page\":\"---\\ntitle: Resilient members\\nsummary: Cascade and round-robin ensembles give one node a fallback ladder or a rotation across providers, so a quota death is not a failed run.\\nstatus: in use\\nimplements:\\n - graph:canopy-builder\\nfigure: resilient-members.figure.json\\nsources:\\n - title: How we built our multi-agent research system (Anthropic), resilience\\n url: https://www.anthropic.com/engineering/multi-agent-research-system\\n---\\n\\n# Resilient members\\n\\n## The problem\\n\\nCheap models run out of quota, and harnesses fail mid-run. A node bound to a single provider turns either event into a failed run, even though the work itself was fine.\\n\\nWorse, the plain gated shape fails the spec the moment implement dies, so nobody reads the failure. If the CLI died on a quota that resets at a known hour, that knowledge is thrown away.\\n\\n## The pattern\\n\\nGive a node several members instead of one.\\n\\n- **Cascade:** the members form a ladder. If the first fails on quota or harness, the run falls through to the next.\\n- **Round robin:** the members take turns, so spend rotates across providers and no single quota is drained first.\\n\\nAnthropic describes the same concern in its multi-agent write-up: restarts are costly, so the team \\\"built systems that can resume from where the agent was when the errors occurred.\\\" Letting a node fall to another member is a cheaper cousin of that idea.\\n\\n## Resilience branch\\n\\nGive implement two exits. On pass it goes to the gate as usual; on fail it goes to a resilience node, so the failure is read instead of thrown away.\\n\\nThe resilience node is a cheap model on a different quota from the implementer, or it is dead exactly when needed. It either repairs a transient failure and reports pass, so implement retries, or recognizes exhaustion, schedules the graph's own wake-up at the reset time, and reports fail. The spec fails, but the graph wakes itself at that instant.\\n\\nGive it the clock. Models do not know what time it is: a real run at night scheduled its wake-up for 6am the next day, a 23-hour stall. The prompt must require reading the current time first.\\n\\n## Free-tier cascade\\n\\nSeveral CLIs with generous free tiers and small ceilings: spend them in sequence before touching anything paid. One ensemble node of kind cascade does it in a single call, members ordered cheapest to dearest.\\n\\nMembers run one at a time. A failure falls through to the next cold; the first pass exits. A bounce resumes the member that last passed. A member with no infra retries falls through at once on a crash. The lab's committer is an ensemble too: several members, one commit.\\n\\n### Routing on why a member failed\\n\\nThe cascade falls through on any failure: exhaustion, a network error, and wrong code all look the same. That is correct when members are equally capable and the goal is only to spend ceilings in order. It is wrong when wrong code should go to a fixer, not a weaker harness.\\n\\nFor that case a router variant reads the failure and routes on the reason. A router is only justified when the reason changes where the work goes; for spending tiers in sequence, the plain cascade is enough.\\n\\n### The naive version and why it is a trap\\n\\nA chain of fail edges from one implementer to the next looks like a cascade but is a trap. Fail conflates \\\"my quota is gone\\\" with \\\"this code is wrong\\\". A spec with a genuine bug burns every free tier on the same wrong task, ending the day with every ceiling spent and nothing done.\\n\\nA chain only makes sense routing on why the node failed: one router for the whole cascade, each fail edge pointing at it. It sends exhaustion down the ladder, transients back for retry, and broken code out as a failed spec. Untested; build it only when the cascade must route on reason.\\n\\n### Two things worth knowing before you build\\n\\n**Exhaustion looks different on every harness.** Some CLIs exit non-zero naming the reset time; others stop responding and die on the node's timeout. A member that times out falls through to the next, which is usually wanted. A router generalizes across both.\\n\\n**The engine has no memory of a spent harness.** If one spec burns the first tiers, the next spec starts at the top and rediscovers they are dead, for every remaining spec. Today the only lever parks the whole graph until a reset. A per-harness \\\"unavailable until\\\" does not exist yet.\\n\\n## What we saw\\n\\nCascade and round robin reuse the ensemble idea from the parallel proposers: an ensemble already holds several members for one node, so the same structure holds fallbacks instead of parallel drafts. The lab has no measurements on how many runs this saved.\\n\\n## In Canopy\\n\\n`canopy-builder` ships this pattern: each of its five working roles is a round-robin ensemble — Designer, Implementer, Reviewer, Auditor and Documenter — and every member is an empty object, so no harness is baked in. From `graphs/canopy-builder/graph.json`:\\n\\n```json\\n{\\n \\\"name\\\": \\\"Implementer\\\",\\n \\\"kind\\\": \\\"round_robin\\\",\\n \\\"commit_rights\\\": false,\\n \\\"entry_from_node\\\": \\\"Plan check\\\",\\n \\\"entry_condition\\\": \\\"pass\\\",\\n \\\"extra_entry_sources\\\": [\\n {\\n \\\"from_node\\\": \\\"Full gates\\\",\\n \\\"condition\\\": \\\"fail\\\"\\n }\\n ],\\n \\\"on_pass_to\\\": \\\"Full gates\\\",\\n \\\"on_fail_to\\\": \\\"Plan check\\\",\\n \\\"min_pass\\\": 4,\\n \\\"members\\\": [\\n {},\\n {},\\n {},\\n {}\\n ],\\n \\\"timeout_minutes\\\": 120\\n}\\n```\\n\\nThe members take turns: spend rotates across providers, and a quota death rotates to the next member instead of failing the spec. Bind a different harness to each member at install time. The schema also allows `cascade`, where the members form a ladder and the first pass exits.\\n\\nFor the failure side of the same problem, `canopy-builder` carries a `Resilience` node: it reacts after a node fails, where members prevent the failure from ending the run.\\n\\n## Try it\\n\\n```sh\\ncanopy graph install canopy-builder\\n```\\n\\nTo spend free tiers first: one cascade ensemble, cheapest to dearest, one pass enough to exit, short per-member timeouts, no infra retries. The honest limit is the missing memory above: each new spec rediscovers the spent tiers.\\n\",\"figureName\":\"resilient-members.figure.json\",\"figure\":\"{\\\"edges\\\": [{\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"gate\\\", \\\"to_node\\\": \\\"review\\\"}, {\\\"condition\\\": \\\"fail\\\", \\\"from_node\\\": \\\"gate\\\", \\\"to_node\\\": \\\"start\\\"}, {\\\"condition\\\": \\\"fail\\\", \\\"from_node\\\": \\\"review\\\", \\\"to_node\\\": \\\"start\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"resilience\\\", \\\"to_node\\\": \\\"start\\\"}], \\\"ensembles\\\": [{\\\"commit_rights\\\": false, \\\"entry_condition\\\": \\\"pass\\\", \\\"entry_from_node\\\": \\\"start\\\", \\\"kind\\\": \\\"cascade\\\", \\\"members\\\": [{}, {}], \\\"min_pass\\\": 1, \\\"name\\\": \\\"implementers\\\", \\\"on_fail_to\\\": \\\"resilience\\\", \\\"on_pass_to\\\": \\\"gate\\\", \\\"prompt_template\\\": \\\"\\\", \\\"timeout_minutes\\\": 30}], \\\"format_version\\\": 6, \\\"name\\\": \\\"resilient-members\\\", \\\"nodes\\\": [{\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"start\\\", \\\"position\\\": 1}, {\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"gate\\\", \\\"position\\\": 2}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"review\\\", \\\"position\\\": 3}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"resilience\\\", \\\"position\\\": 4}]}\\n\"},{\"slug\":\"review-panel\",\"page\":\"---\\ntitle: Review panel\\nsummary: An experiment, each reviewer owns one axis, security, performance, maintainability, or project patterns, and every axis must pass.\\nstatus: experiment\\nimplements: []\\nfigure: review-panel.figure.json\\nsources:\\n - title: Mixture-of-Agents Enhances Large Language Model Capabilities (Wang et al.)\\n url: https://arxiv.org/abs/2406.04692\\n---\\n\\n# Review panel\\n\\nThis pattern is an experiment. The lab has designed it but not run it.\\n\\n## The problem\\n\\nOne reviewer reads for everything and covers nothing well. Security, performance, maintainability, and the project's own patterns each deserve a full read, and a single pass over a large diff gives each a skim. The result is a review that approves correct-looking code with a whole dimension unexamined.\\n\\n## The pattern\\n\\nRun an ensemble where each member reviews a different axis: one reads for security, one for performance, one for maintainability, one for the project's own patterns. Per-member prompt overrides make this one call instead of four sequential nodes, and all four axes run in parallel.\\n\\n**`min_pass` means the wrong thing here.** With homogeneous members, two of three reads as \\\"a majority agrees\\\" and that is sensible. With specialists, each member owns a different axis, so three of four reads as \\\"one whole dimension of quality may fail and we ship anyway.\\\" Every axis must pass.\\n\\nBut all-must-pass with four cheap models means the grumpiest one blocks every change forever. The honest requirement is not a vote count at all. It is severity: block on a real finding, record a nitpick. The quorum counts votes and cannot express that.\\n\\nThe workable version today sets the threshold to all members and pushes the severity discipline into each member's prompt: fail only on a defect in your axis that would matter in production; everything else goes in the report, not the verdict. That raises the floor without pretending the engine understands severity.\\n\\n## What we saw\\n\\nNothing yet. The panel is designed, with the severity problem above as its known open question. Until the engine can tell a blocking finding from a note, the prompt discipline carries that weight alone.\\n\\n## In Canopy\\n\\nThe figure shows the panel as one parallel ensemble between the gate and the commit: implement, then the gate, then the four specialists, then the commit. A fail from the quorum routes back to implement.\\n\\nNo catalogue document implements this pattern yet.\\n\\n## Try it\\n\\nToday the same coverage can be built from four plain agent nodes chained in series. Slower than parallel, identical in coverage:\\n\\n```text\\ngates --pass--> review-security --pass--> review-performance\\nreview-performance --pass--> review-maintainability --pass--> review-patterns\\nreview-patterns --pass--> commit\\nany review --fail--> implement\\n```\\n\\nAny specialist failing bounces the work, which is the semantics wanted and which a vote count cannot express.\\n\\nOne template serves all four, with one axis substituted. The last rules are what keep a cheap model from turning into a style bot:\\n\\n```text\\n# [ROLE]\\nYou review one diff on ONE axis: SECURITY. Other reviewers own the other\\naxes. Do not review theirs.\\n\\n# [WHAT]\\nInjection, authz, secrets in code or logs, unsafe deserialization, TOCTOU,\\nanything that widens what an attacker can reach.\\n\\n# [HOW]\\n- Cite file:line for every finding.\\n- FAIL only for a defect on your axis that would matter in production.\\n- Everything else — style, taste, a nicer way to write it — goes in your\\n report, never in your verdict.\\n- Finding nothing is a legitimate and expected outcome. Do not manufacture\\n a finding to look useful.\\n\\n# [SPEC]\\n{{spec_content}}\\n```\\n\\nThat last rule is not politeness. A model that believes an empty report reflects badly on it will produce findings, and a panel of four such models will bounce every change forever.\\n\\nThe same panel collapses into one parallel ensemble with per-member prompt overrides, one per axis, and the threshold set to the member count. Wall-clock drops to the slowest reviewer instead of the sum. If you publish the result, the repo README explains how.\\n\",\"figureName\":\"review-panel.figure.json\",\"figure\":\"{\\\"edges\\\": [{\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"implement\\\", \\\"to_node\\\": \\\"gate\\\"}], \\\"ensembles\\\": [{\\\"commit_rights\\\": false, \\\"entry_condition\\\": \\\"pass\\\", \\\"entry_from_node\\\": \\\"gate\\\", \\\"kind\\\": \\\"parallel\\\", \\\"members\\\": [{}, {}, {}, {}], \\\"min_pass\\\": 4, \\\"name\\\": \\\"review-panel\\\", \\\"on_fail_to\\\": \\\"implement\\\", \\\"on_pass_to\\\": \\\"commit\\\", \\\"prompt_template\\\": \\\"\\\", \\\"timeout_minutes\\\": 30}], \\\"format_version\\\": 6, \\\"name\\\": \\\"review-panel\\\", \\\"nodes\\\": [{\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"implement\\\", \\\"position\\\": 1}, {\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"gate\\\", \\\"position\\\": 2}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"commit\\\", \\\"position\\\": 3}]}\\n\"},{\"slug\":\"reviewer-fixes\",\"page\":\"---\\ntitle: Auditor fixes\\nsummary: The auditor repairs what it finds, instead of writing change requests for another model to read.\\nstatus: in use\\nimplements:\\n - node:auditor\\nfigure: reviewer-fixes.figure.json\\nsources:\\n - title: Building effective agents (Anthropic), evaluator-optimizer workflow\\n url: https://www.anthropic.com/engineering/building-effective-agents\\n---\\n\\n# Auditor fixes\\n\\n## The problem\\n\\nA rejection is a message from one model to another. The implementer must read it, understand it and redo the work, and on a cheap model each round trip can lose detail or introduce a new fault. When the defect is small, the round trip costs more than the fix.\\n\\n## The pattern\\n\\nLet the auditor repair what it finds. Instead of a list of change requests, it edits the diff, and a later step commits. The loop back to the implementer is kept for the cases the auditor cannot repair.\\n\\nThis differs from the plain evaluator-optimizer loop Anthropic describes, in which \\\"one LLM call generates a response while another provides evaluation and feedback in a loop.\\\" Here the evaluator can also write.\\n\\nRepair wins when the defect is small and clear: a missed edge case, a wrong boundary, a test that asserts too little. The auditor already holds the full context, so the fix is one edit, not a handoff.\\n\\nBounce the work back when the defect is large or the implementer misread the spec. An auditor that rewrites half the diff is no longer auditing; it is implementing without the implementer's instructions, and its output deserves its own review.\\n\\n## What we saw\\n\\nThe lab has no recorded origin story for this pattern, and no measurement of how often repairs beat a rejection. Treat the claim above as a design intent, not a result.\\n\\nThe practical rule is trust calibration. Give the repair licence to an auditor model strong enough to edit carefully, and keep the bounce loop behind it for everything else.\\n\\n## In Canopy\\n\\nThe catalogue's `auditor` node reviews strictly against the spec with a licence to edit. Its prompt repairs what it objects to directly in the working tree, rewrites tests that would not fail without the feature, and runs the gate script once at the end. It never commits: a later node commits its fixes.\\n\\nThe gate script path is the one thing the prompt does not carry. Fill\\nin `` at install time with the project's deterministic\\ngates. From the prompt in `nodes/auditor/node.json`:\\n\\n```\\nIt is exactly what the gate node after you runs (formatting, the\\nlinter at -D warnings, the dependency audit, the coverage floor, the\\nfull suite); it may wait for the CPU lock first. Fix until it prints\\nGATES PASSED. If you changed nothing, do not run it.\\n```\\n\\nThe single run at the end is deliberate. The auditor proves its own\\nrepairs without turning into a second implementer that iterates.\\n\\nThis differs from the catalogue's `reviewer` node, which fails a\\nbad diff back to the implementer with a list of what is wrong instead\\nof fixing it. `canopy-builder` splits the two jobs: its reviewer\\nonly reports what is missing, its auditor repairs what it\\nfinds, and a check node commits those repairs. Pick the arrangement\\nthat matches how much you trust the model that repairs.\\n\\n## Try it\\n\\n```sh\\ncanopy node install auditor\\n```\\n\",\"figureName\":\"reviewer-fixes.figure.json\",\"figure\":\"{\\\"edges\\\": [{\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"implement\\\", \\\"to_node\\\": \\\"review\\\"}, {\\\"condition\\\": \\\"fail\\\", \\\"from_node\\\": \\\"review\\\", \\\"to_node\\\": \\\"implement\\\"}], \\\"format_version\\\": 6, \\\"name\\\": \\\"reviewer-fixes\\\", \\\"nodes\\\": [{\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"implement\\\", \\\"position\\\": 1}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"review\\\", \\\"position\\\": 2}]}\\n\"},{\"slug\":\"task-router\",\"page\":\"---\\ntitle: Task router\\nsummary: An experiment, a router judges the task by kind or complexity and sends it to a model suited to it.\\nstatus: experiment\\nimplements: []\\nfigure: task-router.figure.json\\nsources:\\n - title: Building effective agents (Anthropic), routing\\n url: https://www.anthropic.com/engineering/building-effective-agents\\n---\\n\\n# Task router\\n\\nThis pattern is an experiment. The lab has not tested it properly.\\n\\n## The problem\\n\\nOne model for every task wastes money on easy work and risks quality on hard work. Some tasks also need a quality that cheap models lack, such as visual taste.\\n\\n## The pattern\\n\\nInsert a router in front of the work. It reads the task, judges its kind or its complexity, and sends it down the branch that fits. Two implementers sit behind it on different harnesses and price points, and a cheap classifier decides which one earns each spec.\\n\\nAnthropic names this workflow: \\\"Routing classifies an input and directs it to a specialized followup task.\\\" It is a standard pattern.\\n\\nTwo things about the diagram are load-bearing and easy to get wrong.\\n\\n**A failed cheap implement escalates to the expensive one. It does not go back to the router.** The router would classify the same spec the same way and send it back to the model that just failed, forever. Routing decisions based on the spec are stable by definition; only new information should produce a new decision.\\n\\n**The check's fail edge also goes to the expensive implementer**, for the same reason: by the time a build is red, the cheap classification has been falsified by evidence.\\n\\n**The router must be cheap.** A router as expensive as the expensive implementer means the high price is paid on every task, including the ones routed away from it.\\n\\n## What we saw\\n\\nNothing yet, and that is the point of the label. The router is an untested hypothesis in Canopy, not an observed result.\\n\\nThe idea is an example, not a finding. Graphic design and assets would go to a model with strong taste, and everything else to cheap models. Opus 5.5 designed the univerlab.org site and its assets with the operator, which is why the hypothesis looks plausible to the lab. One project is not evidence that a router would choose well, or that it would cost less than a single model.\\n\\nRisks worth testing: the router can misjudge, and every routing decision is itself a model call.\\n\\n## In Canopy\\n\\nThe graph schema has a `router` node kind, and edges can carry a route label as their condition. A router graph sends each label to a different branch: the simple route to the cheap implementer, the complex route to the expensive one.\\n\\nThe figure shows the full shape with its two load-bearing edges. The cheap implementer's `fail` edge points at the expensive implementer, and the gate's `fail` edge does the same. Neither points back at the router.\\n\\nNo document in this catalogue uses a router yet, and this repository has no real file to copy an excerpt from. This page therefore implements no catalogue document.\\n\\n## Try it\\n\\nNo catalogue document implements this pattern yet. If you build one, the repo README explains how to publish it. Keep the router cheap, keep both fail edges on the expensive branch, and test whether the router's judgments beat a single model before trusting it with a backlog.\\n\",\"figureName\":\"task-router.figure.json\",\"figure\":\"{\\\"edges\\\": [{\\\"condition\\\": {\\\"route\\\": \\\"simple\\\"}, \\\"from_node\\\": \\\"router\\\", \\\"to_node\\\": \\\"implement-cheap\\\"}, {\\\"condition\\\": {\\\"route\\\": \\\"complex\\\"}, \\\"from_node\\\": \\\"router\\\", \\\"to_node\\\": \\\"implement-expensive\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"implement-cheap\\\", \\\"to_node\\\": \\\"gate\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"implement-expensive\\\", \\\"to_node\\\": \\\"gate\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"gate\\\", \\\"to_node\\\": \\\"review\\\"}, {\\\"condition\\\": \\\"fail\\\", \\\"from_node\\\": \\\"gate\\\", \\\"to_node\\\": \\\"implement-expensive\\\"}, {\\\"condition\\\": \\\"fail\\\", \\\"from_node\\\": \\\"implement-cheap\\\", \\\"to_node\\\": \\\"implement-expensive\\\"}], \\\"format_version\\\": 6, \\\"name\\\": \\\"task-router\\\", \\\"nodes\\\": [{\\\"config\\\": {}, \\\"kind\\\": \\\"router\\\", \\\"name\\\": \\\"router\\\", \\\"position\\\": 1}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"implement-cheap\\\", \\\"position\\\": 2}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"implement-expensive\\\", \\\"position\\\": 3}, {\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"gate\\\", \\\"position\\\": 4}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"review\\\", \\\"position\\\": 5}]}\\n\"}]") as BundledPattern[]; +export const BUNDLED_PATTERNS: BundledPattern[] = JSON.parse("[{\"slug\":\"adversarial-review\",\"page\":\"---\\ntitle: Adversarial review\\nsummary: Review the work with models of different families, one told to break it, and back the verdict with deterministic checks.\\nstatus: standard\\nimplements:\\n - graph:adversarial-developer\\n - node:reviewer\\nfigure: adversarial-review.figure.json\\nsources:\\n - title: Building effective agents (Anthropic), evaluator-optimizer workflow\\n url: https://www.anthropic.com/engineering/building-effective-agents\\n---\\n\\n# Adversarial review\\n\\n## The problem\\n\\nA model that wrote a change is a poor judge of it, and a model of the same family as the author tends to share its blind spots. A friendly review passes work that a hostile one would reject. On cheap models, where a single pass is unreliable, that gap costs real runs.\\n\\n## The pattern\\n\\nPut at least two reviewers behind the implementer. One reads constructively. The other is told to assume the diff is wrong until every line earns its trust. Both must pass.\\n\\nRun the reviewers on models from different families, so they do not fail in the same way. Then put deterministic checks (formatter, linter, tests) in front of them, so no reviewer is asked to judge what a command can decide.\\n\\nAnthropic describes the shape in its evaluator-optimizer workflow: \\\"one LLM call generates a response while another provides evaluation and feedback in a loop.\\\" The lab's version adds a second, adversarial evaluator and mixes model families.\\n\\n## The pair\\n\\nA reviewer asks \\\"is this right?\\\". An adversary asks \\\"how do I break this?\\\". Those are different jobs and they produce different findings.\\n\\nThe figure shows the serial pair: implement, then the gate, then the adversary, then the review that commits. Note the inverted edges. The adversary reports fail when it succeeds at breaking the change, and that routes back to implement. It reports pass when it could not break it, and that routes on to the review.\\n\\nThe adversary's prompt must demand a concrete reproduction: an input, a sequence, a race. Not an opinion, not a \\\"this could be risky\\\". An adversary that cannot produce one has not found anything, and saying so must be a legitimate, unpunished outcome, or it will invent findings to look useful.\\n\\nThe pair fits work that arrives from outside. An issue filed by a stranger is exactly the case where something should try to falsify the fix before it lands.\\n\\n## What we saw\\n\\nEarly in Canopy, a bug defeated Opus 4.6. The operator spent many tokens on it without a fix. MiMo 2.5 solved it on the first try. The operator no longer remembers which bug it was.\\n\\nThis is one anecdote, and it proves nothing about which model is better. The lesson the lab took from it is that the models are different: MiMo's training may have covered a problem that Opus's did not. A reviewer from another family may see what the author's family cannot.\\n\\nSince then, the lab's standard is adversarial review by models of different families, plus deterministic checks.\\n\\n## In Canopy\\n\\nA parallel ensemble holds the reviewers. Its `min_pass` is 2, so both members must pass. Member 1 uses the shared constructive prompt. Member 2 overrides it with the adversarial one. No member names a platform or model, so whoever installs the graph binds a different family to each.\\n\\nThe shape you add, with the prompt fields left out:\\n\\n```json\\n{\\n \\\"name\\\": \\\"reviewers\\\",\\n \\\"kind\\\": \\\"parallel\\\",\\n \\\"commit_rights\\\": false,\\n \\\"entry_from_node\\\": \\\"gate\\\",\\n \\\"entry_condition\\\": \\\"pass\\\",\\n \\\"on_pass_to\\\": \\\"commit\\\",\\n \\\"on_fail_to\\\": \\\"implement\\\",\\n \\\"min_pass\\\": 2,\\n \\\"timeout_minutes\\\": 30\\n}\\n```\\n\\nThe reviewers have no commit rights. A single committer lands the work only after both pass, and a failure routes back to the implementer.\\n\\nThe `adversarial-developer` graph is the baseline implementation of this pair: the reviewer fails bad diffs back, the auditor repairs what it finds, and deterministic gates judge what a command can decide. The standalone breaker agent — report FAIL with a concrete reproduction, or PASS — stays opt-in for work that arrives from outside, and is added to that loop as one more ensemble when it is wanted; the baseline ships no dedicated breaker.\\n\\n## Try it\\n\\n```sh\\ncanopy graph install canopy-builder\\n```\\n\\nBind a different model family to each reviewer member of the pair you add.\\n\",\"figureName\":\"adversarial-review.figure.json\",\"figure\":\"{\\\"edges\\\": [{\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"implement\\\", \\\"to_node\\\": \\\"gate\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"gate\\\", \\\"to_node\\\": \\\"adversary\\\"}, {\\\"condition\\\": \\\"fail\\\", \\\"from_node\\\": \\\"adversary\\\", \\\"to_node\\\": \\\"implement\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"adversary\\\", \\\"to_node\\\": \\\"review\\\"}], \\\"format_version\\\": 6, \\\"name\\\": \\\"adversarial-review\\\", \\\"nodes\\\": [{\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"implement\\\", \\\"position\\\": 1}, {\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"gate\\\", \\\"position\\\": 2}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"adversary\\\", \\\"position\\\": 3}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"review\\\", \\\"position\\\": 4}]}\\n\"},{\"slug\":\"cross-platform-check\",\"page\":\"---\\ntitle: Cross-platform check\\nsummary: The reviewer runs on a different vendor from the implementer, so one family's blind spots and one quota ceiling cannot stall the graph.\\nstatus: in use\\nimplements:\\n - graph:canopy-builder\\nfigure: cross-platform-check.figure.json\\nsources:\\n - title: Building effective agents (Anthropic), evaluator-optimizer workflow\\n url: https://www.anthropic.com/engineering/building-effective-agents\\n---\\n\\n# Cross-platform check\\n\\n## The problem\\n\\nA model reviewing its own family's output shares its blind spots. It learned the same habits, trips on the same cases, and reads the spec through the same lens as the author. A second opinion from the same family is barely a second opinion.\\n\\nIt also shares the author's quota ceiling. When the vendor subscription runs out, the writer and the judge go dark together, and the graph stalls with work waiting.\\n\\n## The pattern\\n\\nKeep the gated shape, but put the reviewer on a different vendor from the implementer. One family writes, another family judges.\\n\\nSplitting the two buys independent judgment and means one exhausted subscription does not stall the whole graph. The implementer keeps drafting on its vendor while the reviewer keeps checking on another, and a failure of judgment in one family is caught by eyes trained elsewhere.\\n\\nKeep the cross-check prompt narrow: the diff, the spec, and an instruction to find what is wrong. A cross-checker invited to re-implement will re-implement, and then there are two authors and no reviewer.\\n\\nUse it for security-sensitive changes, and anywhere the fix looks overfit to one model's reading of the spec.\\n\\n## What we saw\\n\\nThe lab runs its reviews across vendors as standard practice. When the implementer and the reviewer come from different families, each covers readings the other misses, and a single vendor outage or quota ceiling leaves half the loop still standing.\\n\\nThe lab has not measured how many defects this catches that a same-family review would miss. It is a design choice, not a benchmark.\\n\\n## In Canopy\\n\\nNo catalogue document is dedicated to this pattern, because it is a binding choice, not a new shape. Install `canopy-builder` and bind its role ensembles across vendors: the Implementer on one, the Designer on a second, and the reviewer and the auditor on a third. Every ensemble in that document arrives without a platform or a model, so the split is yours to make at install time.\\n\\nEverything else stays as the loop defines it. The gate still sits before the reviewer, failures still route back to the implementer, and the documenter's own check still proves the tree is clean. Only the vendors differ.\\n\\nThat loop already reviews twice behind one gate — the reviewer must locate every requirement, then the auditor repairs what it finds — so a vendor split there puts two families on the same diff without adding a node.\\n\\n## Try it\\n\\n```sh\\ncanopy graph install canopy-builder\\n```\\n\\nBind the implementer and the reviewer to different vendors at install time, and keep the review prompt to diff plus spec plus \\\"find what is wrong\\\".\\n\",\"figureName\":\"cross-platform-check.figure.json\",\"figure\":\"{\\\"edges\\\": [{\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"implement\\\", \\\"to_node\\\": \\\"gate\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"gate\\\", \\\"to_node\\\": \\\"crosscheck\\\"}, {\\\"condition\\\": \\\"fail\\\", \\\"from_node\\\": \\\"gate\\\", \\\"to_node\\\": \\\"implement\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"crosscheck\\\", \\\"to_node\\\": \\\"finish\\\"}, {\\\"condition\\\": \\\"fail\\\", \\\"from_node\\\": \\\"crosscheck\\\", \\\"to_node\\\": \\\"implement\\\"}], \\\"format_version\\\": 6, \\\"name\\\": \\\"cross-platform-check\\\", \\\"nodes\\\": [{\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"implement\\\", \\\"position\\\": 1}, {\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"gate\\\", \\\"position\\\": 2}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"crosscheck\\\", \\\"position\\\": 3}, {\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"finish\\\", \\\"position\\\": 4}]}\\n\"},{\"slug\":\"deterministic-commit\",\"page\":\"---\\ntitle: Deterministic commit\\nsummary: A check node commits with the spec's own Conventional Commit subject, and a model never writes the commit.\\nstatus: in use\\nimplements:\\n - graph:canopy-builder\\n - graph:adversarial-developer\\nfigure: deterministic-commit.figure.json\\nsources:\\n - title: Building effective agents (Anthropic), ground truth from the environment\\n url: https://www.anthropic.com/engineering/building-effective-agents\\n---\\n\\n# Deterministic commit\\n\\n## The problem\\n\\nA commit message written by a model drifts. It invents trailers, rewords the intent, or describes what the model thinks it did. The history then no longer matches the work that was asked for.\\n\\nDrift compounds. Each commit that paraphrases its spec makes the log a worse record of what was decided, and a later reader cannot tell which wording was the instruction and which was the model's summary of itself.\\n\\n## The pattern\\n\\nLet the spec name its own commit. The spec's name is a Conventional Commit subject, `(): `, and a deterministic check node runs the commit with that text verbatim. A model never writes it.\\n\\nA second check proves the commit landed. Both are facts about the repo, so neither needs a judge.\\n\\nVerbatim matters because the history is then an audit trail, not a retelling. The commit says what was asked, the diff says what was done, and any gap between them is visible instead of smoothed over by a helpful rewrite.\\n\\n## What we saw\\n\\nThe lab names every spec as the subject its commit will carry, and keeps its graphs' commits verbatim from the spec name. The lab has no recorded origin story for it beyond that convention.\\n\\nWhat the convention buys is boring history. No trailers to strip, no paraphrases to decode, no commit whose message describes work that is not in the diff.\\n\\n## In Canopy\\n\\nBoth loop graphs carry this pattern as a check node with commit rights.\\nIn `adversarial-developer` it is the `Commit` node, whose command stages\\neverything and commits with the spec's own message verbatim. From\\n`graphs/adversarial-developer/graph.json`:\\n\\n```json\\n\\\"config\\\": {\\n \\\"command\\\": \\\"if [ -z \\\\\\\"$(git status --porcelain)\\\\\\\" ]; then echo 'tree clean: nothing to commit'; exit 0; fi; if [ -z \\\\\\\"$CANOPY_SPEC_COMMIT_MESSAGE\\\\\\\" ]; then echo 'CANOPY_SPEC_COMMIT_MESSAGE is empty'; exit 1; fi; git add -A && git commit -q -m \\\\\\\"$CANOPY_SPEC_COMMIT_MESSAGE\\\\\\\" && echo \\\\\\\"committed $(git log -1 --oneline)\\\\\\\"\\\",\\n \\\"commit_rights\\\": true\\n}\\n```\\n\\nWhen the tree is already clean, the command passes without committing.\\nPushing to the work branch is optional: add the push to the command if\\nthe graph should publish the branch.\\n\\nIn `adversarial-developer` the documenter leaves its docs uncommitted, so\\n`Commit` lands the code and the docs together under the one subject.\\n`canopy-builder` commits once per spec the same way: `Commit spec` lands\\nthe spec's own message after the auditor's repairs pass the gates again,\\nand pushes the branch when it succeeds. Its documenter commits the docs\\nseparately.\\n\\nA second check can prove the commit landed — that HEAD moved since the\\nspec started — but neither loop needs one: the gates and the review\\nhave already read the same change before it is committed, and the\\nhistory carries the spec's own subject. Add a proof check only where a\\nconcurrent commit from outside the run could be mistaken for this one.\\n\\nThe model-committed version this pattern replaces is a model that\\nreviews the diff and then commits, with a concise message it writes\\nitself. Pick the deterministic commit when the message must match the\\nspec exactly, and the model-committer when the message needs judgment\\nthe spec cannot carry.\\n\\n## Try it\\n\\n```sh\\ncanopy graph install canopy-builder\\ncanopy graph install adversarial-developer\\n```\\n\",\"figureName\":\"deterministic-commit.figure.json\",\"figure\":\"{\\\"edges\\\": [{\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"commit\\\", \\\"to_node\\\": \\\"proof\\\"}], \\\"format_version\\\": 6, \\\"name\\\": \\\"deterministic-commit\\\", \\\"nodes\\\": [{\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"commit\\\", \\\"position\\\": 1}, {\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"proof\\\", \\\"position\\\": 2}]}\\n\"},{\"slug\":\"deterministic-gates\",\"page\":\"---\\ntitle: Deterministic gates\\nsummary: Shell checks decide pass or fail, never an LLM judge, and a failure carries its reason into the next prompt.\\nstatus: standard\\nimplements:\\n - graph:canopy-builder\\n - graph:adversarial-developer\\n - node:full-gates-cargo\\n - node:full-gates-web\\nfigure: deterministic-gates.figure.json\\nsources:\\n - title: Building effective agents (Anthropic), ground truth from the environment\\n url: https://www.anthropic.com/engineering/building-effective-agents\\n - title: How we built our multi-agent research system (Anthropic), deterministic safeguards\\n url: https://www.anthropic.com/engineering/multi-agent-research-system\\n---\\n\\n# Deterministic gates\\n\\n## The problem\\n\\nAn LLM asked \\\"does this pass?\\\" can be wrong, flattered, or tired. On a cheap model it is also another paid call. Whether the formatter is clean and the tests are green is a fact about the repo, not an opinion.\\n\\n## The pattern\\n\\nLet a shell command decide. The gate runs the formatter, the linter and the tests, and its exit code is the verdict. No model sits in the judging seat.\\n\\nA gate that only says \\\"fail\\\" teaches the next attempt nothing. So the failure's output goes into the next prompt, and the implementer sees why it was sent back.\\n\\nAnthropic's agent guidance makes the same point about grounding: agents should \\\"gain 'ground truth' from the environment at each step.\\\" Its multi-agent write-up also pairs model judgment with deterministic safeguards such as retries and checkpoints.\\n\\nThree rules make this shape work, and each was learned by watching it not work.\\n\\n**The implement edge is `pass`, never `always`.** An `always` edge routes a failed implement into the reviewer, which then approves work that was never written. A real run marked a spec completed with no commit behind it that way.\\n\\n**The check sits before the reviewer.** Code that does not build never reaches review. Without that order, the reviewer writes up the breakage and the spec is marked completed anyway, because routing keys on the run's status, not on its prose.\\n\\n**Auto-fixing commands must not bounce work.** Run the formatter in rewrite mode, which fixes, not in check mode, which fails. Only genuine breakage should route back to implement.\\n\\nUse it for essentially everything. Start here and add shape only when a specific failure justifies it.\\n\\n## What we saw\\n\\nThe lab runs cheap, unreliable models with tight quota. Its standard loop puts the gates before any reviewer, so a reviewer's quota is never spent on code that does not compile.\\n\\nThe lab has not measured how much this saves. It is a design choice, not a benchmark.\\n\\n## Handing plans down\\n\\nA plan from one node to the next travels through spec notes, not files. A designer node writes its plan with `spec_note_write` instead of a temp file no later node can find, and a verify step checks the notes file for the plan's heading. Every agent prompt already carries the notes, so nothing needs deleting and nothing goes stale.\\n\\n## In Canopy\\n\\nA `check` node holds the command. Edges route `pass` forward and `fail` back to the implementer. In `adversarial-developer`, the gate that follows the implementer and its two edges look like this:\\n\\n```json\\n{\\n \\\"from_node\\\": \\\"Full gates\\\",\\n \\\"to_node\\\": \\\"Reviewer\\\",\\n \\\"condition\\\": \\\"pass\\\"\\n},\\n{\\n \\\"from_node\\\": \\\"Full gates\\\",\\n \\\"to_node\\\": \\\"Implementer\\\",\\n \\\"condition\\\": \\\"fail\\\"\\n}\\n```\\n\\nThe implementer's prompt reads `{{previous_feedback}}` and must address every point in it, which is how a failure's reason reaches the next attempt.\\n\\nThe `full-gates-cargo` node is the Rust version of the gate, from `nodes/full-gates-cargo/node.json`:\\n\\n```json\\n{\\n \\\"format_version\\\": 1,\\n \\\"name\\\": \\\"full-gates-cargo\\\",\\n \\\"kind\\\": \\\"check\\\",\\n \\\"description\\\": \\\"The Rust full gate: one check command runs `cargo fmt --all -- --check`, `cargo clippy --all-targets --all-features -- -D warnings` and `cargo test --all-features`. The project should add its dependency audit and coverage floor to the command.\\\",\\n \\\"config\\\": {\\n \\\"command\\\": \\\"cargo fmt --all -- --check && cargo clippy --all-targets --all-features -- -D warnings && cargo test --all-features\\\",\\n \\\"mutex\\\": [\\\"cpu\\\"],\\n \\\"timeout_seconds\\\": 6000\\n }\\n}\\n```\\n\\nA TypeScript or web project installs `full-gates-web` instead: the same shape, with a command that runs the install, the lint, the typecheck, the unit tests, the build and the end-to-end tests.\\n\\nIn the catalogue the gate command is the `` placeholder. Replace it with the command that runs your own formatter, linter, dependency audit, coverage floor and test suite. Until you do, the placeholder fails, so a graph cannot pass its gates by doing nothing.\\n\\n## Keep hardening the gate\\n\\nAn earlier catalogue carried a second graph whose whole job was to cover a finished change with tests. The lab dropped that shape. A separate test-review graph inspects one change at a time, while the gate every change must pass stays exactly as weak as it was, and work that nobody pointed the second graph at goes on shipping untested.\\n\\nHarden the one gate instead, so every spec meets the same bar. Put the test budget inside it: a cap on added-plus-removed lines, so a \\\"fix\\\" that rewrites the suite is refused. Keep the strict lint thresholds there, the dependency audit, and the coverage floor. Then a suite that ballooned, a suppressed lint and a stale dependency all fail the same command as a broken build, on every spec, without anyone deciding to run a review that day.\\n\\n## Try it\\n\\n```sh\\ncanopy graph install canopy-builder\\ncanopy node install full-gates-cargo\\ncanopy node install full-gates-web\\n```\\n\",\"figureName\":\"deterministic-gates.figure.json\",\"figure\":\"{\\\"edges\\\": [{\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"implement\\\", \\\"to_node\\\": \\\"gate\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"gate\\\", \\\"to_node\\\": \\\"review\\\"}, {\\\"condition\\\": \\\"fail\\\", \\\"from_node\\\": \\\"gate\\\", \\\"to_node\\\": \\\"implement\\\"}, {\\\"condition\\\": \\\"fail\\\", \\\"from_node\\\": \\\"review\\\", \\\"to_node\\\": \\\"implement\\\"}], \\\"format_version\\\": 6, \\\"name\\\": \\\"deterministic-gates\\\", \\\"nodes\\\": [{\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"implement\\\", \\\"position\\\": 1}, {\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"gate\\\", \\\"position\\\": 2}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"review\\\", \\\"position\\\": 3}]}\\n\"},{\"slug\":\"event-intake\",\"page\":\"---\\ntitle: Event intake\\nsummary: A graph that fires on a watched file, turning outside events into specs while nobody is watching.\\nstatus: in use\\nimplements: []\\nfigure: event-intake.figure.json\\nsources:\\n - title: GitHub webhooks documentation\\n url: https://docs.github.com/en/webhooks\\n---\\n\\n# Event intake\\n\\n## The problem\\n\\nWork arrives while nobody is watching. Issues are filed at night, feeds update on their own schedule, and a graph that only starts by hand waits for a human to notice. The issue sits until morning, and morning has its own work.\\n\\n## The pattern\\n\\nA graph does not have to be started by hand. It can fire on a cron slot or a filesystem watch, which makes it the back half of an event pipeline whose front half is anything that can write a file.\\n\\nThe front half does not need a public endpoint. A real webhook needs a tunnel, a forwarding service, something listening. For work that can wait five minutes, one cron line polling for new items and writing a file is strictly simpler and stays local. Use a real webhook only when the latency actually matters.\\n\\nThe intake node is a normal agent node whose job is to turn the file into a spec and queue it. Once it does, the rest of the graph is the gated loop and knows nothing about where the work came from.\\n\\n**The gap**: a watch-triggered graph is told that it fired, never what fired it. The watcher logs the changed path and then calls the engine with only the graph id. If the script writes to one well-known path, the intake node just reads that path and the gap costs nothing, which is why this is a solved pattern today. It bites the moment a directory is watched and the intake needs to know which file arrived.\\n\\n## What we saw\\n\\nThe lab runs intake against a single well-known path: a poll script writes one file, the graph fires on it, the intake node reads it. Within that shape the missing payload has never cost a run. Directory watch stays out of reach until the engine passes the path along.\\n\\n## In Canopy\\n\\nThe figure shows the full pipeline: the outside event, the script that writes the inbox file, the watch, the intake node, and then the gated loop of implement, gate, and review.\\n\\nNo catalogue document implements this pattern yet. The back half is a trigger watching that file plus the intake prompt below; the front half is one script in front of them.\\n\\n## Try it\\n\\nAn issue labeled for automation gets a branch and a proposed fix by morning. The front half is one cron line running a script like this every ten minutes:\\n\\n```bash\\n#!/usr/bin/env bash\\n# ~/bin/canopy-issue-inbox.sh — run from cron every 10 minutes\\nset -euo pipefail\\nINBOX=\\\"$HOME/.canopy/inbox/github-issue.json\\\"\\nmkdir -p \\\"$(dirname \\\"$INBOX\\\")\\\"\\n\\ngh issue list --repo you/yourrepo --label auto --state open \\\\\\n --limit 1 --json number,title,body > \\\"$INBOX.tmp\\\"\\n\\n# Only publish when there is actually an issue, and write atomically so the\\n# watcher never reads a half-written file.\\nif [ \\\"$(jq 'length' < \\\"$INBOX.tmp\\\")\\\" -gt 0 ]; then\\n mv \\\"$INBOX.tmp\\\" \\\"$INBOX\\\"\\nelse\\n rm -f \\\"$INBOX.tmp\\\"\\nfi\\n```\\n\\nThe atomic move matters. A watcher fires on the first write event, and a graph that starts reading a file another process is still writing will read half an issue.\\n\\nThe back half is a graph watching that file. The intake node turns the file into work and stops:\\n\\n```text\\n# [ROLE]\\nTurn one GitHub issue into one spec. You do not fix anything.\\n\\n# [HOW]\\n1. Read the inbox file.\\n2. Reproduce the problem well enough to state it precisely. An issue is a\\n report, not a specification.\\n3. Write the spec: objective, functional requirements, in scope, out of\\n scope, acceptance. Where the issue is ambiguous, choose the narrowest\\n reading and say so in OUT OF SCOPE.\\n4. Queue it for the graph.\\n5. Report pass with the spec id.\\n```\\n\\nFrom there it is the gated loop, plus an adversary before the commit:\\n\\n```text\\n# [ROLE]\\nYour job is to BREAK the change in the diff below. You succeed by finding a\\nconcrete way it fails.\\n\\n# [HOW]\\n- Produce a reproduction: an input, a sequence, a race, a boundary. Not an\\n opinion, not a \\\"this could be risky\\\".\\n- Report FAIL if you broke it — include the reproduction.\\n- Report PASS if you could not. That is a real result and an expected one.\\n- Do not fix anything. Breaking and repairing are different jobs.\\n```\\n\\nThe adversary's fail means it succeeded. Wire its fail edge back to implement and its pass edge on to the commit.\\n\\nThree things to keep an eye on. The graph never learns which file woke it, so the intake reads a fixed path and watching a directory does not work today. The script must be idempotent, or the same issue rewritten produces the same spec twice. And nothing here pushes: the graph commits on a branch, and opening the pull request stays a human decision, which is the correct default for work done unattended.\\n\",\"figureName\":\"event-intake.figure.json\",\"figure\":\"{\\\"edges\\\": [{\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"github-issue\\\", \\\"to_node\\\": \\\"inbox-script\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"inbox-script\\\", \\\"to_node\\\": \\\"watch\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"watch\\\", \\\"to_node\\\": \\\"intake\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"intake\\\", \\\"to_node\\\": \\\"implement\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"implement\\\", \\\"to_node\\\": \\\"gate\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"gate\\\", \\\"to_node\\\": \\\"review\\\"}], \\\"format_version\\\": 6, \\\"name\\\": \\\"event-intake\\\", \\\"nodes\\\": [{\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"github-issue\\\", \\\"position\\\": 1}, {\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"inbox-script\\\", \\\"position\\\": 2}, {\\\"config\\\": {}, \\\"kind\\\": \\\"gate\\\", \\\"name\\\": \\\"watch\\\", \\\"position\\\": 3}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"intake\\\", \\\"position\\\": 4}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"implement\\\", \\\"position\\\": 5}, {\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"gate\\\", \\\"position\\\": 6}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"review\\\", \\\"position\\\": 7}]}\\n\"},{\"slug\":\"parallel-proposers\",\"page\":\"---\\ntitle: Parallel proposers\\nsummary: Several cheap models draft the same spec in parallel, then one implementer builds on the best ideas.\\nstatus: in use\\nimplements: []\\nfigure: parallel-proposers.figure.json\\nsources:\\n - title: Mixture-of-Agents Enhances Large Language Model Capabilities (Wang et al.)\\n url: https://arxiv.org/abs/2406.04692\\n - title: Fusion Router (OpenRouter documentation)\\n url: https://openrouter.ai/docs/guides/routing/routers/fusion-router\\n - title: Building effective agents (Anthropic), parallelization\\n url: https://www.anthropic.com/engineering/building-effective-agents\\n---\\n\\n# Parallel proposers\\n\\n## The problem\\n\\nOne cheap model, one attempt, one set of blind spots. Spending a strong model on every spec is not affordable under tight quota, and a blank spec is the hardest thing to hand to a weak implementer.\\n\\n## The pattern\\n\\nAsk several models for a draft of the same spec at the same time. Then give one implementer the drafts and let it build on the best ideas. The implementer never faces the blank spec.\\n\\nThe canonical use spends the expensive model once, on a pre-digested problem. Three cheap drafts go in, the quorum consolidates them into one document, and the paid implementer writes the code. That is one expensive run instead of one expensive exploration plus one expensive implementation.\\n\\nThe idea has published precedent. The Mixture-of-Agents paper by Wang et al. arranges several LLMs in layers, where each agent uses the previous layer's outputs as additional context. Anthropic lists parallelization as a workflow: \\\"running the same task multiple times to get diverse outputs.\\\"\\n\\nUse it for problems where the approach is the hard part and the typing is not.\\n\\n## The mirror image\\n\\nThe same unit works in reverse: two or three cheap reviewers read the same diff, and the quorum consolidates their findings into one verdict. Drafting and reviewing are both pre-digestion; only the direction differs.\\n\\n## What we saw\\n\\nThe lab took the idea from OpenRouter's fusion mode. OpenRouter documents it as a panel of models answering the same prompt in parallel, with an analyst model comparing the answers. It states that the analyst \\\"doesn't merge them.\\\" The lab's version is simpler: the drafts go straight to an implementer.\\n\\nWith a similar technique, the operator got reasoning from cheaper models comparable to Fable 5. This is an observation from the lab's own use. It is not a benchmark, and the lab has no controlled comparison.\\n\\nParallel drafts cost more calls. OpenRouter estimates a three-model panel at roughly four to five times a single completion, and the lab does not claim the trade is free.\\n\\n## In Canopy\\n\\nAn ensemble is one unit, not several hand-wired nodes. A parallel ensemble takes two to eight members, and the sequential kinds take more, because they dispatch one member at a time. Both caps can be raised in settings.\\n\\nCreate it with a single call that makes the members, the quorum, and the entry and exit edges together. Wiring several parallel `pass` edges out of one node by hand is the ambiguous-outgoing-edges mistake, and it aborts the run.\\n\\nA parallel ensemble node holds the proposers. All members share one prompt template, and each member is an empty object so that nobody's platform or model is baked in. The catalogue ships no proposers node: build the ensemble in the graph you own, give it the draft prompt, set `min_pass` to the member count so every draft must pass, and bind a different model to each member at install time. The figure on this page carries the same shape, with the wiring left out:\\n\\n```json\\n{\\n \\\"name\\\": \\\"drafters\\\",\\n \\\"kind\\\": \\\"parallel\\\",\\n \\\"min_pass\\\": 3,\\n \\\"members\\\": [\\n {},\\n {},\\n {}\\n ],\\n \\\"timeout_minutes\\\": 30\\n}\\n```\\n\\nThree drafters run at once and their outputs go straight to one implementer, which is the point: the drafts are pre-digestion, and the paid model only writes.\\n\",\"figureName\":\"parallel-proposers.figure.json\",\"figure\":\"{\\\"edges\\\": [], \\\"ensembles\\\": [{\\\"commit_rights\\\": false, \\\"entry_condition\\\": \\\"pass\\\", \\\"entry_from_node\\\": \\\"start\\\", \\\"kind\\\": \\\"parallel\\\", \\\"members\\\": [{}, {}, {}], \\\"min_pass\\\": 3, \\\"name\\\": \\\"drafters\\\", \\\"on_pass_to\\\": \\\"implement\\\", \\\"prompt_template\\\": \\\"\\\", \\\"timeout_minutes\\\": 30}], \\\"format_version\\\": 6, \\\"name\\\": \\\"parallel-proposers\\\", \\\"nodes\\\": [{\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"start\\\", \\\"position\\\": 1}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"implement\\\", \\\"position\\\": 2}]}\\n\"},{\"slug\":\"resilient-members\",\"page\":\"---\\ntitle: Resilient members\\nsummary: Cascade and round-robin ensembles give one node a fallback ladder or a rotation across providers, so a quota death is not a failed run.\\nstatus: in use\\nimplements:\\n - graph:canopy-builder\\nfigure: resilient-members.figure.json\\nsources:\\n - title: How we built our multi-agent research system (Anthropic), resilience\\n url: https://www.anthropic.com/engineering/multi-agent-research-system\\n---\\n\\n# Resilient members\\n\\n## The problem\\n\\nCheap models run out of quota, and harnesses fail mid-run. A node bound to a single provider turns either event into a failed run, even though the work itself was fine.\\n\\nWorse, the plain gated shape fails the spec the moment implement dies, so nobody reads the failure. If the CLI died on a quota that resets at a known hour, that knowledge is thrown away.\\n\\n## The pattern\\n\\nGive a node several members instead of one.\\n\\n- **Cascade:** the members form a ladder. If the first fails on quota or harness, the run falls through to the next.\\n- **Round robin:** the members take turns, so spend rotates across providers and no single quota is drained first.\\n\\nAnthropic describes the same concern in its multi-agent write-up: restarts are costly, so the team \\\"built systems that can resume from where the agent was when the errors occurred.\\\" Letting a node fall to another member is a cheaper cousin of that idea.\\n\\n## Resilience branch\\n\\nGive implement two exits. On pass it goes to the gate as usual; on fail it goes to a resilience node, so the failure is read instead of thrown away.\\n\\nThe resilience node is a cheap model on a different quota from the implementer, or it is dead exactly when needed. It either repairs a transient failure and reports pass, so implement retries, or recognizes exhaustion, schedules the graph's own wake-up at the reset time, and reports fail. The spec fails, but the graph wakes itself at that instant.\\n\\nGive it the clock. Models do not know what time it is: a real run at night scheduled its wake-up for 6am the next day, a 23-hour stall. The prompt must require reading the current time first.\\n\\n## Free-tier cascade\\n\\nSeveral CLIs with generous free tiers and small ceilings: spend them in sequence before touching anything paid. One ensemble node of kind cascade does it in a single call, members ordered cheapest to dearest.\\n\\nMembers run one at a time. A failure falls through to the next cold; the first pass exits. A bounce resumes the member that last passed. A member with no infra retries falls through at once on a crash. The lab's committer is an ensemble too: several members, one commit.\\n\\n### Routing on why a member failed\\n\\nThe cascade falls through on any failure: exhaustion, a network error, and wrong code all look the same. That is correct when members are equally capable and the goal is only to spend ceilings in order. It is wrong when wrong code should go to a fixer, not a weaker harness.\\n\\nFor that case a router variant reads the failure and routes on the reason. A router is only justified when the reason changes where the work goes; for spending tiers in sequence, the plain cascade is enough.\\n\\n### The naive version and why it is a trap\\n\\nA chain of fail edges from one implementer to the next looks like a cascade but is a trap. Fail conflates \\\"my quota is gone\\\" with \\\"this code is wrong\\\". A spec with a genuine bug burns every free tier on the same wrong task, ending the day with every ceiling spent and nothing done.\\n\\nA chain only makes sense routing on why the node failed: one router for the whole cascade, each fail edge pointing at it. It sends exhaustion down the ladder, transients back for retry, and broken code out as a failed spec. Untested; build it only when the cascade must route on reason.\\n\\n### Two things worth knowing before you build\\n\\n**Exhaustion looks different on every harness.** Some CLIs exit non-zero naming the reset time; others stop responding and die on the node's timeout. A member that times out falls through to the next, which is usually wanted. A router generalizes across both.\\n\\n**The engine has no memory of a spent harness.** If one spec burns the first tiers, the next spec starts at the top and rediscovers they are dead, for every remaining spec. Today the only lever parks the whole graph until a reset. A per-harness \\\"unavailable until\\\" does not exist yet.\\n\\n## What we saw\\n\\nCascade and round robin reuse the ensemble idea from the parallel proposers: an ensemble already holds several members for one node, so the same structure holds fallbacks instead of parallel drafts. The lab has no measurements on how many runs this saved.\\n\\n## In Canopy\\n\\n`canopy-builder` ships this pattern: each of its five working roles is a round-robin ensemble — Designer, Implementer, Reviewer, Auditor and Documenter — and every member is an empty object, so no harness is baked in. From `graphs/canopy-builder/graph.json`:\\n\\n```json\\n{\\n \\\"name\\\": \\\"Implementer\\\",\\n \\\"kind\\\": \\\"round_robin\\\",\\n \\\"commit_rights\\\": false,\\n \\\"entry_from_node\\\": \\\"Plan check\\\",\\n \\\"entry_condition\\\": \\\"pass\\\",\\n \\\"extra_entry_sources\\\": [\\n {\\n \\\"from_node\\\": \\\"Full gates\\\",\\n \\\"condition\\\": \\\"fail\\\"\\n }\\n ],\\n \\\"on_pass_to\\\": \\\"Full gates\\\",\\n \\\"on_fail_to\\\": \\\"Plan check\\\",\\n \\\"min_pass\\\": 4,\\n \\\"members\\\": [\\n {},\\n {},\\n {},\\n {}\\n ],\\n \\\"timeout_minutes\\\": 120\\n}\\n```\\n\\nThe members take turns: spend rotates across providers, and a quota death rotates to the next member instead of failing the spec. Bind a different harness to each member at install time. The schema also allows `cascade`, where the members form a ladder and the first pass exits.\\n\\nFor the failure side of the same problem, `canopy-builder` carries a `Resilience` node: it reacts after a node fails, where members prevent the failure from ending the run.\\n\\n## Try it\\n\\n```sh\\ncanopy graph install canopy-builder\\n```\\n\\nTo spend free tiers first: one cascade ensemble, cheapest to dearest, one pass enough to exit, short per-member timeouts, no infra retries. The honest limit is the missing memory above: each new spec rediscovers the spent tiers.\\n\",\"figureName\":\"resilient-members.figure.json\",\"figure\":\"{\\\"edges\\\": [{\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"gate\\\", \\\"to_node\\\": \\\"review\\\"}, {\\\"condition\\\": \\\"fail\\\", \\\"from_node\\\": \\\"gate\\\", \\\"to_node\\\": \\\"start\\\"}, {\\\"condition\\\": \\\"fail\\\", \\\"from_node\\\": \\\"review\\\", \\\"to_node\\\": \\\"start\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"resilience\\\", \\\"to_node\\\": \\\"start\\\"}], \\\"ensembles\\\": [{\\\"commit_rights\\\": false, \\\"entry_condition\\\": \\\"pass\\\", \\\"entry_from_node\\\": \\\"start\\\", \\\"kind\\\": \\\"cascade\\\", \\\"members\\\": [{}, {}], \\\"min_pass\\\": 1, \\\"name\\\": \\\"implementers\\\", \\\"on_fail_to\\\": \\\"resilience\\\", \\\"on_pass_to\\\": \\\"gate\\\", \\\"prompt_template\\\": \\\"\\\", \\\"timeout_minutes\\\": 30}], \\\"format_version\\\": 6, \\\"name\\\": \\\"resilient-members\\\", \\\"nodes\\\": [{\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"start\\\", \\\"position\\\": 1}, {\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"gate\\\", \\\"position\\\": 2}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"review\\\", \\\"position\\\": 3}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"resilience\\\", \\\"position\\\": 4}]}\\n\"},{\"slug\":\"review-panel\",\"page\":\"---\\ntitle: Review panel\\nsummary: An experiment, each reviewer owns one axis, security, performance, maintainability, or project patterns, and every axis must pass.\\nstatus: experiment\\nimplements: []\\nfigure: review-panel.figure.json\\nsources:\\n - title: Mixture-of-Agents Enhances Large Language Model Capabilities (Wang et al.)\\n url: https://arxiv.org/abs/2406.04692\\n---\\n\\n# Review panel\\n\\nThis pattern is an experiment. The lab has designed it but not run it.\\n\\n## The problem\\n\\nOne reviewer reads for everything and covers nothing well. Security, performance, maintainability, and the project's own patterns each deserve a full read, and a single pass over a large diff gives each a skim. The result is a review that approves correct-looking code with a whole dimension unexamined.\\n\\n## The pattern\\n\\nRun an ensemble where each member reviews a different axis: one reads for security, one for performance, one for maintainability, one for the project's own patterns. Per-member prompt overrides make this one call instead of four sequential nodes, and all four axes run in parallel.\\n\\n**`min_pass` means the wrong thing here.** With homogeneous members, two of three reads as \\\"a majority agrees\\\" and that is sensible. With specialists, each member owns a different axis, so three of four reads as \\\"one whole dimension of quality may fail and we ship anyway.\\\" Every axis must pass.\\n\\nBut all-must-pass with four cheap models means the grumpiest one blocks every change forever. The honest requirement is not a vote count at all. It is severity: block on a real finding, record a nitpick. The quorum counts votes and cannot express that.\\n\\nThe workable version today sets the threshold to all members and pushes the severity discipline into each member's prompt: fail only on a defect in your axis that would matter in production; everything else goes in the report, not the verdict. That raises the floor without pretending the engine understands severity.\\n\\n## What we saw\\n\\nNothing yet. The panel is designed, with the severity problem above as its known open question. Until the engine can tell a blocking finding from a note, the prompt discipline carries that weight alone.\\n\\n## In Canopy\\n\\nThe figure shows the panel as one parallel ensemble between the gate and the commit: implement, then the gate, then the four specialists, then the commit. A fail from the quorum routes back to implement.\\n\\nNo catalogue document implements this pattern yet.\\n\\n## Try it\\n\\nToday the same coverage can be built from four plain agent nodes chained in series. Slower than parallel, identical in coverage:\\n\\n```text\\ngates --pass--> review-security --pass--> review-performance\\nreview-performance --pass--> review-maintainability --pass--> review-patterns\\nreview-patterns --pass--> commit\\nany review --fail--> implement\\n```\\n\\nAny specialist failing bounces the work, which is the semantics wanted and which a vote count cannot express.\\n\\nOne template serves all four, with one axis substituted. The last rules are what keep a cheap model from turning into a style bot:\\n\\n```text\\n# [ROLE]\\nYou review one diff on ONE axis: SECURITY. Other reviewers own the other\\naxes. Do not review theirs.\\n\\n# [WHAT]\\nInjection, authz, secrets in code or logs, unsafe deserialization, TOCTOU,\\nanything that widens what an attacker can reach.\\n\\n# [HOW]\\n- Cite file:line for every finding.\\n- FAIL only for a defect on your axis that would matter in production.\\n- Everything else — style, taste, a nicer way to write it — goes in your\\n report, never in your verdict.\\n- Finding nothing is a legitimate and expected outcome. Do not manufacture\\n a finding to look useful.\\n\\n# [SPEC]\\n{{spec_content}}\\n```\\n\\nThat last rule is not politeness. A model that believes an empty report reflects badly on it will produce findings, and a panel of four such models will bounce every change forever.\\n\\nThe same panel collapses into one parallel ensemble with per-member prompt overrides, one per axis, and the threshold set to the member count. Wall-clock drops to the slowest reviewer instead of the sum. If you publish the result, the repo README explains how.\\n\",\"figureName\":\"review-panel.figure.json\",\"figure\":\"{\\\"edges\\\": [{\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"implement\\\", \\\"to_node\\\": \\\"gate\\\"}], \\\"ensembles\\\": [{\\\"commit_rights\\\": false, \\\"entry_condition\\\": \\\"pass\\\", \\\"entry_from_node\\\": \\\"gate\\\", \\\"kind\\\": \\\"parallel\\\", \\\"members\\\": [{}, {}, {}, {}], \\\"min_pass\\\": 4, \\\"name\\\": \\\"review-panel\\\", \\\"on_fail_to\\\": \\\"implement\\\", \\\"on_pass_to\\\": \\\"commit\\\", \\\"prompt_template\\\": \\\"\\\", \\\"timeout_minutes\\\": 30}], \\\"format_version\\\": 6, \\\"name\\\": \\\"review-panel\\\", \\\"nodes\\\": [{\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"implement\\\", \\\"position\\\": 1}, {\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"gate\\\", \\\"position\\\": 2}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"commit\\\", \\\"position\\\": 3}]}\\n\"},{\"slug\":\"reviewer-fixes\",\"page\":\"---\\ntitle: Auditor fixes\\nsummary: The auditor repairs what it finds, instead of writing change requests for another model to read.\\nstatus: in use\\nimplements:\\n - node:auditor\\nfigure: reviewer-fixes.figure.json\\nsources:\\n - title: Building effective agents (Anthropic), evaluator-optimizer workflow\\n url: https://www.anthropic.com/engineering/building-effective-agents\\n---\\n\\n# Auditor fixes\\n\\n## The problem\\n\\nA rejection is a message from one model to another. The implementer must read it, understand it and redo the work, and on a cheap model each round trip can lose detail or introduce a new fault. When the defect is small, the round trip costs more than the fix.\\n\\n## The pattern\\n\\nLet the auditor repair what it finds. Instead of a list of change requests, it edits the diff, and a later step commits. The loop back to the implementer is kept for the cases the auditor cannot repair.\\n\\nThis differs from the plain evaluator-optimizer loop Anthropic describes, in which \\\"one LLM call generates a response while another provides evaluation and feedback in a loop.\\\" Here the evaluator can also write.\\n\\nRepair wins when the defect is small and clear: a missed edge case, a wrong boundary, a test that asserts too little. The auditor already holds the full context, so the fix is one edit, not a handoff.\\n\\nBounce the work back when the defect is large or the implementer misread the spec. An auditor that rewrites half the diff is no longer auditing; it is implementing without the implementer's instructions, and its output deserves its own review.\\n\\n## What we saw\\n\\nThe lab has no recorded origin story for this pattern, and no measurement of how often repairs beat a rejection. Treat the claim above as a design intent, not a result.\\n\\nThe practical rule is trust calibration. Give the repair licence to an auditor model strong enough to edit carefully, and keep the bounce loop behind it for everything else.\\n\\n## In Canopy\\n\\nThe catalogue's `auditor` node reviews strictly against the spec with a licence to edit. Its prompt repairs what it objects to directly in the working tree, rewrites tests that would not fail without the feature, and runs the gate script once at the end. It never commits: a later node commits its fixes.\\n\\nThe gate script path is the one thing the prompt does not carry. Fill\\nin `` at install time with the project's deterministic\\ngates. From the prompt in `nodes/auditor/node.json`:\\n\\n```\\nIt is exactly what the gate node after you runs (formatting, the\\nlinter at -D warnings, the dependency audit, the coverage floor, the\\nfull suite); it may wait for the CPU lock first. Fix until it prints\\nGATES PASSED. If you changed nothing, do not run it.\\n```\\n\\nThe single run at the end is deliberate. The auditor proves its own\\nrepairs without turning into a second implementer that iterates.\\n\\nThis differs from the catalogue's `reviewer` node, which fails a\\nbad diff back to the implementer with a list of what is wrong instead\\nof fixing it. `canopy-builder` splits the two jobs: its reviewer\\nonly reports what is missing, its auditor repairs what it\\nfinds, and a check node commits those repairs. Pick the arrangement\\nthat matches how much you trust the model that repairs.\\n\\n## Try it\\n\\n```sh\\ncanopy node install auditor\\n```\\n\",\"figureName\":\"reviewer-fixes.figure.json\",\"figure\":\"{\\\"edges\\\": [{\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"implement\\\", \\\"to_node\\\": \\\"review\\\"}, {\\\"condition\\\": \\\"fail\\\", \\\"from_node\\\": \\\"review\\\", \\\"to_node\\\": \\\"implement\\\"}], \\\"format_version\\\": 6, \\\"name\\\": \\\"reviewer-fixes\\\", \\\"nodes\\\": [{\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"implement\\\", \\\"position\\\": 1}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"review\\\", \\\"position\\\": 2}]}\\n\"},{\"slug\":\"task-router\",\"page\":\"---\\ntitle: Task router\\nsummary: An experiment, a router judges the task by kind or complexity and sends it to a model suited to it.\\nstatus: experiment\\nimplements: []\\nfigure: task-router.figure.json\\nsources:\\n - title: Building effective agents (Anthropic), routing\\n url: https://www.anthropic.com/engineering/building-effective-agents\\n---\\n\\n# Task router\\n\\nThis pattern is an experiment. The lab has not tested it properly.\\n\\n## The problem\\n\\nOne model for every task wastes money on easy work and risks quality on hard work. Some tasks also need a quality that cheap models lack, such as visual taste.\\n\\n## The pattern\\n\\nInsert a router in front of the work. It reads the task, judges its kind or its complexity, and sends it down the branch that fits. Two implementers sit behind it on different harnesses and price points, and a cheap classifier decides which one earns each spec.\\n\\nAnthropic names this workflow: \\\"Routing classifies an input and directs it to a specialized followup task.\\\" It is a standard pattern.\\n\\nTwo things about the diagram are load-bearing and easy to get wrong.\\n\\n**A failed cheap implement escalates to the expensive one. It does not go back to the router.** The router would classify the same spec the same way and send it back to the model that just failed, forever. Routing decisions based on the spec are stable by definition; only new information should produce a new decision.\\n\\n**The check's fail edge also goes to the expensive implementer**, for the same reason: by the time a build is red, the cheap classification has been falsified by evidence.\\n\\n**The router must be cheap.** A router as expensive as the expensive implementer means the high price is paid on every task, including the ones routed away from it.\\n\\n## What we saw\\n\\nNothing yet, and that is the point of the label. The router is an untested hypothesis in Canopy, not an observed result.\\n\\nThe idea is an example, not a finding. Graphic design and assets would go to a model with strong taste, and everything else to cheap models. Opus 5.5 designed the univerlab.org site and its assets with the operator, which is why the hypothesis looks plausible to the lab. One project is not evidence that a router would choose well, or that it would cost less than a single model.\\n\\nRisks worth testing: the router can misjudge, and every routing decision is itself a model call.\\n\\n## In Canopy\\n\\nThe graph schema has a `router` node kind, and edges can carry a route label as their condition. A router graph sends each label to a different branch: the simple route to the cheap implementer, the complex route to the expensive one.\\n\\nThe figure shows the full shape with its two load-bearing edges. The cheap implementer's `fail` edge points at the expensive implementer, and the gate's `fail` edge does the same. Neither points back at the router.\\n\\nNo document in this catalogue uses a router yet, and this repository has no real file to copy an excerpt from. This page therefore implements no catalogue document.\\n\\n## Try it\\n\\nNo catalogue document implements this pattern yet. If you build one, the repo README explains how to publish it. Keep the router cheap, keep both fail edges on the expensive branch, and test whether the router's judgments beat a single model before trusting it with a backlog.\\n\",\"figureName\":\"task-router.figure.json\",\"figure\":\"{\\\"edges\\\": [{\\\"condition\\\": {\\\"route\\\": \\\"simple\\\"}, \\\"from_node\\\": \\\"router\\\", \\\"to_node\\\": \\\"implement-cheap\\\"}, {\\\"condition\\\": {\\\"route\\\": \\\"complex\\\"}, \\\"from_node\\\": \\\"router\\\", \\\"to_node\\\": \\\"implement-expensive\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"implement-cheap\\\", \\\"to_node\\\": \\\"gate\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"implement-expensive\\\", \\\"to_node\\\": \\\"gate\\\"}, {\\\"condition\\\": \\\"pass\\\", \\\"from_node\\\": \\\"gate\\\", \\\"to_node\\\": \\\"review\\\"}, {\\\"condition\\\": \\\"fail\\\", \\\"from_node\\\": \\\"gate\\\", \\\"to_node\\\": \\\"implement-expensive\\\"}, {\\\"condition\\\": \\\"fail\\\", \\\"from_node\\\": \\\"implement-cheap\\\", \\\"to_node\\\": \\\"implement-expensive\\\"}], \\\"format_version\\\": 6, \\\"name\\\": \\\"task-router\\\", \\\"nodes\\\": [{\\\"config\\\": {}, \\\"kind\\\": \\\"router\\\", \\\"name\\\": \\\"router\\\", \\\"position\\\": 1}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"implement-cheap\\\", \\\"position\\\": 2}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"implement-expensive\\\", \\\"position\\\": 3}, {\\\"config\\\": {}, \\\"kind\\\": \\\"check\\\", \\\"name\\\": \\\"gate\\\", \\\"position\\\": 4}, {\\\"config\\\": {}, \\\"kind\\\": \\\"agent\\\", \\\"name\\\": \\\"review\\\", \\\"position\\\": 5}]}\\n\"}]") as BundledPattern[]; diff --git a/apps/graphs/test/fixtures/graphs/adversarial-developer/README.md b/apps/graphs/test/fixtures/graphs/adversarial-developer/README.md index 0d198fd..d587499 100644 --- a/apps/graphs/test/fixtures/graphs/adversarial-developer/README.md +++ b/apps/graphs/test/fixtures/graphs/adversarial-developer/README.md @@ -15,12 +15,10 @@ resilience node that can schedule its own wake-up. Differences that matter when you move from here to `canopy-builder`: -- **One commit, at the end.** The documenter leaves its docs uncommitted +- **One commit, docs included.** The documenter leaves its docs uncommitted and `Commit` lands the spec's work and its docs together, under the - spec's own message. `canopy-builder` commits the code first, reviews the - commit, and commits the audit fixes separately. -- **The auditor reads the working tree.** Here the change is - uncommitted when it audits; in `canopy-builder` it reads `HEAD`. + spec's own message. `canopy-builder` also commits once per spec, after the + auditor, but its documenter commits the docs separately. - **No lock.** Nothing stops another process committing while the loop runs, so do not run two of these against the same branch. - **A designer failure ends the spec.** There is no resilience node to diff --git a/package-lock.json b/package-lock.json index e4ba5b3..2e4bbd4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "canopy-web", - "version": "0.1.0", + "version": "0.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "canopy-web", - "version": "0.1.0", + "version": "0.1.1", "license": "Apache-2.0", "dependencies": { "@cfworker/json-schema": "^4.1.1", diff --git a/package.json b/package.json index c058211..cb6d390 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "canopy-web", - "version": "0.1.0", + "version": "0.1.1", "description": "UniverLab graphs catalogue: JSON Schemas and validator for shareable Canopy graph and node documents", "type": "module", "license": "Apache-2.0",