Composing a small architecture from scratch
You’re going to build a complete cognitive architecture from scratch — two skills, one workflow, two agent configs, no external tools. It’s the smallest useful thing: a model proposes a patch, a critic adversarially reviews it, you ship or you don’t. By the end you’ll have a 30-line YAML you can run.
The goal
Section titled “The goal”A code-review architecture. The flow:
- A reviewing agent reads a patch and produces a verdict against a rubric.
- A critiquing agent adversarially attacks the verdict — what did the reviewer miss?
- The workflow lands in
donewith both opinions recorded in the workflow’s shared state (itscontext).
No retrieval, no editing, no human gate. The minimum viable cognitive architecture. You’d extend it later; for now you’ll see the whole shape on one screen. And here’s the part that fixes the failure you actually care about: by Step 6 you’ll add a deterministic step that runs the real test suite before any approval lands — no model in the loop, so the model can’t talk its way past a red build.
Step 1 — Identify the verbs
Section titled “Step 1 — Identify the verbs”Both jobs are evaluation against criteria. One verb: review. Subject root: review.*. That’s it.
If you found yourself reaching for two different verbs at this step, you’d want two different workflows or you’d want to think harder about whether you have one job or two. Keep it tight.
Step 2 — Write two skill fragments
Section titled “Step 2 — Write two skill fragments”skills: review.code.adversarial: verb: review lifecycle: stable body: | # Adversarial patch review - Find missed edge cases, hidden regressions, policy or security issues. - Cite each finding with file:line and a counterexample input. - Return verdict: accept | retry | escalate.
review.code.final-approval: verb: review lifecycle: stable body: | # Final approval checklist - Re-read the diff against the original acceptance criteria, not just the summary. - Confirm the adversarial review's findings were addressed or explicitly waived. - Return verdict: approve | reject with a specific question.Two skills, two distinct cognitive postures. Both review, but adversarial primes “attack this” and final-approval primes “decide and ship.”
Step 3 — Write the workflow
Section titled “Step 3 — Write the workflow”Three states: reviewing → critiquing → done. The first two delegate to named agents (the operator wires up which model plays each role). The terminal state has no delegate.
The blackboard: block is the workflow’s shared state — the typed declaration of what context holds (the same context you thread results into elsewhere; see Workflows). Each output: mapping writes into it, and later states read from it.
workflows: code_review: version: "2026-05-25" initialState: reviewing skills: - review.code.adversarial - review.code.final-approval blackboard: candidatePatch: { type: object } reviewVerdict: { type: string } critiqueVerdict: { type: string }
states: reviewing: delegate: review-agent goal: Adversarially review the candidate patch skills: - review.code.adversarial transitions: review_done: target: critiquing executor: { kind: noop } output: reviewVerdict: "$.arguments.verdict"
critiquing: delegate: critique-agent goal: Final approval pass over the reviewed patch skills: - review.code.final-approval transitions: approve: target: done executor: { kind: noop } output: critiqueVerdict: "$.arguments.verdict"
done: terminal: truekind: noop means no executor runs on the transition — the agent’s verdict in arguments is the result, and the output: mapping records it. (The deterministic test-runner step in Step 6 is where an executor actually does work.)
A note on delegate:, because it changes meaning depending on how you run this. Run this workflow inside your existing agent — Claude Code or Cursor driving flowgate’s two tools — and delegate: is purely informational: one model (your host’s model) drives every state, and review-agent/critique-agent are just labels on the record. You still get the real win here: the model must walk reviewing → critiquing → done in order, and the verification step in Step 6 can’t be skipped. The actual multi-model split — review-agent on one model, critique-agent on another, each in an isolated sub-agent session with no shared write handle — needs flowgate’s own runtime, which is one of three ways to run this. See How to run it for which mode does what.
Step 4 — Pick agents
Section titled “Step 4 — Pick agents”Two roles, two model choices. The bet is that the first pass is volume work and the critique is precision work.
review-agent— Sonnet. Cheap enough to run on every patch, smart enough to catch the obvious misses. The agent’s job is throughput.critique-agent— Opus. You’re paying for the second opinion. If it agrees with the reviewer, you ship. If it doesn’t, you’ve spent the money to find the bug before the user did.
You’d swap these around for other workloads (Opus for review on safety-critical code, Sonnet for critique on docs PRs). The workflow doesn’t change; the agent configs do.
Step 5 — Wire connections
Section titled “Step 5 — Wire connections”None. This architecture doesn’t touch external tools. The reviewing and critiquing agents work entirely from the patch text and the rubric in the skill body.
This is the simplest case. The moment you want retrieval (search the codebase before reviewing), verification (run tests after editing), or human approval gates, you add a connections: block. For now: skip it.
Step 6 — Add a deterministic verification step (scripts)
Section titled “Step 6 — Add a deterministic verification step (scripts)”Reviewers are good. They miss things anyway. Before the workflow declares approval, run the workspace’s actual test suite — deterministically, hash-pinned, no model in the loop.
This is what curated scripts are for. Where a skill is guidance for an LLM, a script is executable bytes the workflow runs through the script executor. Both live at the same intent layer (see The intent layer). Both are content-identified by hash. Their division of labor is determinism — skills are indeterminate (the model interprets), scripts are determinate (the bytes execute).
Add a scripts: block. One script for now: run the workspace’s checks and either pass or fail loud.
scripts: verify.workspace.green: verb: verify lifecycle: stable body: | #!/usr/bin/env bash set -euo pipefail cargo fmt --all -- --check >&2 cargo clippy --workspace --all-targets -- -D warnings >&2 cargo test --workspace >&2 echo '{"passed":true,"stages":["format","lint","test"]}'Insert a verifying state into the workflow between critiquing and done. The state’s transition uses kind: script with the curated subject — the runtime materializes the body from this workflow’s pinned snapshot, execs it (chmod 0700 temp file), captures the verdict, and audit-logs the body hash.
verifying: goal: Confirm the workspace is green before approval transitions: run_verifier: target: done actor: deterministic executor: kind: script subject: verify.workspace.green output: verifierPassed: "$.output.success"The transition is actor: deterministic because no model decides whether to take it — the workflow auto-chains once the critique succeeds. The verifying script’s exit code drives the verdict: a non-zero exit fails the transition (if you declared an onTimeout or escalate path on the state, it takes that — this minimal example just fails the transition and stops). Pair the executor with a script_acknowledged guard if you want operators to review the script body before it runs.
Step 7 — Compose the gateway config
Section titled “Step 7 — Compose the gateway config”Put it all together. Save as code-review.yaml:
version: "1.0.0"
skills: review.code.adversarial: verb: review lifecycle: stable body: | # Adversarial patch review - Find missed edge cases, hidden regressions, policy or security issues. - Cite each finding with file:line and a counterexample input. - Return verdict: accept | retry | escalate.
review.code.final-approval: verb: review lifecycle: stable body: | # Final approval checklist - Re-read the diff against the original acceptance criteria. - Confirm the adversarial review's findings were addressed or waived. - Return verdict: approve | reject with a specific question.
scripts: verify.workspace.green: verb: verify lifecycle: stable body: | #!/usr/bin/env bash set -euo pipefail cargo fmt --all -- --check >&2 cargo clippy --workspace --all-targets -- -D warnings >&2 cargo test --workspace >&2 echo '{"passed":true,"stages":["format","lint","test"]}'
workflows: code_review: version: "2026-05-25" initialState: reviewing skills: [review.code.adversarial, review.code.final-approval] blackboard: candidatePatch: { type: object } reviewVerdict: { type: string } critiqueVerdict: { type: string } verifierPassed: { type: boolean } states: reviewing: delegate: review-agent goal: Adversarially review the candidate patch skills: [review.code.adversarial] transitions: review_done: target: critiquing executor: { kind: noop } output: { reviewVerdict: "$.arguments.verdict" } critiquing: delegate: critique-agent goal: Final approval pass over the reviewed patch skills: [review.code.final-approval] transitions: approve: target: verifying executor: { kind: noop } output: { critiqueVerdict: "$.arguments.verdict" } verifying: goal: Confirm the workspace is green before approval transitions: run_verifier: target: done actor: deterministic executor: kind: script subject: verify.workspace.green output: verifierPassed: "$.output.success" done: terminal: trueStep 8 — Validate
Section titled “Step 8 — Validate”mcp-flowgate check --config code-review.yamlcheck confirms the skill verbs are in the closed ten, the script verbs are in the closed twelve, every subject starts with a blessed root, every skills: ref resolves, the script executor’s subject resolves into the workflow’s snapshot, the blackboard slots line up with the output: mappings, and every state is reachable. Fix anything it flags before you run.
Step 9 — Run
Section titled “Step 9 — Run”Start the workflow through any MCP host by calling flowgate.command:
{ "definitionId": "code_review", "input": { "candidatePatch": { "diff": "..." } }}The host’s model walks reviewing → critiquing → verifying → done, following the HATEOAS links the gateway returns at each step. Run it this way and one model drives all four states. To actually bind review-agent and critique-agent to different models — each in its own isolated sub-agent session, with per-state timeouts and escalation as safety rails — wire them up via agents.yaml and run under flowgate’s runtime (How to run it covers the trade-offs).
What you skipped
Section titled “What you skipped”This architecture has no retrieval, no verification, no human review gate, no retry budget on failed critique. All of those are one or two states each, and you compose them in by extending the workflow.
For a fuller example with retrieval, constrained editing, deterministic verification, and a human approval gate, see cognitive-architectures/examples/full-swe-pipeline.yaml. Same three layers, more states, more skills, real connections.