In the spirit of Gamma, Helm, Johnson, and Vlissides

Applies toClaude Code v2.1.154+ (dynamic workflows), v2.1.203+ for ultracode effort mode
Pattern count20 patterns, 8 anti-patterns
Last revisedJuly 2026

Preface

The Gang of Four wrote their catalog because object-oriented designers kept reinventing the same solutions and had no shared vocabulary for them. Dynamic workflows are at that same moment. The primitives shipped recently, the community has already converged on a handful of recurring shapes (fan-out/reduce, adversarial verify, loop-until-dry), and those shapes have names in scattered blog posts but no systematic treatment: no stated intent, no applicability criteria, no consequences, no relationships between them.

This catalog fixes that. It documents the recurring, named, composable solutions to orchestration problems in dynamic workflows: scripts that coordinate dozens to hundreds of subagents deterministically while only the final answer reaches the conversation.

Two convictions drive the selection of patterns here, and they mirror the GoF’s own:

  1. The structure is the value, not the agents. Spawning more agents does not produce confidence; arranging them so they check each other does. A verify gate, a judge panel, a fixed-point loop: these are what turn raw parallelism into trustworthy results.
  2. A pattern is a named trade-off, not a recipe. Every pattern in this catalog has a Consequences section that tells you what it costs. Barriers cost wall-clock idle time. Verification gates cost tokens. Worktree isolation costs merge complexity. If a pattern’s costs aren’t worth its benefits for your task, don’t use it, and the catalog tells you what to use instead.

How This Catalog Is Organized

Part I describes the machinery: the runtime, the script API, the determinism contract, and the execution model. Patterns are meaningless without knowing what the medium permits and forbids, so read Part I first even if you have run workflows before. Several patterns exist because of a specific runtime constraint (the journal forbids Date.now(); the script has no filesystem access; there is no mid-run user input), and knowing the constraint explains the pattern.

Part II is the catalog proper: 20 patterns in four families.

  • Structural patterns arrange agents in space: who fans out, who converges, who owns which files.
  • Behavioral patterns arrange work over time: loops, convergence conditions, budgets, gates between stages.
  • Verification patterns manufacture confidence: skeptics, lenses, judges, cross-checks, test gates.
  • Contract patterns govern how data crosses the boundary between deterministic script and stochastic agent: schemas, ledgers, null handling, parameterization.

Part III maps the relationships between patterns and provides a decision guide. Part IV catalogs anti-patterns: recurring mistakes with names, symptoms, and corrections. Appendices hold the API quick reference, a glossary, and sources.

The Pattern Template

Each pattern uses the GoF template, adapted where the medium demands it:

SectionContents
IntentOne or two sentences: what the pattern does and what problem it solves.
Also Known AsOther names in circulation.
MotivationA concrete scenario showing the problem and how the pattern resolves it.
ApplicabilityThe conditions under which to use the pattern.
StructureA Mermaid figure showing the shape.
ParticipantsThe script constructs and agent roles involved, and their responsibilities.
CollaborationsHow the participants interact at runtime.
ConsequencesBenefits, costs, and trade-offs. Numbered, honest.
ImplementationPitfalls, techniques, and runtime-specific concerns.
Sample CodeA working script fragment in the workflow dialect of JavaScript.
Known UsesWhere the pattern appears in shipped or published workflows.
Related PatternsCross-references, including which anti-patterns it degrades into.

A note on the sample code: workflow scripts are plain JavaScript with top-level await, no imports, and no filesystem access. Every sample in this catalog is written in that dialect and follows the runtime’s determinism rules (no Date.now(), no Math.random(), no argless new Date()).


Part I: The Machinery

1.1 What a Dynamic Workflow Is

A dynamic workflow is a JavaScript script that orchestrates subagents at scale. You describe a task; Claude writes the script; a runtime executes it in the background, in an isolated environment separate from the conversation, while the session stays responsive. The script holds the loop, the branching, and every intermediate result in ordinary variables. Only the final return value reaches Claude’s context window.

That last sentence is the entire architectural point. A turn-by-turn agent is both orchestrator and worker: it decides what to do next and does it, and every result lands back in one context window, which caps both scale and objectivity. A workflow splits the roles. The script is the orchestrator: deterministic, inspectable, re-runnable. The agents are the workers: each one a fresh context with one focused job. Determinism in the control flow, model judgment inside each step.

flowchart LR
    subgraph conversation ["Your Claude Code Session"]
        U["You"] --> C["Claude"]
    end
    subgraph runtime ["Workflow Runtime - isolated"]
        S["Orchestration Script<br/>loops · branches · variables"]
        S --> A1["agent"]
        S --> A2["agent"]
        S --> A3["agent"]
        S --> AN["... up to 1,000 per run"]
    end
    C -- "writes script,<br/>launches run" --> S
    S -- "final return value only" --> C
    A1 & A2 & A3 & AN -- "results into<br/>script variables" --> S

Figure 1.1 - The division of labor. Intermediate results never touch the conversation.

Where Workflows Sit Among the Orchestration Options

Claude Code offers four ways to run multi-step work. The distinguishing question is who holds the plan:

SubagentsSkillsAgent teamsWorkflows
What it isA worker Claude spawnsInstructions Claude followsA lead agent supervising peer sessionsA script the runtime executes
Who decides what runs nextClaude, turn by turnClaude, following the promptThe lead agent, turn by turnThe script
Where intermediate results liveClaude’s contextClaude’s contextA shared task listScript variables
What’s repeatableThe worker definitionThe instructionsThe team definitionThe orchestration itself
ScaleA few per turnSame as subagentsA handful of peersDozens to hundreds per run
InterruptionRestarts the turnRestarts the turnTeammates keep runningResumable in-session

Reach for a workflow when the job needs one of three things a single context cannot supply:

  • Breadth - review every file in the diff, audit all dependencies, check every route.
  • Independent verification - findings checked by agents that did not produce them.
  • Scale - migrations, sweeps, anything bigger than one context window can hold.

If the task is ordinary, let one agent do it turn by turn. If you need to weigh in between every step, a workflow is the wrong tool: it accepts no mid-run input (see 1.7 and the Mid-Run Conversation anti-pattern).

1.2 The Participants

These are the actors that appear throughout the catalog. The GoF called this the vocabulary; get these eleven terms straight and every pattern reads easily.

ParticipantWhat it is
ScriptThe JavaScript orchestration program. Owns control flow and state. Has no filesystem or shell access of its own.
RuntimeExecutes the script in isolation, schedules agents against the concurrency cap, journals results, renders progress.
AgentOne subagent spawned by agent(). A fresh context, one prompt, one result. Agents read files, run commands, search the web; the script cannot.
meta blockThe exported literal at the top of the script: name, description, whenToUse, declared phases. Must be a pure literal, no computation.
PhaseA named progress group. Declared in meta.phases, entered via phase() or the per-agent phase: option; visible in /workflows.
JournalThe runtime’s record of every agent() call and result. Enables in-session resume: completed agents return cached results on relaunch.
SchemaA JSON Schema passed to agent(). Forces the agent into a structured-output tool call, validated at the tool-call layer with automatic retry on mismatch.
WorktreeAn isolated git worktree an agent runs in when isolation: 'worktree' is set, so parallel edits never collide.
argsThe input passed when a saved workflow is invoked, available as a global. undefined when launched without input.
budgetThe token target from a +Nk directive: { total, spent(), remaining() }. total is null without a directive, and remaining() is then Infinity.
Sub-workflowAnother workflow invoked inline via workflow(). Shares the parent’s concurrency cap, agent counter, and budget. Nesting is one level deep.

1.3 Architecture

flowchart TD
    subgraph authoring ["Authoring & Launch"]
        P1["ultracode keyword<br/>or natural-language ask"] --> W["Claude writes script"]
        P2["saved command /name"] --> L["Runtime loads script"]
        P3["bundled /deep-research"] --> L
        W --> APR["Approval prompt<br/>phases · raw script · yes/no"]
        APR --> L
    end
    subgraph exec ["Execution"]
        L --> RT["Runtime"]
        RT --> SCH["Scheduler<br/>~16 concurrent, queue overflow"]
        RT --> JRN[("Journal<br/>every agent call + result")]
        SCH --> AG["Agent pool<br/>acceptEdits mode<br/>inherits tool allowlist"]
        AG --> FS["Filesystem / shell / web<br/>via agent tools only"]
        AG -- "worktree isolation<br/>when requested" --> WT["Git worktrees"]
    end
    subgraph observe ["Observation & Control"]
        RT --> UI["/workflows view<br/>phases · agents · tokens · time"]
        UI --> CTRL["pause p · stop x<br/>restart r · save s"]
        RT --> TP["task panel summary line"]
    end
    RT -- "final return" --> DONE["Result lands in session"]
    JRN -. "resume: cached results" .-> SCH

Figure 1.2 - The full system. The script never touches the filesystem; agents do. The journal is what makes a stopped run resumable.

Key architectural facts, each of which shapes at least one pattern in Part II:

  • Every run writes its script to a file under the session’s directory in ~/.claude/projects/. Claude receives the path, so you can read the orchestration, diff it against a prior run’s script, edit it, and relaunch from the edited version. The orchestration is an artifact, not an ephemeral decision.
  • Subagents always run in acceptEdits mode and inherit your tool allowlist regardless of the session’s permission mode. File edits are auto-approved; shell commands, web fetches, and MCP tools outside the allowlist can still prompt mid-run. Pre-approve what the agents need before a long run or the run stalls waiting on a prompt.
  • From Claude’s side, a workflow is a tool. There is a Workflow tool the same way there is a Read or Bash tool. “Running a workflow” means Claude calls that tool with a script; the runtime does the rest in the background.

1.4 Run Lifecycle

sequenceDiagram
    participant U as You
    participant C as Claude (session)
    participant R as Runtime
    participant J as Journal
    participant A as Agents

    U->>C: ultracode: audit every route for missing auth
    C->>C: writes orchestration script
    C->>U: approval prompt (phases, script, options)
    U->>C: Yes, run it
    C->>R: Workflow tool call with script
    activate R
    R->>J: open journal for run
    loop script executes
        R->>A: spawn agent (queue if >16 active)
        A->>A: read files / run commands / search
        A-->>R: result (schema-validated if requested)
        R->>J: record call + result
        R-->>U: progress in /workflows + task panel
    end
    R-->>C: final return value only
    deactivate R
    C-->>U: answer, grounded in the returned result
    Note over U,R: pause p / stop x at any time - resume replays journal,<br/>completed agents return cached results

Figure 1.3 - One run, launch to landing. Note what never happens: intermediate results entering the conversation, and the user being consulted mid-run.

The lifecycle states, for completeness:

stateDiagram-v2
    [*] --> Drafted: Claude writes script
    Drafted --> Approved: user consents (mode-dependent)
    Drafted --> Cancelled: user declines
    Approved --> Running: runtime executes
    Running --> Paused: p in /workflows
    Paused --> Running: resume (journal replays completed agents)
    Running --> Stopped: x, or agent/loop caps hit
    Running --> Completed: script returns
    Completed --> Saved: s in /workflows → .claude/workflows/name.js
    Stopped --> Running: relaunch same script (in-session)
    Completed --> [*]
    Cancelled --> [*]
    note right of Paused: An agent mid-flight when you pause is not journaled and restarts on resume. Many small agents preserve more progress than one long agent.
    note right of Saved: Saved scripts become /name commands and accept structured input via args.

Figure 1.4 - Run states. Resume works within the same session only; exiting Claude Code starts the workflow fresh next session.

1.5 The Script API

Seven constructs do all the work. Everything in Part II composes these.

classDiagram
    class agent {
        +prompt: string
        +schema?: JSONSchema
        +label?: string
        +phase?: string
        +model?: string
        +agentType?: string
        +isolation?: worktree
        returns Promise~object|string|null~
    }
    class parallel {
        +thunks: Array~fn~
        barrier semantics
        failed thunk → null
        returns Promise~any[]~
    }
    class pipeline {
        +items: any[]
        +stages: fn(prev, item, i)...
        no inter-stage barrier
        throwing stage → item null
        returns Promise~any[]~
    }
    class workflow {
        +nameOrRef: string|scriptPath
        +args?: any
        one level of nesting
        shares parent budget+caps
        returns Promise~any~
    }
    class phase {
        +title: string
        starts a progress group
    }
    class log {
        +message: string
        narrator line above tree
    }
    class globals {
        args: any
        budget: total spent remaining
    }
    parallel --> agent : runs thunks of
    pipeline --> agent : stages call
    workflow --> agent : child spawns

Figure 1.5 - The API surface. agent() is the only stochastic element; everything else is plain deterministic JavaScript.

agent(prompt, options?) -> Promise

Spawns one subagent. Without options, resolves to the agent’s final text. Resolves to null if the agent is skipped or dies, which is why every collection pattern in this catalog filters with .filter(Boolean). Options:

OptionEffectWhen to use
schemaJSON Schema. Forces a structured-output tool call, validated at the tool layer with retry on mismatch.Whenever a downstream stage consumes the result. See the Structured Output Contract pattern.
labelDisplay name in /workflows.Always, in fan-outs. review:auth.ts beats forty identical rows.
phaseAssigns the agent to a progress group without touching global phase() state.Inside parallel()/pipeline(), to avoid racing on the global phase.
modelOverrides the model for this one call. Default: inherit session model.Route cheap classification to a smaller model. See Model Routing.
agentTypeUse a custom subagent type instead of the default workflow agent.When a project-defined subagent (with its own prompt and tool set) fits the job.
isolation: 'worktree'Run in an isolated git worktree.Only when agents write files in parallel. See Worktree Isolation.

The CLAUDE_CODE_SUBAGENT_MODEL environment variable overrides both the session default and per-call model options.

parallel(thunks) -> Promise<any[]>

Runs thunks concurrently and awaits all of them: a barrier. A thunk that throws resolves to null rather than rejecting the whole call. You can pass a hundred thunks; roughly your CPU core count run at once (capped near 16) and the rest queue. Reach for it only when a downstream step genuinely needs every result at once. The Barrier Rendezvous pattern covers legitimate uses; Barrier Abuse covers the rest.

pipeline(items, ...stages) -> Promise<any[]>

Runs each item through all stages independently, with no barrier between stages. Item A can be in stage 3 while item B is still in stage 1; fast items finish early and no slot idles waiting for the batch. Each stage callback receives (prevResult, originalItem, index). A throwing stage drops that item to null and the others continue. This is the default for multi-stage work.

workflow(nameOrRef, args?) -> Promise

Runs another workflow inline as a sub-step and returns whatever it returns. Pass a saved name, or { scriptPath } for a script file. The child shares the parent’s concurrency cap, agent counter, and token budget, and appears as its own group in /workflows. Nesting is one level: a workflow() call inside a child throws. See Sub-Workflow Composition.

phase(title), log(message), args, budget

phase() opens a progress group for subsequent agent() calls. log() emits a narrator line above the progress tree; use it at every milestone since it is the only window into a background run besides the tree itself. args carries invocation input verbatim. budget exposes the +Nk token target; guard any budget loop on budget.total because remaining() is Infinity without a target, and note the target is a hard ceiling: agent() throws once it is hit.

1.6 The Determinism Contract

The runtime journals every agent() call so a stopped run can resume with completed agents returning cached results. That cache is only valid if the script, replayed from the top, makes the same calls in the same order. Non-determinism would invalidate it, so the runtime enforces the contract bluntly:

Date.now(), Math.random(), and an argless new Date() all throw inside a workflow script.

The consequences ripple through pattern design:

  • Need a timestamp? Pass it through args, or have an agent resolve “today” via its own tools. Time is data entering the system, not ambient state.
  • Need variety across N agents? Vary the prompt or label by index. Diversity comes from structure, not dice.
  • Need randomness for sampling? Derive it deterministically (hash an input, use the index) or push the sampling into an agent.

The deeper reading: a workflow script is closer to a build file than to application code. Same inputs, same orchestration graph, every time. All the stochasticity in the system lives inside agent() calls, which is exactly where the journal can cache it. Several patterns (Loop-Until-Dry, Seen-Set Ledger) work precisely because the deterministic shell can maintain exact state across stochastic steps.

1.7 Execution Model and Limits

ConstraintValueWhy it existsPattern pressure it creates
Concurrent agents~16, fewer on limited CPUs; excess queuesBounds local resource usePrefer streaming (pipeline) so slots never idle at a barrier
Agents per run1,000 hard capBackstop against runaway loopsLoops need their own convergence condition; see Loop-Until-Dry
Mid-run user inputNone; only agent permission prompts pause a runThe script is the plan; there is no one to askSign-off between stages = separate workflows; see Phase-Gated Staging
Script filesystem/shellNoneSeparation of concerns: script coordinates, agents actAll I/O is delegated; discovery itself is an agent job (Scout-Worker)
Resume scopeSame session onlyJournal is session-scopedFan out across many small agents rather than one long one to preserve progress
Subagent permission modeAlways acceptEdits, inheriting your allowlistUnattended executionPre-approve needed commands or a 200-agent run stalls on one prompt

Cost realities worth internalizing before the patterns, since half the Consequences sections reference them:

  • One run can use meaningfully more tokens than the same task in conversation, and it counts against plan usage and rate limits like any other session.
  • Claude Code flags runs that schedule more than 25 agents or project past 1.5M tokens with a Large workflow warning (advisory only; a /config size guideline of small/medium/large replaces the 25-agent threshold).
  • Gauge spend by running on a slice first: one directory, one narrow question. /workflows shows per-agent token totals live, and stopping a run loses nothing already completed.
  • Prompt-cache TTL matters on long runs: for workflows whose phases are separated by more than a few minutes, the default 5-minute cache TTL has been reported to cost 3-6x more than a 1-hour TTL.

1.8 Triggering, Saving, Composing

Four routes into a run:

  1. The ultracode keyword (or asking for “a workflow” in your own words) runs that single task as a workflow at the session’s current effort level. The keyword is an opt-in only in prompts you type yourself; as of v2.1.210 it does not trigger from -p prompts, un-stamped SDK input, scheduled tasks, or webhook/PR-comment relays - a meaningful injection-surface reduction for anyone wiring Claude Code into CI.
  2. /effort ultracode combines xhigh reasoning with automatic orchestration: Claude plans a workflow for every substantive task, sometimes several in a row (understand -> change -> verify). Session-scoped; drop back with /effort high.
  3. Bundled commands: /deep-research <question> ships in the box and is the reference implementation for half the verification patterns in this catalog.
  4. Saved commands: after a good run, press s in /workflows to write the script to .claude/workflows/ (repo-shared, version-controlled, monorepo-aware as of v2.1.178) or ~/.claude/workflows/ (personal; honors CLAUDE_CONFIG_DIR). Project beats personal on a name collision. Saved workflows accept structured input through args. See Parameterized Command.

1.9 Reading This Catalog Against Your Own Work

A workflow’s script is version-controllable, reviewable, and diffable. For a team, that means orchestration patterns can be governed the way code is: a pr-review workflow in the repo is a review policy everyone runs identically; an auth-check workflow is a release gate. The catalog’s patterns are therefore not just implementation techniques but candidate organizational standards. Where a pattern has that character, its Known Uses section says so.


Part II: The Pattern Catalog

Catalog Summary

#PatternFamilyIntent in one line
1Fan-Out / Reduce / SynthesizeStructuralDecompose into independent slices, merge deterministically, compose one answer
2Streaming PipelineStructuralPush each item through multi-stage processing with no inter-stage barrier
3Barrier RendezvousStructuralSynchronize a full result set before a stage that needs all of it
4Scout-WorkerStructuralLet one agent discover the work items; fan workers across the discovered set
5Sub-Workflow CompositionStructuralInvoke a saved workflow as one step of a larger one
6Worktree IsolationStructuralGive each writing agent its own git worktree so parallel edits never collide
7Loop-Until-DryBehavioralIterate discovery until consecutive rounds find nothing new
8Converge-Until-GreenBehavioralAlternate check and fix until the check passes or progress stalls
9Budget-Scaled DepthBehavioralLet a token target, not a fixed plan, decide how deep the run goes
10Phase-Gated StagingBehavioralSplit work needing human sign-off into separate workflows per stage
11Model RoutingBehavioralAssign each stage the cheapest model that can do its job
12Adversarial VerifyVerificationAttack each finding with independent skeptics before reporting it
13Perspective-Diverse VerifyVerificationVerify through distinct lenses instead of identical redundant checks
14Judge PanelVerificationGenerate competing attempts, score them, synthesize from the winner
15Cross-Checked ClaimsVerificationExtract atomic claims and confirm each against independent sources
16Test GateVerificationFollow every mutation with an executable check before accepting it
17Structured Output ContractContractBind agent output to a JSON Schema validated with automatic retry
18Seen-Set LedgerContractKeep an exact deterministic record of everything encountered across rounds
19Null-Tolerant CollectionContractTreat individual agent death as data loss, not run failure
20Parameterized CommandContractExpose a saved workflow as a reusable command taking structured input

Family I: Structural Patterns

Structural patterns arrange agents in space. They answer: how many agents, over what items, converging where, writing to what.


Pattern 1: Fan-Out / Reduce / Synthesize

Family: Structural Also Known As: Map-Reduce-Compose, Scatter-Gather-Write

Intent

Decompose a breadth problem into independent slices, run one agent per slice concurrently, merge their structured results with deterministic code, and delegate the final composition to a single synthesis agent that sees only the merged summary.

Motivation

Consider producing a weekly ecosystem digest: nine sources to check (release feeds, official blogs, Hacker News, Reddit, key maintainers, podcasts), then one coherent newsletter. A single agent doing this serially holds every half-read source in one context; by source six it is summarizing its own earlier summaries and the context is polluted with raw HTML it no longer needs. The task has an obvious seam: each source is independent. So: one researcher per source, all at once, each returning schema-validated items. A dozen lines of plain JavaScript flatten and dedupe the results. One curator ranks them. One writer composes the newsletter from the curated list, never seeing a single raw source.

This is the foundational shape of the medium. Nearly every other structural and verification pattern is a refinement of one of its three legs: refine the fan-out and you get Scout-Worker; refine the reduce and you get Seen-Set Ledger; insert a gate before synthesis and you get Adversarial Verify.

Applicability

Use Fan-Out / Reduce / Synthesize when:

  • The problem decomposes into slices with no data dependencies between them (sources, files, modules, dimensions, angles).
  • Coverage matters: you want a guarantee that every slice was examined exactly once, which the script provides and a turn-by-turn agent cannot (“the model decided to skip three files” is not a failure mode a .map() has).
  • The final artifact is a synthesis over all slices, not a per-slice result.

Do not use it when the slices feed each other (use Streaming Pipeline stages instead) or when there is only a handful of trivial slices (a single agent is cheaper).

Structure
flowchart LR
    IN["slice list<br/>(sources, files, angles)"] --> F{{"fan out<br/>one agent per slice"}}
    F --> A1["worker 1"]
    F --> A2["worker 2"]
    F --> A3["worker ..."]
    F --> AN["worker N"]
    A1 & A2 & A3 & AN --> R["reduce<br/>plain JS: flatten · dedupe · filter · rank"]
    R --> S["synthesize<br/>one agent, sees only the reduction"]
    S --> OUT["final artifact"]

Figure 2.1 - The three legs. Only the middle leg is deterministic code; it is also the only leg that sees everything.

Participants
  • Slice list - plain data in the script: an array of sources, files, or prompts. Owning it in code is what makes coverage provable.
  • Workers - one agent() per slice, each with a schema so the reduce step consumes objects, not prose. Labeled per-slice for the progress view.
  • Reducer - ordinary JavaScript. Flattening, deduplication, filtering, sorting need no model and should not pay for one.
  • Synthesizer - a single closing agent() whose prompt embeds the reduced data (JSON.stringify) and whose job is composition, not research.
Collaborations

The script maps the slice list into thunks and awaits them with parallel() (a legitimate barrier: the reduce needs every slice; see Barrier Rendezvous). Failed workers arrive as null and are dropped by the reducer (Null-Tolerant Collection). The reducer’s output is small enough to inline into the synthesizer’s prompt; the synthesizer returns the artifact, which the script returns to the session.

Consequences
  1. Coverage becomes a property of the code, not the model’s diligence. One agent per slice, enforced by a .map(). This is the pattern’s central guarantee.
  2. Context stays clean at every tier. Workers see one source; the synthesizer sees a curated summary; the conversation sees only the artifact.
  3. Wall-clock time approaches the slowest slice, not the sum: nine sources in about three minutes in the published newsletter example, versus a serial crawl.
  4. Cost multiplies by slice count. Nine researchers cost roughly nine researchers. The reduce leg is free; keep as much work there as possible.
  5. The reduce leg is a quality bottleneck. Dedup keys, ranking heuristics, and filters are your code; a sloppy key merges distinct findings or duplicates identical ones. Budget design attention accordingly.
  6. The synthesizer inherits garbage silently if workers are allowed to return prose. Pair with Structured Output Contract always.
Implementation
  • Keep the slice list as data (an array of { key, prompt } objects), not as unrolled code. It documents the fan-out, feeds labels, and makes the next slice a one-line addition.
  • Give every worker a distinct label and a phase: option; forty rows named agent is an undebuggable progress view.
  • Do the reduce in the script even when it feels like judgment work. Deduplication by key, impact sorting, and empty-filtering are deterministic. Reserve an agent (a “curator”) only for genuinely judgment-laden reduction, and then it becomes a fourth leg between reduce and synthesize.
  • Inline the reduced data into the synthesizer prompt with JSON.stringify(data, null, 2); the schema’d shape survives the round trip.
Sample Code
export const meta = {
  name: 'ecosystem-digest',
  description: 'Research sources in parallel, curate, and write a digest',
  phases: [{ title: 'Research' }, { title: 'Curate' }, { title: 'Write' }],
}

const SOURCES = [
  { key: 'releases', prompt: 'Find releases this week in repos X, Y, Z...' },
  { key: 'blog',     prompt: 'Check the official blog for posts this week...' },
  { key: 'hn',       prompt: 'Search Hacker News for relevant stories this week...' },
  // ...one entry per source; adding a source is adding a line
]

const ITEMS = { type: 'object', required: ['items'], properties: { items: {
  type: 'array', items: { type: 'object',
    required: ['title', 'url', 'impact'],
    properties: { title: { type: 'string' }, url: { type: 'string' },
                  impact: { enum: ['high', 'medium', 'low'] } } } } } }

phase('Research')
const raw = await parallel(SOURCES.map((s) => () =>
  agent(s.prompt, { label: `research:${s.key}`, phase: 'Research', schema: ITEMS })))

// Reduce: deterministic, free, and the only place that sees everything.
const flat = raw.filter(Boolean).flatMap((r) => r.items)
const deduped = [...new Map(flat.map((i) => [i.url, i])).values()]
log(`${flat.length} raw items → ${deduped.length} after dedupe`)

phase('Write')
return await agent(
  `Write the weekly digest from these curated items:\n${JSON.stringify(deduped, null, 2)}`,
  { phase: 'Write' })
Known Uses
  • The published vue-newsletter workflow: nine research agents -> JS dedupe -> curate agent -> write agent, seventeen items across nine sources in about three minutes.
  • The bundled /deep-research is this skeleton with a verification gate inserted before synthesis (see Cross-Checked Claims).
  • Codebase mapping (“summarize each subsystem, then draw the architecture”), onboarding-guide generation, incident RCA (independent investigation angles -> one root-cause synthesis).

Scout-Worker generates the slice list when it isn’t known upfront. Barrier Rendezvous justifies the one barrier this pattern contains. Structured Output Contract is effectively mandatory on the workers. Insert Adversarial Verify between reduce and synthesize when findings must be trusted. Degrades into Context Flood if the reduce leg is skipped and raw worker output is returned.


Pattern 2: Streaming Pipeline

Family: Structural Also Known As: Per-Item Stages, Assembly Line

Intent

Run each work item through a sequence of stages independently, so an item advances the moment its previous stage finishes, no slot idles waiting for the slowest member of a batch, and one item’s failure never blocks the rest.

Motivation

A migration touches 200 files: each needs a rewrite, then a verification. The batch formulation - rewrite all 200 behind a barrier, then verify all 200 - leaves fifteen agent slots idle while the one pathological 2,000-line file finishes its rewrite, and delays the first verification until the last rewrite completes. But file 1’s verification depends only on file 1’s rewrite. pipeline(files, migrate, verify) expresses exactly that: file A can be in verification while file B is still being rewritten. Fast files finish early; the pathological file inconveniences only itself.

This is the workflow analogue of instruction pipelining, and it is the reason the community guidance converged on a blunt rule: default to pipeline(); reach for parallel() only when a stage genuinely needs all prior results at once.

Applicability

Use Streaming Pipeline when:

  • Items are processed through two or more stages and each item’s stage N depends only on that item’s stage N-1.
  • Item processing times vary (they always do: files differ in size, sources differ in depth), which is precisely when barrier idle time is worst.
  • Partial failure should be partial: a throwing stage drops that item to null and the line keeps moving.

Do not use it for single-stage fan-outs (plain parallel() is equivalent and simpler) or when a stage needs cross-item visibility (that is a Barrier Rendezvous).

Structure
flowchart TD
    subgraph barrier ["parallel + parallel - the batch shape"]
        direction LR
        b1["A: stage 1"] --> W["BARRIER<br/>everything waits<br/>for the slowest"] --> b4["A: stage 2"]
        b2["B: stage 1"] --> W --> b5["B: stage 2"]
        b3["C: stage 1 (slow)"] --> W --> b6["C: stage 2"]
    end
    subgraph stream ["pipeline - the streaming shape"]
        direction LR
        s1["A: stage 1"] --> s2["A: stage 2"] --> s3["A done early"]
        s4["B: stage 1"] --> s5["B: stage 2"] --> s6["B done early"]
        s7["C: stage 1 (slow)"] --> s8["C: stage 2"] --> s9["C done late - alone"]
    end

Figure 2.2 - The same work, two shapes. In the barrier shape, A and B’s stage 2 waits on C’s stage 1. In the stream, only C pays for C.

Participants
  • Item list - the units of work, discovered upfront or by a Scout-Worker first phase.
  • Stages - functions (prevResult, originalItem, index) => ..., each typically wrapping one agent() call. The signature’s originalItem means later stages can reference the item without threading it through every intermediate result, though carrying an accumulating object ({ ...prev, verified }) is the cleaner habit.
  • The line - pipeline() itself, scheduling each item’s next stage as soon as its previous one resolves, within the global concurrency cap.
Collaborations

pipeline() interleaves stage executions across items; the /workflows view still groups them by the phase: option on each agent() call, so declare the phase per-agent rather than with global phase() calls (which race across concurrently-processing items). Items that fail a stage surface as null in the result array, preserving positional correspondence with the input list.

Consequences
  1. Throughput. Zero inter-stage idle. On heterogeneous items this is the difference between “wall clock ~ slowest item’s total path” and “wall clock ~ slowest stage of every batch, summed.”
  2. Failure isolation. One item’s exception is one null, not a stalled run.
  3. Early signal. The first verified migration exists minutes into the run, not after the last rewrite; you can eyeball early results in /workflows and stop a misbehaving run before it spends the budget.
  4. No cross-item visibility. By design a stage sees one item. Deduplication across items, “compare against the other findings,” or early-exit-on-total all require a barrier; that is the boundary with Barrier Rendezvous.
  5. Conditional stages need in-stage branching. A stage that shouldn’t run for some items (don’t remove code that verification kept) is expressed as a ternary inside the stage returning a passthrough object, which is slightly less obvious than a filtered second batch.
Implementation
  • Accumulate: have each stage return { ...prev, newFields } so the final array carries each item’s full history.
  • Phase-per-agent, not global phase(), inside the stages.
  • To skip a stage per-item, branch inside the stage and return a synthetic result for the skip path (see the dead-code sample below); never pre-filter between stages, which reintroduces a barrier.
  • If a stage fans out further (one review produces N findings, each needing its own verification), nest a parallel() inside the stage: the inner barrier scopes to one item and does not block the line.
Sample Code
// Dead-code removal: verify each candidate is truly unused, then remove -
// with an in-stage branch so "keep" items pass through unharmed.
const result = await pipeline(
  candidates,
  (c) => agent(
    `Confirm ${c.name} in ${c.file} has ZERO references anywhere - including ` +
    `dynamic imports, string keys, and re-exports. Default unused=false on any doubt.`,
    { label: `verify:${c.name}`, phase: 'Verify', schema: VERDICT })
    .then((v) => ({ ...c, unused: v?.unused })),
  (c) => c.unused
    ? agent(`Remove the unused export ${c.name} from ${c.file} and any orphaned imports.`,
        { label: `remove:${c.name}`, phase: 'Remove',
          isolation: 'worktree', schema: DONE })
        .then((d) => ({ ...c, removed: d?.removed }))
    : { ...c, removed: false }        // pass-through: no agent, no barrier
)
return result.filter(Boolean).filter((e) => e.removed)
Known Uses
  • Framework migrations and language ports (migrate -> test per module, the shape credited in the Bun Zig->Rust effort’s module-by-module port with a test gate per unit).
  • Security audits where each dimension’s findings flow straight into verification without waiting for the other dimensions.
  • Docs-drift checks: each doc file checked against code independently.

The default alternative to Barrier Rendezvous; the “smell test” between them: parallel -> transform -> parallel where the transform has no cross-item dependency should have been one pipeline. Stages typically embed Structured Output Contract and, for writing stages, Worktree Isolation. Test Gate is most naturally a pipeline stage. Ignoring this pattern is the Barrier Abuse anti-pattern.


Pattern 3: Barrier Rendezvous

Family: Structural Also Known As: Synchronization Point, Gather

Intent

Deliberately synchronize on a complete result set before a downstream step that requires cross-item visibility, accepting the idle time as the price of a computation that is meaningless on partial data.

Motivation

A curation step cannot dedupe “the same release surfaced by four sources” until all four sources report. A triage step cannot decide “zero findings, skip verification entirely” until the finding count is final. A comparison prompt (“rank these plans against each other”) is undefined over a partial set. These steps have a genuine all-items dependency, and for them the barrier that parallel() imposes is not waste; it is the semantics.

The pattern exists in this catalog mostly to draw its own boundary. Barriers are the most over-used construct in early workflow scripts, because batch thinking is the habit sequential programming installs. The discipline: every parallel() between stages must be justifiable by naming the cross-item computation it protects.

Applicability

Use a barrier when the next step:

  • Merges or dedupes across the full set (curation over all sources; the reduce leg of Pattern 1).
  • Branches on an aggregate (early exit on zero findings; escalate if more than K criticals).
  • Compares items against each other (judge panels, “rank these,” cross-item conflict detection).

Three justifications; that is the complete list. “I need to flatten first” (do it in a stage), “the stages feel conceptually separate” (separate != synchronized), and “it’s cleaner code” (barrier latency is real wall-clock waste) do not qualify.

Structure
flowchart LR
    A1["worker 1"] --> B{{"BARRIER<br/>await all"}}
    A2["worker 2"] --> B
    A3["worker N"] --> B
    B --> Q{"aggregate<br/>question"}
    Q -- "empty set" --> EXIT["early exit -<br/>skip downstream cost"]
    Q -- "items" --> X["cross-item step:<br/>dedupe · rank · compare"]

Figure 2.3 - A justified barrier: the downstream step is a function of the whole set, and the aggregate enables an early exit that a stream could never take.

Participants
  • Workers - the upstream fan-out.
  • The barrier - parallel(thunks), resolving only when every thunk has resolved or died to null.
  • The aggregate consumer - either deterministic reduce code or a cross-item agent (curator, judge, ranker).
Collaborations

The barrier hands the consumer a positionally complete array (with null holes for casualties; filter first). When the consumer is deterministic code, the barrier-to-consumer handoff is free; when it is an agent, the whole set rides into one prompt, so the set must fit: reduce deterministically first, then hand the summary to the agent.

Consequences
  1. Correctness for aggregate computations. The only shape in which they are well-defined.
  2. Early-exit economics. Aggregates enable skipping entire downstream phases (“0 findings -> no verification”), which a stream, committed item-by-item, cannot do. On clean codebases this can be the single largest cost saver in a script.
  3. Idle time equal to the spread of worker durations. Every slot waits for the slowest worker. Minimize by making barrier-feeding workers uniform in scope.
  4. Latency of first downstream result equals the last upstream result. Where Pattern 2 delivers early signal, a barrier delivers nothing until everything.
  5. Barrier placement is a design decision that compounds. Two barriers in sequence serialize the run into batches; audit any script with more than one or two.
Implementation
  • filter(Boolean) immediately after the barrier, before anything indexes into results.
  • If the barrier exists only to count or flatten, and the count feeds no branch, delete it and use a pipeline stage.
  • Scope barriers tightly: an inner parallel() nested within a pipeline stage barriers one item’s sub-tasks (three skeptic votes on one finding) without synchronizing the line. Most “I need a barrier” intuitions are satisfied by this per-item scope.
Sample Code
phase('Investigate')
const leads = await parallel(ANGLES.map((p, i) => () =>
  agent(p, { label: `angle-${i + 1}`, phase: 'Investigate', schema: LEAD })))

const found = leads.filter(Boolean)
if (!found.length) {                      // aggregate branch: the barrier's payoff
  log('No leads from any angle - stopping without synthesis')
  return { rootCause: null, leads: [] }
}

phase('Synthesize')                        // cross-item consumer: needs all leads
return await agent(
  `Given these independent leads, what is the single most likely root cause? ` +
  `Rank them and propose the next verification step.\n${JSON.stringify(found, null, 2)}`)
Known Uses
  • The reduce leg of every Fan-Out / Reduce / Synthesize instance, including the newsletter’s curation barrier (“curation has to see every source before it can dedupe and rank across them” - the published example’s own justification).
  • Judge panels (Pattern 14), which are cross-item by definition.
  • Per-finding skeptic votes: an inner barrier over 3 verifiers, nested inside an outer per-finding fan-out.

The disciplined complement of Streaming Pipeline. Its abuse is the Barrier Abuse anti-pattern, the most common structural mistake in the medium. Judge Panel and the reduce leg of Fan-Out / Reduce / Synthesize are its two canonical clients.


Pattern 4: Scout-Worker

Family: Structural Also Known As: Discover-Then-Process, Enumerate-and-Fan

Intent

When the work items are not known at script-writing time, dedicate a first agent to discovering the item list as structured data, then drive the fan-out from that list, keeping coverage provable over a set the script could not have named in advance.

Motivation

“Audit every route handler for missing auth checks” - but the script author does not know the routes. Hard-coding a file list goes stale the day someone adds an endpoint. Asking each worker to “find and check some routes” surrenders the coverage guarantee that justified using a workflow at all: workers overlap, miss, and double-count. The resolution is a two-beat structure: a scout agent enumerates (List every HTTP route handler under src/routes/; report method, path, file) against a schema, and the script fans one checker per discovered route. The enumeration is stochastic exactly once, converted immediately into deterministic data the script owns.

The official documentation’s canonical ultracode example (“audit every API endpoint under src/routes/ for missing auth checks”) is this pattern, and nearly every recipe in the community library opens with it: list the dependencies, list the modules, list the docs, list the pages, list the commits - then fan.

Applicability

Use Scout-Worker when:

  • The item set is a property of the current state of the repo, the CI history, or the world, not of the task description.
  • Coverage over the discovered set matters (audits, migrations, backfills).
  • Discovery is meaningfully cheaper than processing, so serializing one scout before the fan costs little.

When items are known and stable, skip the scout: a literal array is more inspectable. When discovery itself is open-ended (bugs, not files), you need Loop-Until-Dry, which is iterated scouting with a convergence condition.

Structure
flowchart TD
    S["SCOUT<br/>one agent, schema'd:<br/>'list every X, report fields A, B'"] --> D[("discovered item list<br/>now deterministic script data")]
    D --> F{{"fan out: one worker per item"}}
    F --> W1["worker: item 1"]
    F --> W2["worker: item 2"]
    F --> WN["worker: item N"]
    W1 & W2 & WN --> R["collect / reduce"]

Figure 2.4 - One stochastic enumeration, frozen into data, then a provably-covering fan.

Participants
  • Scout - a single agent() whose entire job is enumeration, bound by a schema whose fields are exactly what workers need (file always; method/path/line as the task requires). Its prompt defines the universe: what counts as “a route,” “a dependency,” “a page.”
  • Discovered list - the schema-validated array. From this point it is ordinary data: the script can slice it for a trial run, log its size, or branch on it.
  • Workers - one per item, as in Pattern 1 or 2.
Collaborations

The scout runs alone (a trivially justified serialization: nothing to fan over yet). log(${items.length} found) immediately after is near-mandatory: it is the run’s first sanity check, and a scout that returns 0 or 4,000 items should be visible before the fan spends anything. The list then feeds either a parallel() (single-stage checks) or a pipeline() (multi-stage processing).

Consequences
  1. Freshness. The run audits the repo as it is, not as it was when the script was saved. A Scout-Worker script in .claude/workflows/ stays correct as the codebase grows.
  2. Provable coverage over an unknown set. The fan is a .map() over real data; every discovered item gets exactly one worker.
  3. The scout is a single point of failure for coverage. Items the scout misses are silently un-audited, and nothing downstream will notice. Mitigations: a tight universe definition in the prompt, a second scout with a different strategy union-merged, or full Loop-Until-Dry when completeness is critical.
  4. The scout’s schema is the workers’ interface. Underspecify it (no line field) and workers waste tokens re-finding what the scout knew; overspecify and the scout hallucinates fields to satisfy the schema. Match fields to genuine worker needs.
  5. One serial step. Rarely material; scouts are cheap relative to fans.
Implementation
  • Parameterize the universe through args (const dir = args?.dir || 'src/routes/') so the saved command targets any directory. See Parameterized Command.
  • For trial economics, slice: const targets = args?.limit ? items.slice(0, args.limit) : items gives a --limit-style dry run on a big repo.
  • Consider a model: downgrade on the scout: enumeration is usually easier than analysis. See Model Routing.
  • If two scouts with different strategies (grep-driven vs. framework-aware) are cheap, union their outputs by key before fanning; disagreement between them is itself signal.
Sample Code
export const meta = {
  name: 'auth-check',
  description: 'Audit every route handler for a missing authorization check',
  phases: [{ title: 'Discover' }, { title: 'Check' }],
}

const ROUTES = { type: 'object', required: ['routes'], properties: { routes: {
  type: 'array', items: { type: 'object', required: ['path', 'file'],
    properties: { method: { type: 'string' }, path: { type: 'string' },
                  file: { type: 'string' } } } } } }
const CHECK = { type: 'object', required: ['guarded'], properties: {
  guarded: { type: 'boolean' }, guard: { type: 'string' }, risk: { type: 'string' } } }

phase('Discover')
const dir = (args && args.dir) || 'src/routes/'
const { routes } = await agent(
  `List every HTTP route handler under ${dir}. Report method, path, and file for each.`,
  { schema: ROUTES })
log(`${routes.length} handlers found`)

phase('Check')
const checked = await parallel(routes.map((r) => () =>
  agent(`Does the handler for ${r.method || ''} ${r.path} in ${r.file} enforce ` +
        `authentication AND authorization before doing work? Name the guard if present.`,
    { label: `check:${r.path}`, phase: 'Check', schema: CHECK })
    .then((c) => ({ ...r, ...c }))))
return checked.filter(Boolean).filter((r) => !r.guarded)
Known Uses
  • The docs’ canonical auth audit; dependency risk audits (enumerate manifest -> assess each); accessibility audits (list pages -> audit each); changelog builders (list commits since tag -> classify each); i18n extraction (list view files -> extract per file).
  • Any migration’s Discover phase (Pattern 2’s known uses all begin here).

Feeds Fan-Out / Reduce / Synthesize and Streaming Pipeline. Iterated with a convergence condition it becomes Loop-Until-Dry. The scout’s output is a Structured Output Contract; its parameterization is Parameterized Command.


Pattern 5: Sub-Workflow Composition

Family: Structural Also Known As: Workflow-as-Subroutine, Nested Orchestration

Intent

Invoke an existing saved or bundled workflow as one step of a larger one, reusing a proven orchestration (with its internal phases, fan-outs, and verification) instead of re-implementing it inline.

Motivation

A competitive-analysis workflow needs, for each of three competitors, a cross-checked research pass. That research pass already exists: /deep-research ships with the product and carries a five-phase verify pipeline you would be foolish to re-derive. workflow('deep-research', { question }) runs it inline and returns the cited report to the parent script as an ordinary awaited value. The parent composes; the child executes its own proven internals; the /workflows view shows the child as its own group.

This is the medium’s subroutine call, and it changes what “saving a workflow” means: a saved workflow is not just a command for humans but a library function for other workflows.

Applicability

Use Sub-Workflow Composition when:

  • A saved or bundled workflow already implements a stage of your task, especially one with nontrivial internal quality machinery.
  • You are building a family of workflows and want shared stages (a standard verify pass, a standard research pass) maintained in one place.
  • A large orchestration reads better as “call these three named things” than as 400 inline lines.

Constraints bound the pattern hard: nesting is one level deep (a workflow() call inside a child throws), and the child shares the parent’s concurrency cap, agent counter, and token budget, so composition does not multiply capacity: three /deep-research children still fit inside one 1,000-agent, one-budget run.

Structure
flowchart TD
    subgraph parent ["Parent workflow"]
        P1["own phase: scope targets"] --> C1
        subgraph children ["workflow() calls - one level only"]
            C1["child: deep-research Q1"]
            C2["child: deep-research Q2"]
            C3["child: saved verify pass"]
        end
        C1 & C2 & C3 --> P2["own phase: compare & synthesize"]
    end
    LIB[(".claude/workflows/<br/>bundled + saved scripts")] -. "resolved by name<br/>or scriptPath" .-> children
    CAP["shared: concurrency cap ·<br/>agent counter · token budget"] -.-> parent

Figure 2.5 - Composition without multiplication: children draw on the parent’s caps and budget.

Participants
  • Parent script - owns the composition: which children, with what args, in what arrangement (children are thunk-able, so they fan with parallel() like any agent).
  • Child workflow - a saved name ('deep-research') or { scriptPath }. Executes its own meta, phases, and internals; returns its return value.
  • Shared envelope - the cap/counter/budget the runtime enforces across the whole tree.
Collaborations

The parent awaits workflow(name, args) exactly as it awaits agent(); the child’s args global receives the second parameter, which is the composition interface (see Parameterized Command: a workflow designed for composition should document its args shape in meta.whenToUse). Child progress renders as a distinct group in /workflows, preserving drill-down.

Consequences
  1. Reuse of quality machinery. The strongest argument: /deep-research’s adversarial verification comes along for free, and a team’s standard pr-review stage is maintained once.
  2. Legibility. A parent that reads scope -> research x3 -> compare is auditable at a glance.
  3. Versioning coupling. The parent depends on the child’s args contract and return shape; a saved child edited incompatibly breaks parents silently. Treat saved workflow interfaces with the same discipline as function signatures in a shared library; the project-vs-personal save location (1.8) becomes an API-ownership question.
  4. No capacity multiplication, by design. Budget a parent as the sum of its children. Three deep-research children at ~30 agents each is ~90 agents of one budget.
  5. One level only. Deep hierarchies are off the table; if a child needs its own children, restructure so the parent calls both layers directly.
Implementation
  • Fan children with thunks exactly like agents: await parallel(questions.map((q) => () => workflow('deep-research', { question: q }))).
  • Prefer names over scriptPath for anything shared; paths pin machines.
  • When designing a workflow for composition, return structured data, not prose: parents parse returns.
Sample Code
export const meta = {
  name: 'competitor-scan',
  description: 'Deep-research each competitor, then compare approaches',
  phases: [{ title: 'Research' }, { title: 'Compare' }],
}

const competitors = (args && args.names) || ['CompetitorA', 'CompetitorB', 'CompetitorC']
const topic = (args && args.topic) || 'rate limiting'

phase('Research')
const reports = await parallel(competitors.map((c) => () =>
  workflow('deep-research', { question: `How does ${c} handle ${topic}?` })))

phase('Compare')
return await agent(
  `Compare these researched approaches to ${topic}. Identify the strongest design ` +
  `and what each gets wrong.\n\n${reports.filter(Boolean).join('\n\n---\n\n')}`)
Known Uses
  • Research workflows delegating sub-questions to the bundled /deep-research instead of re-implementing its fan-out (the documented motivating case for workflow()).
  • Team-standard stages: a saved review or verify workflow invoked by several task-specific parents.

Parameterized Command defines the child’s interface. Phase-Gated Staging is the alternative when a human must approve between what would otherwise be parent-child stages. The one-level limit is what keeps this pattern from becoming a Monolithic Agent in workflow clothing at the meta level.


Pattern 6: Worktree Isolation

Family: Structural Also Known As: Isolated Copies, Parallel-Write Sandboxing

Intent

Give each file-writing agent its own git worktree so that concurrent edits proceed without colliding in the working directory, converting an unsafe parallel mutation into N safe serialized-per-tree mutations.

Motivation

Sixteen agents concurrently editing files in one working directory is a race: two agents touching the same file, or one agent’s build artifacts confusing another’s test run, produce corruption no verification stage can reliably catch. Serializing the writes forfeits the parallelism that justified the workflow. Git worktrees resolve the tension: isolation: 'worktree' on an agent() call runs that agent in its own checkout, where its edits are invisible to its peers. The documentation’s migration example is explicit about the shape: “transform each one in an isolated copy so edits don’t conflict.”

Applicability

Use Worktree Isolation when - and only when - agents write files in parallel and would otherwise conflict. The published guidance is deliberately restrictive: read-only agents never need it; a single writing agent never needs it; sequential writers never need it. Each worktree has real setup cost and, more importantly, real merge consequences downstream.

Structure
flowchart TD
    REPO[("main working tree")] --> WT1["worktree A<br/>agent migrates file A"]
    REPO --> WT2["worktree B<br/>agent migrates file B"]
    REPO --> WT3["worktree N<br/>agent migrates file N"]
    WT1 --> V1["verify in-tree"]
    WT2 --> V2["verify in-tree"]
    WT3 --> V3["verify in-tree"]
    V1 & V2 & V3 --> M{"integration:<br/>merge surviving edits"}
    M --> DONE["main tree advanced"]
    M -- "failed verification" --> DROP["edit discarded -<br/>main tree untouched"]

Figure 2.6 - Isolation buys safe parallelism and free rollback: a failed edit simply never merges.

Participants
  • Writing agents - each with isolation: 'worktree', editing “their” files in a private checkout.
  • Per-tree verifiers - verification stages (compile, test) that run against the same worktree, which is the point: they check the edit in the context of the edit.
  • Integration - the step that brings surviving edits back to the main tree.
Collaborations

Pairing with Streaming Pipeline is canonical: pipeline(files, migrateInWorktree, verifyResult) gives each file its own tree, its own verification, its own fate. Pairing with Test Gate per tree means a failing file is discarded, not committed: rollback is the default state, not a recovery procedure.

Consequences
  1. Race-free parallel mutation. The only safe way to run concurrent writers.
  2. Per-item rollback for free. An edit that fails its gate never touches the main tree.
  3. Merge is now your problem. N surviving worktrees must integrate. Per-file edits (each agent owns disjoint files) merge trivially; overlapping edits (two agents touching a shared import barrel) reintroduce conflicts at merge time that isolation only deferred. Partition the work by file ownership when possible.
  4. Cross-file consistency is deferred. An agent renaming a symbol in its file cannot see that another agent’s file references it; integration needs a whole-tree verification pass (compile/test the merged result) as a final phase.
  5. Setup overhead per tree. Immaterial for substantial edits, material for hundreds of one-line changes; batch small related edits into one agent-and-tree.
Implementation
  • Partition items so each agent’s write set is disjoint. The pattern removes physical races; only partitioning removes logical ones.
  • Always close with a whole-tree gate after integration: agent('Run the full build and test suite; report pass/fail', ...) as a final phase.
  • Flag judgment-required edits instead of forcing them: a needsReview: boolean in the write schema routes non-mechanical cases to a human list rather than merging a guess (the api-deprecation recipe’s approach).
Sample Code
const done = await pipeline(
  files,
  (f) => agent(
    `Migrate ${f} from the old router API to the new one. Keep behavior identical.`,
    { label: `migrate:${f}`, phase: 'Migrate',
      isolation: 'worktree', schema: RESULT })
    .then((r) => ({ file: f, ...r })),
  (r, file) => agent(
    `Verify the migration of ${file} compiles and preserves behavior. Report pass/fail.`,
    { label: `verify:${file}`, phase: 'Verify', schema: RESULT })
    .then((v) => ({ ...r, verified: v?.migrated }))
)
const surviving = done.filter(Boolean).filter((d) => d.verified)

phase('Integrate')
const final = await agent(
  `The per-file migrations are merged. Run the full build and test suite; ` +
  `report pass/fail and any cross-file breakage.`, { schema: RESULT })
return { migrated: surviving, wholeTree: final }
Known Uses
  • The documentation’s migration example (styled-components -> Tailwind, “each file in its own isolated copy”); framework migrations, language ports, project-wide renames, docstring backfills, and deprecation rollouts in the community recipe library all set isolation: 'worktree' on their write stages.

Almost always inside a Streaming Pipeline with a Test Gate stage. The needsReview escape hatch connects to Phase-Gated Staging (human review as a separate stage). Omitting it for parallel writes is not a named anti-pattern because it is not a pattern at all; it is a race.


Family II: Behavioral Patterns

Behavioral patterns arrange work over time. They answer: when to iterate, when to stop, how deep to go, where a human belongs, and which model does what.


Pattern 7: Loop-Until-Dry

Family: Behavioral Also Known As: Fixed-Point Discovery, Exhaustive Sweep

Intent

For discovery problems whose result set has unknown size, iterate rounds of finding until K consecutive rounds surface nothing new, using convergence of the discovered set - not a predetermined count - as the stopping condition.

Motivation

“Hunt for bugs across the whole codebase.” How many bugs are there? Unknown; that is the task. A fixed plan (run 4 finders once) samples; it does not sweep. A plan of “run finders 10 times” is arbitrary in both directions: wasteful on a clean codebase, insufficient on a rotten one. The correct stopping condition is a property of the results: when rounds stop producing new findings, the well is dry. The script - deterministic, stateful - is exactly the machinery that can hold “everything seen so far,” diff each round against it, and count consecutive empty diffs.

The official docs bless this shape directly (“find flaky tests: run the suite repeatedly, record which fail intermittently, and stop once two rounds in a row find nothing new”), and the community bug-sweep recipe is its full expression.

Applicability

Use Loop-Until-Dry when:

  • The size of the result set is unknown and completeness matters more than a fixed budget.
  • Individual rounds are stochastic samplers (different finders, or the same finders re-rolled) so that repeated rounds genuinely explore new territory.
  • You can define a stable identity key for findings, so “new” is decidable. No key, no convergence.

Prefer a single Scout-Worker pass when the item set is enumerable in one shot (files are; bugs are not). Prefer Budget-Scaled Depth when the honest constraint is spend, not completeness.

Structure
flowchart TD
    START(["dry = 0, seen = {}, found = []"]) --> ROUND["round: fan out finders<br/>(parallel, diverse prompts)"]
    ROUND --> DIFF{"any findings<br/>NOT in seen?"}
    DIFF -- "no" --> INC["dry += 1"]
    INC --> K{"dry ≥ K<br/>(typically 2)?"}
    K -- "no" --> ROUND
    K -- "yes" --> DONE(["return found"])
    DIFF -- "yes" --> RESET["dry = 0<br/>add ALL fresh to seen<br/>(verified or not)"]
    RESET --> VER["optional: verify fresh findings<br/>(Adversarial Verify)"]
    VER --> ACC["append survivors to found"]
    ACC --> CAPQ{"safety cap hit?<br/>(count / budget)"}
    CAPQ -- "no" --> ROUND
    CAPQ -- "yes" --> DONE

Figure 2.7 - Convergence, not counting. Note the detail the arrows encode: seen absorbs everything fresh, including findings verification later rejects.

Participants
  • Finder cohort - parallel finders per round, each with a distinct hunting prompt (boundary bugs, race conditions, swallowed exceptions…). Diversity across finders is what makes rounds explore rather than repeat; see Perspective-Diverse Verify for the same principle applied to checking.
  • Seen-set - the deterministic ledger of every finding ever encountered, keyed stably (see Seen-Set Ledger). The loop’s memory.
  • Dry counter - consecutive rounds with an empty diff. K=2 is the published convention; raise it when finders are highly stochastic.
  • Optional verify gate - fresh findings pass through skeptics before joining the confirmed list.
  • Safety cap - a confirmed-count or budget bound, because the 1,000-agent runtime cap is a backstop, not a stopping strategy.
Collaborations

Each round is itself a small Fan-Out (barrier justified: the diff is an aggregate). The script diffs, updates the ledger, and decides continuation - all deterministic, all journaled-around. The verify gate, when present, nests per-finding skeptic barriers inside the round.

Consequences
  1. Completeness with an evidence-based stop. The run does as much work as the codebase demands, no more.
  2. Cost is unbounded a priori. Two clean rounds on a clean repo is cheap; a rotten repo runs long. Pair with a cap and, when spend is the real constraint, hand the reins to Budget-Scaled Depth.
  3. Convergence is only as sound as the key. A key too coarse (file only) merges distinct bugs and stops early; too fine (include the free-text description verbatim) makes every paraphrase “new” and the loop never dries. Truncated-normalized descriptions plus location is the working compromise.
  4. The one detail that makes or breaks it: dedupe against everything seen, not everything confirmed. Otherwise findings the verify gate rejected reappear every round, the diff is never empty, and the loop cannot converge. This is the published example’s own hard-won warning.
  5. Diminishing returns are invisible mid-run except through log(). Emit per-round counts (round 4: 2 fresh, 31 total, dry 0/2) religiously.
Implementation
  • Key findings as ${file}:${line ?? '?'}:${desc.slice(0, 40)} or similar: location plus a stable prefix.
  • Cap explicitly: while (dry < 2 && confirmed.length < 40).
  • To sharpen exploration, tell later rounds what is already known: append a compact digest of seen to finder prompts (“do not re-report these”) - spending a few prompt tokens to save whole redundant rounds.
Sample Code
const key = (b) => `${b.file}:${b.line ?? '?'}:${b.desc.slice(0, 40)}`
const seen = new Set()
const confirmed = []
let dry = 0

while (dry < 2 && confirmed.length < 40) {
  const found = (await parallel(FINDERS.map((p) => () =>
    agent(`${p} Report file, line, one-line description.`,
          { phase: 'Find', schema: BUGS }))))
    .filter(Boolean).flatMap((r) => r.bugs)

  const fresh = found.filter((b) => !seen.has(key(b)))
  if (!fresh.length) { dry++; log(`dry round ${dry}/2`); continue }

  dry = 0
  fresh.forEach((b) => seen.add(key(b)))   // seen absorbs ALL fresh - pre-verification

  const judged = await parallel(fresh.map((b) => () =>
    parallel([0, 1, 2].map((i) => () =>
      agent(`Try to refute this bug (reviewer ${i + 1}); default real=false if unsure: ` +
            `${b.desc} at ${b.file}`, { phase: 'Verify', schema: VERDICT })))
      .then((votes) => ({ b, real: votes.filter(Boolean)
                                        .filter((v) => v.real).length >= 2 }))))
  confirmed.push(...judged.filter((v) => v.real).map((v) => v.b))
  log(`${confirmed.length} confirmed so far`)
}
return confirmed
Known Uses
  • The flaky-test hunt in the official docs; the codebase-wide bug sweep in the community library; any “find issues until the list stops growing” formulation.

Built on Seen-Set Ledger (its memory), Barrier Rendezvous (its per-round diff), and usually Adversarial Verify (its gate). Budget-Scaled Depth replaces its stopping condition when spend is the constraint. Without a cap it is the Unbounded Loop anti-pattern waiting to happen.


Pattern 8: Converge-Until-Green

Family: Behavioral Also Known As: Fix Loop, Check-Repair Iteration

Intent

Alternate an executable check with targeted fixes for what the check reports, iterating until the check passes or consecutive rounds make no progress, so the workflow terminates in a verified state or an honest stall report - never in “probably fixed.”

Motivation

“Fix the type errors” is not one task; it is an unknown number of tasks revealed incrementally, because fixing one error routinely uncovers or creates the next. A single agent pass ends wherever its context ran out. The convergent shape instead treats the checker as the ground truth: run npx tsc --noEmit, fix exactly what it reported, run it again. Two extra ingredients make it robust: a pass condition (green) and a stall condition (two rounds with no reduction in error count), because some error sets are not monotonically fixable by the fixers you fielded, and looping forever on them is worse than reporting the residue.

The official docs offer this verbatim as an example prompt: “run npx tsc —noEmit and keep fixing the reported errors until the type check passes or two rounds in a row make no progress.”

Applicability

Use Converge-Until-Green when:

  • An executable oracle exists - compiler, linter, test suite, schema validator - whose output enumerates the remaining work.
  • Fixes interact: repairing one item can add or remove others, so the work list must be re-derived each round rather than computed once.
  • A stalled state is meaningful and reportable (“these 3 errors need a human”).

Without an oracle there is nothing to converge on; use Adversarial Verify to manufacture judgment instead. When errors are independent (fixing one never affects another), a single Scout-Worker pass over the error list is cheaper.

Structure
stateDiagram-v2
    [*] --> Check
    Check --> Green: oracle passes
    Check --> Fix: errors reported
    Fix --> Check: re-run oracle
    Check --> Stalled: no progress K rounds
    Green --> [*]: return verified state
    Stalled --> [*]: return residue + diagnosis
    note right of Fix: fan fixers over the CURRENT error list - grouped by file so fixers do not collide
    note right of Stalled: progress = error count strictly decreasing; equal counts across rounds trip the stall counter

Figure 2.8 - Two exits, both honest: verified green, or a named residue.

Participants
  • Oracle - an agent that runs the check and returns the error list structurally ({ pass, errors: [{ file, line, message }] }). The single source of truth; fixers never self-assess.
  • Fixers - fanned over the current error list, one per error group (grouping by file avoids two fixers editing one file; add Worktree Isolation if groups still overlap).
  • Progress meter - deterministic script state: previous round’s error count versus this round’s. Strict decrease resets the stall counter.
Collaborations

Each iteration: oracle -> group -> fan fixers -> oracle. The re-run of the oracle after fixing is not optional bookkeeping; it is the pattern. Fix-then-assume is precisely the failure the pattern exists to prevent.

Consequences
  1. Terminates in a machine-verified state. “Green” means the oracle said so on the final round, not that fixers reported success.
  2. Handles interacting fixes that any single-pass shape mishandles: the work list is always fresh.
  3. Stall detection converts pathology into information. The residue list plus per-round counts is a precise handoff to a human.
  4. Oracle cost per round. A full test suite as oracle is expensive; scope it (affected packages only) or use a cheap oracle (typecheck) per round with the full suite as a final gate.
  5. Oscillation risk. Fixer A’s fix creates the error fixer B fixes by recreating A’s. Strict-decrease progress metering catches this as a stall; including recent-round history in fixer prompts (“this error was previously ‘fixed’ by doing X; do not undo it”) prevents it.
Implementation
  • Group errors by file before fanning; one fixer per file per round.
  • Track prevCount in the script; if (count >= prevCount) stall++ else stall = 0.
  • Cap rounds (round < 10) in addition to stall detection; oscillations with net-negative rounds can evade a pure stall counter.
Sample Code
const ERRORS = { type: 'object', required: ['pass', 'errors'], properties: {
  pass: { type: 'boolean' }, errors: { type: 'array', items: { type: 'object',
    required: ['file', 'message'], properties: { file: { type: 'string' },
      line: { type: 'number' }, message: { type: 'string' } } } } } }
const FIXED = { type: 'object', required: ['attempted'], properties: {
  attempted: { type: 'number' }, notes: { type: 'string' } } }

let prevCount = Infinity, stall = 0, round = 0
while (round++ < 10 && stall < 2) {
  phase(`Check ${round}`)
  const check = await agent(
    'Run `npx tsc --noEmit`. Return pass and every error with file, line, message.',
    { schema: ERRORS })
  if (check?.pass) { log(`green on round ${round}`); return { green: true, rounds: round } }

  const count = check.errors.length
  if (count >= prevCount) { stall++; log(`no progress: ${count} errors (stall ${stall}/2)`) }
  else stall = 0
  prevCount = count

  const byFile = Object.entries(Object.groupBy(check.errors, (e) => e.file))
  await parallel(byFile.map(([file, errs]) => () =>
    agent(`Fix exactly these type errors in ${file}, changing nothing else:\n` +
          errs.map((e) => `  L${e.line ?? '?'}: ${e.message}`).join('\n'),
      { label: `fix:${file}`, phase: `Fix ${round}`, schema: FIXED })))
}
return { green: false, residual: prevCount, rounds: round - 1 }
Known Uses
  • The docs’ typecheck example; lint-clean sweeps; making a generated test suite pass (the test-generation recipe’s Verify phase is one iteration of this loop, and extending it to convergence is the natural hardening).

The oracle is a Test Gate promoted from stage to loop condition. Shares stall-vs-cap machinery with Loop-Until-Dry (that one converges on discovery, this one on repair). Worktree Isolation when per-round fixers overlap. Its degenerate form - fix without re-checking - has no pattern name because it is simply the absence of the pattern.


Pattern 9: Budget-Scaled Depth

Family: Behavioral Also Known As: Loop-Until-Budget, Pay-for-Depth

Intent

Make the token budget an explicit input to the orchestration, so depth of coverage scales to declared willingness to spend rather than to a fixed plan, and the run self-paces to fill - but not exceed - its allocation.

Motivation

“Exhaustively audit this repo” has no natural finish line; every additional round finds marginally more. The honest framing is economic: how much is exhaustiveness worth today? The +Nk directive (ultracode +500k: exhaustively audit...) sets a turn token target, and the script’s budget global - { total, spent(), remaining() } - lets the orchestration read its own remaining allowance and decide, deterministically, whether another round fits. The plan is no longer “4 rounds” but “rounds while at least 50k tokens remain,” and the same saved script runs shallow on +100k and deep on +1m.

Applicability

Use Budget-Scaled Depth when:

  • Marginal value is smoothly decreasing (audits, sweeps, research breadth) so any budget cut still yields a coherent partial result.
  • The user explicitly sets a target; the pattern is meaningless without one.
  • You want one saved script to serve both quick passes and deep dives (see Parameterized Command: budget becomes the depth parameter).

Do not use it for tasks with correctness cliffs - a migration 80% done is not 80% of the value - where Converge-Until-Green or a fixed plan fits better.

Structure
flowchart TD
    T["+Nk directive sets budget.total"] --> G{"budget.total set?"}
    G -- "no (null) - remaining() is Infinity" --> FB["fallback: fixed round count<br/>NEVER loop on remaining() alone"]
    G -- "yes" --> L["round of work"]
    L --> R{"budget.remaining() ><br/>reserve (cost of a round<br/>+ synthesis margin)?"}
    R -- "yes, and new results" --> L
    R -- "no" --> S["synthesize from what exists"]
    L -- "round found nothing new" --> S
    S --> OUT["return - under the ceiling,<br/>because agent() THROWS at the cap"]

Figure 2.9 - The reserve check is load-bearing: the ceiling is hard, and a run that hits it mid-synthesis loses the synthesis.

Participants
  • The directive - the user’s +Nk, the pattern’s sole input.
  • The budget global - the script’s meter. total: null without a directive; remaining(): Infinity then; agent() throws once the target is hit.
  • The reserve - a script constant: enough remaining tokens for one more round plus the closing synthesis. The margin that keeps the hard ceiling from truncating the ending.
  • Rounds - any repeatable unit: an audit slice, a research angle, a finder cohort.
Collaborations

Composes naturally with Loop-Until-Dry: the loop condition becomes dry < K && remaining() > reserve, giving two exits - the well ran dry, or the wallet did - and the return value should say which. log() the meter each round (~${Math.round(budget.remaining()/1000)}k left) since budget exhaustion is otherwise invisible until the throw.

Consequences
  1. Cost is a contract, not a surprise. The run spends what was authorized, visibly.
  2. One script, many depths. The depth knob moves to the invocation.
  3. Hard-ceiling hazard. Because agent() throws at the cap, a script without a reserve dies mid-flight and returns nothing. The reserve calculation - estimate a round’s cost, double it, add synthesis - is the pattern’s craft.
  4. The null trap. Guard every budget-driven loop on budget.total first; with no directive, remaining() is Infinity and an unguarded loop runs to the 1,000-agent cap. This is the documented footgun and the Unbounded Loop anti-pattern’s favorite entry point.
  5. Depth becomes invocation-dependent, so two runs of “the same” audit are comparable only at comparable budgets. Record the budget in the return value for honest comparisons.
Implementation
const RESERVE = 50_000            // one round + synthesis margin
const capped = budget.total != null
const roomFor = (n = 1) => !capped || budget.remaining() > RESERVE * n

const found = [], seen = new Set()
let rounds = 0
while (roomFor() && rounds++ < (capped ? Infinity : 4)) {   // fixed fallback when uncapped
  const round = await agent(
    'Audit a slice of this repo for issues not already reported. New issues only.',
    { phase: 'Audit', schema: ISSUES })
  const fresh = (round?.issues || []).filter((i) => !seen.has(i.issue))
  if (!fresh.length) { log('dry - stopping early'); break }
  fresh.forEach((i) => seen.add(i.issue)); found.push(...fresh)
  if (capped) log(`${found.length} issues, ~${Math.round(budget.remaining()/1000)}k left`)
}
return { found, rounds, budgetTotal: budget.total }
Known Uses
  • The budget-scaled exhaustive audit in the community library (“depth scales to what you are willing to pay for”); deep-research variants that add angles while budget remains.

Usually wraps Loop-Until-Dry, adding the economic exit to the epistemic one. Model Routing stretches any given budget further. The unguarded form is a canonical Unbounded Loop.


Pattern 10: Phase-Gated Staging

Family: Behavioral Also Known As: Human-in-the-Loop Decomposition, Stage-and-Sign-Off

Intent

When a task needs human judgment between major stages, split it into separate workflows - one per stage - with the human decision carried between them as args, honoring the runtime’s no-mid-run-input constraint instead of fighting it.

Motivation

A CAB-governed deployment pipeline, a destructive migration, a mass refactor: somebody must approve the plan before execution touches anything. But a workflow accepts no mid-run input; only agent permission prompts pause it, and those are tool-level, not decision-level. Trying to smuggle a review into a run - an agent that “waits” for a file to appear, a prompt that asks the void - fights the architecture (see the Mid-Run Conversation anti-pattern). The documentation states the resolution as a constraint’s own remedy: “For sign-off between stages, run each stage as its own workflow.”

So: /plan-migration produces a plan artifact and a summary; you read it, in the conversation, with full Claude assistance; then /execute-migration takes the approved plan (or your amended version) as args. The gate is the conversation itself - the one place in the system where a human and Claude deliberate - and each stage is independently resumable, savable, and re-runnable.

Applicability

Use Phase-Gated Staging when:

  • A stage’s output is a decision input, not just an intermediate result: plans before execution, findings before remediation, candidate lists before deletion.
  • Governance or blast radius demands recorded human sign-off (change control, destructive operations, anything auditable).
  • Stages have different cost profiles and you want the option to not run stage 2 at all.

When no human decision separates stages, a single workflow with internal phases is simpler; when the “human” gate is really a mechanical check, use a Test Gate inside one run.

Structure
sequenceDiagram
    participant U as You (the gate)
    participant C as Claude (session)
    participant W1 as Workflow: Plan
    participant W2 as Workflow: Execute
    U->>C: /plan-migration src/legacy/
    C->>W1: run
    W1-->>C: plan artifact + risk summary (return value)
    C-->>U: presents plan, discusses trade-offs
    Note over U,C: the gate: deliberation happens in conversation, with Claude's help, on the stage-1 artifact
    U->>C: approved, but exclude module X
    C->>W2: run with args = amended plan
    W2-->>C: execution report
    C-->>U: results

Figure 2.10 - The human gate lives between runs, where the architecture puts a human anyway.

Participants
  • Stage workflows - each a complete, saved workflow with its own meta, phases, and return value. Stage N’s return is designed as stage N+1’s input.
  • The gate - the conversation between runs: review, amendment, approval, or veto.
  • The carried decision - structured args into the next stage: the approved plan, the amended list, the selected subset.
Collaborations

Stage 1 ends by returning a machine-usable artifact (a plan object, a target list), not just prose - the Structured Output Contract applied at workflow granularity - plus a human-readable summary. The session presents it; the human amends it; the amended object launches stage 2. Each stage being a Parameterized Command is what makes the handoff clean.

Consequences
  1. Real deliberation at the gate. The human reviews with Claude’s full interactive help, not through a permission popup. For consequential decisions this is strictly better than any in-run prompt could be.
  2. Auditability. Each stage’s script, inputs, and outputs are separately recorded; the approval is a visible conversation turn. This maps directly onto change-advisory-board evidence requirements.
  3. Independent economics. Stage 2 can be declined, deferred, re-run with amendments, or run on a subset - none of which a monolithic run permits.
  4. Handoff contract maintenance. Stage boundaries are now interfaces; changing stage 1’s return shape breaks stage 2 (the same coupling as Sub-Workflow Composition, consequence 3).
  5. Lost cross-stage journal. Stage 2 cannot reuse stage 1’s cached agents; anything genuinely shared must travel in the artifact. Design stage 1’s return to carry everything stage 2 needs so it re-discovers nothing.
Implementation
  • Name stage pairs conventionally: plan-X / apply-X, find-X / fix-X.
  • Make stage 1’s return self-sufficient: targets with the context an executor needs (file, line, rationale), not bare identifiers.
  • Record the approval in the stage-2 invocation itself (args: { plan, approvedBy: 'rallen', amendments: [...] }) so the run’s own record carries the sign-off.
Sample Code
// Stage 1: plan-deprecation.js - returns a reviewable, executable plan
export const meta = { name: 'plan-deprecation',
  description: 'Plan the removal of a deprecated API; output for human review',
  phases: [{ title: 'Discover' }, { title: 'Assess' }] }

const { sites } = await agent(`Find every call site of ${args.old}(). File + line.`,
  { schema: SITES })
const assessed = await parallel(sites.map((s) => () =>
  agent(`Assess rewriting ${args.old} at ${s.file}:${s.line}: mechanical or judgment-required?`,
    { label: `assess:${s.file}`, schema: ASSESSMENT })
    .then((a) => ({ ...s, ...a }))))
return {
  plan: assessed.filter(Boolean),
  summary: { total: sites.length,
             mechanical: assessed.filter((a) => a?.mechanical).length },
}

// Stage 2: apply-deprecation.js - takes the APPROVED (possibly amended) plan
export const meta = { name: 'apply-deprecation',
  description: 'Execute an approved deprecation plan', phases: [{ title: 'Rewrite' }] }

const approved = args.plan.filter((s) => !args.excluded?.includes(`${s.file}:${s.line}`))
return await parallel(approved.map((s) => () =>
  agent(`Rewrite the call at ${s.file}:${s.line} per the approved plan: ${s.rationale}`,
    { label: `rewrite:${s.file}`, isolation: 'worktree', schema: EDIT })))
Known Uses
  • The documentation’s own recommendation for inter-stage sign-off; any change-controlled environment where “plan artifact reviewed before execution” is policy rather than preference (canary rollouts, monitor-threshold mass updates, destructive cleanups).

Each stage is a Parameterized Command; the alternative composition without a human gate is Sub-Workflow Composition. The mechanical-vs-judgment split in stage 1 echoes Worktree Isolation’s needsReview escape hatch. Fighting the constraint instead is the Mid-Run Conversation anti-pattern.


Pattern 11: Model Routing

Family: Behavioral Also Known As: Tiered Delegation, Cheap-Stage Assignment

Intent

Assign each stage the least expensive model that can do its job - enumeration and classification to small models, synthesis and judgment to strong ones - so that a run’s cost tracks the difficulty profile of its stages rather than its agent count.

Motivation

A changelog builder classifies 300 commits and writes one summary. The classification is 300 cheap, well-scoped calls; the summary is one hard call. Running all 301 on the session’s strongest model pays frontier prices for triage. The model: option on agent() routes per call; the docs make the economics explicit (“every agent uses your session’s model unless the script routes a stage to a different one… ask Claude to use a smaller model for stages that don’t need the strongest one”) and the environment variable CLAUDE_CODE_SUBAGENT_MODEL provides a global override that trumps both.

In a fan-out medium, per-call routing has multiplied leverage: a model choice on a 300-agent stage is a 300x-weighted decision.

Applicability

Route stages down when the stage is: enumeration (Scout-Worker scouts), classification against a closed enum, mechanical transformation, or per-item extraction with a tight schema. Keep stages up when they are: synthesis over everything, adversarial judgment (a skeptic weaker than the finder rubber-stamps; see Adversarial Verify), planning, or anything whose errors compound downstream.

Structure
flowchart LR
    subgraph cheap ["small model tier"]
        SC["scout: enumerate"] --> CL["classify × N<br/>(the multiplied stage)"]
    end
    subgraph strong ["strong model tier"]
        V["verify / judge"] --> SY["synthesize"]
    end
    CL --> V
    ENV["CLAUDE_CODE_SUBAGENT_MODEL<br/>overrides everything"] -.-> cheap & strong

Figure 2.11 - Route by stage difficulty. The wide stage is where routing pays; the narrow stages are where it costs accuracy.

Consequences
  1. Cost tracks difficulty. The wide-and-easy stages stop subsidizing prices set by the narrow-and-hard ones.
  2. Schema validation is the safety net for downgraded stages: the Structured Output Contract’s retry-on-mismatch catches a small model’s format failures, though not its judgment failures. Downgrade stages whose failures a schema can catch.
  3. Verification asymmetry is a real hazard. Verifiers should be at least as strong as what they verify; a cheap skeptic panel converts Adversarial Verify into theater.
  4. The environment override cuts both ways: CLAUDE_CODE_SUBAGENT_MODEL silently flattens your careful routing. Note in meta.whenToUse if a script assumes tiering.
  5. Comparability. Runs routed differently are not directly comparable; record routing in the return value alongside budget (Pattern 9, consequence 5).
Sample Code
const classified = await parallel(commits.map((c) => () =>
  agent(`Classify commit "${c.subject}" as feature/fix/perf/docs/internal/noise; ` +
        `if user-facing, one-line note.`,
    { label: `classify:${c.sha.slice(0, 7)}`, phase: 'Classify',
      model: 'claude-haiku-4-5-20251001',          // 300 calls: route down
      schema: CLASS })
    .then((k) => ({ ...c, ...k }))))

phase('Write')
return await agent(                                  // 1 call: session model
  `Group into release notes under Features/Fixes/Performance/Docs:\n` +
  keep.map((c) => `- (${c.type}) ${c.userFacing || c.subject}`).join('\n'))
Known Uses
  • The docs’ own cost guidance; classification-heavy recipes (changelog, i18n extraction, dependency triage) are the natural clients.

Multiplies the reach of Budget-Scaled Depth. Bounded by Adversarial Verify’s strength requirement. The scout in Scout-Worker is its most reliable customer.


Family III: Verification Patterns

Verification patterns manufacture confidence. Their shared premise is the catalog’s first conviction: agents checking each other, arranged deliberately, produce trust that raw generation cannot. These patterns are the reason to use a workflow even for tasks a single agent could technically attempt - a single turn-by-turn agent never runs a verification pass against itself.


Pattern 12: Adversarial Verify

Family: Verification Also Known As: Skeptic Gate, Refutation Panel

Intent

Before any finding is reported, submit it to N independent agents explicitly prompted to refute it, and report only findings that survive a majority; the burden of proof lies on the finding, and ties die.

Motivation

Finders are optimists. An agent hunting bugs is rewarded - by its own prompt - for producing bugs, and plausible-but-wrong findings are its characteristic failure. Asking the finder “are you sure?” re-samples the same context and the same incentive. The correction is structural: fresh agents, zero shared context with the finder, prompted in the adversarial direction (“try to refute this bug; default real=false if unsure”), voting independently. A finding that survives 2-of-3 motivated skeptics is categorically more trustworthy than one that survived its own author’s re-read.

The stakes are asymmetric, which is why the prompt’s default matters: a false positive in a bug report costs an engineer’s investigation and the report’s credibility; a false negative costs one missed finding among many. default real=false if unsure encodes that asymmetry. This pattern is why Jarred Sumner’s account of the six-day Bun Zig->Rust port credits “dynamic workflows and adversarial code review” in the same breath.

Applicability

Use Adversarial Verify when:

  • Findings will be acted on - filed, fixed, reported upward - so false positives have a real cost.
  • Findings are checkable by an agent with access to the code/data (the skeptic can look).
  • No executable oracle exists; when one does, Test Gate is cheaper and stronger.

Skip it for exploratory, low-stakes output where a human will eyeball everything anyway; the gate costs N verifier calls per finding.

Structure
flowchart TD
    F["finding<br/>(from a finder agent)"] --> S1["skeptic 1<br/>'try to refute - default false'"]
    F --> S2["skeptic 2<br/>independent, fresh context"]
    F --> S3["skeptic 3<br/>no access to finder's reasoning"]
    S1 --> V{"majority vote<br/>≥ 2 of 3 say real"}
    S2 --> V
    S3 --> V
    V -- "survives" --> R["reported"]
    V -- "killed" --> X["discarded -<br/>but stays in the seen-set"]

Figure 2.12 - Independence is the active ingredient: skeptics share the finding, never the finder’s reasoning.

Participants
  • The finding - a structured claim (file, line, description) from any upstream producer.
  • Skeptics - N fresh agent() calls (N=3 conventional), each receiving the finding only: not the finder’s reasoning, not each other’s votes. Prompted to refute, with the unsure-default set by the cost asymmetry.
  • The vote - deterministic script code: count real, compare to threshold. Never delegate the tally to another agent.
Collaborations

Per-finding skeptic panels are inner parallel() barriers (justified: the vote is an aggregate) nested inside an outer per-finding fan - the canonical nested-barrier idiom from Barrier Rendezvous. In Loop-Until-Dry, the gate sits between “fresh” and “confirmed,” and rejected findings must still enter the seen-set (Pattern 7, consequence 4).

Consequences
  1. Precision buys credibility. A report of 12 findings that are all real beats a report of 40 that are 60% real, in every context where humans triage the output. This is the pattern’s economic argument.
  2. Recall is spent deliberately. Real-but-subtle findings die when skeptics can’t reproduce the reasoning. The threshold and the unsure-default are the tuning knobs; for high-recall passes (pre-release security), lower the bar and mark confidence instead of killing.
  3. Cost multiplies: N verifier calls per finding. On a 100-finding sweep at N=3, verification is 300 calls - often more than discovery. Mitigate by early-exit on zero findings (Barrier Rendezvous) and severity-gating (verify criticals at N=3, lows at N=1).
  4. Verifier strength is load-bearing. Skeptics weaker than the finder rubber-stamp or randomly veto; see Model Routing, consequence 3.
  5. Independence can decay silently. Skeptics fed the finder’s full argument become editors, not adversaries. Pass the claim, not the case for it.
Implementation
  • Structure verdicts ({ real: boolean, reason: string }); keep reason for the survivors’ report and for auditing dead findings.
  • Number the skeptics in the prompt (reviewer ${i+1}) - index-derived variety, since Math.random() is unavailable and identical prompts invite identical blind spots. Better still, differentiate them properly: that upgrade is Perspective-Diverse Verify.
  • Vote in script code. An agent asked to “tally these votes” can miscount; filter(v => v.real).length >= 2 cannot.
Sample Code
const verdicts = await parallel(findings.map((f) => () =>
  parallel([1, 2, 3].map((i) => () =>
    agent(`Reviewer ${i}: try to REFUTE this finding against the actual code. ` +
          `Default real=false if unsure.\nFinding: ${f.title} (${f.file}:${f.line ?? '?'})`,
      { label: `verify:${f.file}`, phase: 'Verify', schema: VERDICT })))
    .then((votes) => {
      const yes = votes.filter(Boolean).filter((v) => v.real)
      return { ...f, real: yes.length >= 2,
               evidence: yes.map((v) => v.reason) }
    })))
return verdicts.filter(Boolean).filter((f) => f.real)
Known Uses
  • The multi-dimension PR review recipe (“the canonical review pattern”); the bug sweep’s confirmation gate; the exploitability check in the security-audit recipe (“is this genuinely exploitable in context? Be skeptical”); /deep-research’s per-claim voting is the same mechanism applied to research claims (Cross-Checked Claims).

Perspective-Diverse Verify replaces redundant skeptics with differentiated ones. Test Gate replaces judgment with execution where possible. Judge Panel applies the same energy to comparing alternatives rather than killing findings. Skipping the vote’s independence turns the pattern into decoration.


Pattern 13: Perspective-Diverse Verify

Family: Verification Also Known As: Lens Panel, Multi-Modal Review

Intent

Differentiate the members of a verification (or investigation) panel by giving each a distinct analytical lens - correctness, security, performance, reproducibility - so the panel’s coverage is the union of genuinely different examinations rather than N samples of the same one.

Motivation

Three identical skeptics protect against variance: one agent’s bad sample. They do not protect against bias: a blind spot shared by all three, which - since they run the same prompt on the same model - is common. A finding that is “correct code, catastrophic under load” sails past three correctness-prompted skeptics unanimously. Assign the seats instead: one skeptic judges correctness, one security, one whether the issue reproduces. Now the panel’s blind spot is the intersection of three different blind spots, which is smaller than any one of them. The principle predates workflows (it is why review checklists have sections), but the medium makes it nearly free: differentiation is a prompt array.

The same move applies upstream of verification: investigation panels (an incident examined via diffs, logs, config changes, and dependency bumps simultaneously) are this pattern applied to discovery.

Applicability

Use Perspective-Diverse Verify when the failure modes of what you’re checking genuinely span dimensions (code does: correct/secure/fast/tested are near-orthogonal), and each lens is articulable as a prompt. Prefer plain Adversarial Verify when the question is one-dimensional (“does this reference exist anywhere?”) and variance, not bias, is the enemy - redundancy fights variance; diversity fights bias. Use both when stakes justify it: N diverse lenses times M redundant samples per lens.

Structure
flowchart TD
    subgraph identical ["redundant panel - fights variance"]
        F1["finding"] --> I1["skeptic"] & I2["skeptic"] & I3["skeptic"]
        I1 & I2 & I3 --> BV["vote"]
        NOTE1["shared blind spot<br/>survives unanimously"]
    end
    subgraph diverse ["lens panel - fights bias"]
        F2["finding"] --> L1["correctness lens"] & L2["security lens"] & L3["repro lens"]
        L1 & L2 & L3 --> DV["vote or union"]
        NOTE2["blind spot =<br/>intersection of three"]
    end

Figure 2.13 - Same agent count, different epistemics. “Diversity catches failure modes redundancy can’t.”

Participants
  • The lens roster - script data: ['correctness', 'security', 'repro'] or richer { key, prompt } objects. The roster is the review policy; in a saved workflow it is a governable artifact (a team’s definition of “reviewed”).
  • Lens agents - one per lens per subject, each prompted within its dimension only.
  • The combiner - a vote (finding must survive at least K lenses), a union (all lens findings kept, tagged by dimension), or a per-lens report, depending on whether the lenses are judging one claim or hunting in different territories.
Collaborations

As a verify gate: identical wiring to Pattern 12 with the prompt array swapped (['correctness','security','repro'].map((lens) => ...)) - the loop-until-dry sample in Part I’s sources does exactly this. As an investigation fan: the lens roster feeds a Fan-Out / Reduce / Synthesize where the synthesis weighs lenses against each other (the incident-RCA shape).

Consequences
  1. Coverage becomes designed, not sampled. The dimensions checked are exactly the roster; a review’s scope is auditable by reading an array.
  2. Vote semantics change and must be re-derived. Three lens verdicts are not exchangeable votes: a finding real-under-security but not-reproducible is information, not a 1-2 defeat. Decide per task whether the combiner is majority, any-lens-kills, any-lens-confirms, or tag-and-report.
  3. Lens quality varies invisibly. A weakly-prompted lens contributes noise while appearing to contribute coverage; per-lens spot checks (read a few of its verdicts in /workflows) are the maintenance cost.
  4. Roster drift is policy drift. Adding a lens to the saved script changes what “reviewed” means for everyone using it - a feature for governance, a hazard for silent edits (the same coupling as Sub-Workflow Composition, consequence 3).
Sample Code
const LENSES = [
  { key: 'correctness', prompt: 'Judge strictly on logic: is the reported behavior actually wrong?' },
  { key: 'security',    prompt: 'Judge strictly on exploitability: can this be abused in context?' },
  { key: 'repro',       prompt: 'Judge strictly on reproducibility: can you trigger it from the code as written?' },
]

const judged = await parallel(fresh.map((b) => () =>
  parallel(LENSES.map((l) => () =>
    agent(`${l.prompt}\nFinding: "${b.desc}" at ${b.file}. Real under YOUR lens only?`,
      { label: `verify:${l.key}`, phase: 'Verify', schema: VERDICT })
      .then((v) => ({ lens: l.key, real: v?.real, reason: v?.reason }))))
    .then((views) => ({
      ...b,
      real: views.filter((v) => v.real).length >= 2,
      views,                                   // keep per-lens detail: it is information
    }))))
Known Uses
  • The published loop-until-dry example’s ['correctness','security','repro'] panel; the multi-dimension PR review’s dimension roster (correctness/security/perf/tests) is the pattern applied to discovery; incident RCA’s four investigation angles (diffs/logs/config/deps).

The differentiated refinement of Adversarial Verify. The lens roster generating discovery rather than judgment is a Fan-Out over dimensions. Judge Panel differentiates the generators the same way this pattern differentiates the checkers.


Pattern 14: Judge Panel

Family: Verification Also Known As: Tournament Synthesis, Generate-Score-Graft

Intent

For problems with many defensible answers, generate N complete attempts from deliberately different angles, score each with judges, then synthesize the final answer from the winner while grafting the best elements of the runners-up - buying answer quality through structured competition rather than a bigger single attempt.

Motivation

“Draft the migration plan” has no oracle and no single right answer; it has better and worse answers along axes that trade off. One agent produces one point in that space, anchored by its first framing. The tournament shape samples the space deliberately: an MVP-first plan, a risk-first plan, a user-value-first plan - each written by an agent committed to its angle - then judges score all three, and a synthesizer writes the final plan “based on the top plan, grafting the best ideas from the others.” The graft step is what elevates this above pick-the-winner: the risk-first plan’s rollback section improves the MVP-first winner even when it loses overall.

The docs name this shape as a first-class motivation for workflows: “a plan you want stress-tested from every angle before you commit to it” / “draft a plan from several independent angles and weigh them against each other.”

Applicability

Use Judge Panel when:

  • The task is generative with quality axes rather than correctness (plans, designs, architectures, hard prose).
  • Angles are articulable and genuinely produce different answers (if all N attempts converge, the tournament confirmed robustness - useful, but you paid Nx for it).
  • The decision is worth more than one pass: the pattern’s cost is N generations + M judgments + 1 synthesis for one deliverable.

Do not use it where an oracle exists (Test Gate / Converge-Until-Green are cheaper and objective) or for routine output.

Structure
flowchart TD
    G["goal"] --> D1["draft: MVP-first angle"]
    G --> D2["draft: risk-first angle"]
    G --> D3["draft: user-value angle"]
    D1 & D2 & D3 --> B{{"barrier - judging is cross-item"}}
    B --> J1["judge: score + strengths + risks"] & J2["judge"] & J3["judge"]
    J1 & J2 & J3 --> RANK["rank by score<br/>(deterministic)"]
    RANK --> SYN["synthesize:<br/>winner as base,<br/>graft runners-up strengths"]
    SYN --> OUT["final plan"]

Figure 2.14 - Competition then composition. The barrier is triply justified: ranking, comparison, and grafting are all cross-item.

Participants
  • Angle roster - the deliberate diversity of the drafts, same principle as Pattern 13’s lens roster applied to generation. Each angle prompt commits its agent (“Take the risk-first angle: de-risk the hardest part early. Include steps, risks, and a rollback.”).
  • Drafters - one per angle, unschema’d or lightly schema’d (plans are prose-shaped; over-structuring them costs quality).
  • Judges - scoring agents with a structured rubric ({ score, strengths, risks }). One judge per draft is the light form; multiple judges per draft with score averaging fights judge variance when stakes warrant.
  • The rank - deterministic sort on scores.
  • The synthesizer - receives the winner and the losers, with explicit grafting instructions.
Collaborations

Drafts fan in parallel; judging waits at a barrier (Barrier Rendezvous, all three justifications at once); the synthesizer’s prompt is a careful assembly: goal, winning plan in full, others in full or in judged-strengths summary. When drafts are long, summarize losers to their judged strengths before synthesis to keep the prompt tractable - the reduce discipline of Pattern 1.

Consequences
  1. Anchoring resistance. The final answer was chosen against alternatives, not defaulted into. For one-way-door decisions this is the whole value.
  2. The graft captures dominated-but-partial value that pick-the-winner discards. In practice the graft sections (a rollback plan, a testing strategy) are often the synthesis’s best material.
  3. N-fold generation cost for one deliverable - the most expensive pattern per output in this catalog. Reserve it for decisions with commensurate stakes.
  4. Judge validity is assumed, not established. Scores are model judgments; a systematically biased judge (favoring length, favoring its own style) corrupts the rank invisibly. Structured rubrics, multiple judges, and reading the strengths/risks text (not just the number) are the checks.
  5. Convergent drafts are a finding. If all angles produce the same plan, report that: it is evidence the plan is over-determined by constraints, which a decision-maker wants to know.
Sample Code
const ANGLES = ['MVP-first / smallest shippable slice',
                'risk-first / de-risk the hardest part early',
                'user-first / fastest visible value']
const SCORE = { type: 'object', required: ['score'], properties: {
  score: { type: 'number' }, strengths: { type: 'string' }, risks: { type: 'string' } } }

phase('Draft')
const drafts = await parallel(ANGLES.map((angle) => () =>
  agent(`Draft a concrete plan for: ${goal}. Take the "${angle}" angle. ` +
        `Include steps, risks, and a rollback.`,
    { label: `draft:${angle.split('/')[0].trim()}`, phase: 'Draft' })
    .then((plan) => ({ angle, plan }))))

phase('Judge')
const judged = await parallel(drafts.filter(Boolean).map((d) => () =>
  agent(`Score this plan 0-10 for ${goal}. Note strengths and risks.\n\n${d.plan}`,
    { label: 'judge', phase: 'Judge', schema: SCORE })
    .then((s) => ({ ...d, ...s }))))

const ranked = judged.filter(Boolean).sort((a, b) => (b.score || 0) - (a.score || 0))
phase('Synthesize')
return await agent(
  `Write the final plan for "${goal}". Base it on the top plan below; graft the best ` +
  `ideas from the others.\n\nTOP (${ranked[0].angle}):\n${ranked[0].plan}\n\nOTHERS:\n` +
  ranked.slice(1).map((r) => `[${r.angle} - judged strengths: ${r.strengths}]\n${r.plan}`)
        .join('\n---\n'))
Known Uses
  • The plan-panel recipe; the docs’ “hard plan worth drafting from several independent angles”; architecture and rollout decisions generally.

Generation-side sibling of Perspective-Diverse Verify. The judging barrier is Barrier Rendezvous’s cross-item case in pure form. For claims rather than artifacts, Cross-Checked Claims is the analogous competition. A judge panel whose synthesizer ignores the losers has collapsed into an expensive argmax; the graft is the pattern.


Pattern 15: Cross-Checked Claims

Family: Verification Also Known As: Claim-Level Verification, The Deep-Research Shape

Intent

Decompose research output into atomic claims, verify each claim independently against sources the original reader did not use, and synthesize only from survivors - so the final report’s unit of trust is the claim, not the source or the researcher.

Motivation

A single agent doing web research holds every half-read source in one context and never checks its own claims; its report inherits every misreading and every confident source error. Document-level verification (“is this source reliable?”) is too coarse: good sources contain wrong claims and bad sources contain right ones. The right granularity is the claim. The bundled /deep-research is the production embodiment: scope a question into distinct search angles -> search in parallel -> fetch and extract claims with source attribution -> give each claim an adversarial multi-vote check against fresh sources -> synthesize a cited report from which “claims that didn’t survive cross-checking [are] already filtered out.” As of v2.1.196 it even distinguishes epistemic states: a claim the verifiers couldn’t check (rate limit, fetch error) is reported as unverified rather than counted refuted - a distinction worth copying.

Applicability

Use Cross-Checked Claims when research feeds a decision (the report will be believed and acted on), sources conflict or vary in quality, and claims are independently checkable on the open web or in the repo. Skip the verification pass for orientation-level research a human will independently confirm; the pass roughly doubles cost.

Structure
flowchart TD
    Q["question"] --> SC["scope: decompose into<br/>4-6 distinct search angles"]
    SC --> S1["search+read angle 1"] & S2["angle 2"] & SN["angle N"]
    S1 & S2 & SN --> EX["extract atomic claims,<br/>each with source URL"]
    EX --> DD["dedupe claims across angles<br/>(deterministic)"]
    DD --> V["per claim: independent check<br/>against FRESH sources, multi-vote"]
    V -- "supported" --> KEEP["survivors"]
    V -- "refuted" --> KILL["filtered out"]
    V -- "uncheckable" --> UNV["reported as UNVERIFIED -<br/>a third state, not a refutation"]
    KEEP & UNV --> SYN["synthesize cited report<br/>from survivors only"]

Figure 2.15 - Trust granularity: the claim. Note the three-way verdict - the v2.1.196 refinement.

Participants
  • The scoper - one agent decomposing the question into angles “so the searches don’t all chase the same wording.” Angle diversity here is Pattern 13’s roster principle applied to search strategy.
  • Reader agents - one per angle: search, fetch, extract { claim, source } pairs under schema.
  • The claim pool - deduplicated in script code across angles.
  • Checkers - per claim, prompted to verify against a fresh source (independence from the original reader is the active ingredient, exactly as in Pattern 12), voting supported / refuted / uncheckable.
  • The synthesizer - writes the cited report from survivors, carrying source URLs through.
Consequences
  1. Claim-granular trust, and a report you can cite forward. Every surviving statement carries its source and its survival.
  2. Independence between reading and checking is what distinguishes this from “read carefully”: the checker’s sources are not the reader’s.
  3. Roughly 2x the cost of unchecked research (a check per claim). The scoper’s angle count and a claim-importance filter (check load-bearing claims at N votes, background claims at 1) are the throttles.
  4. Atomic decomposition loses compositional claims. “X causes Y because Z” verified as three atoms can survive while the causal linkage is wrong. Keep causal claims intact as single checkable units.
  5. The three-way verdict resists silent degradation. Binary supported/refuted converts infrastructure failures (rate limits) into refutations; model the uncheckable state explicitly.
Sample Code
phase('Scope')
const { angles } = await agent(
  `Break this question into 4-6 distinct search angles: ${question}`, { schema: ANGLES })

const claims = (await parallel(angles.map((a) => () =>
  agent(`Search the web for "${a}", fetch the best sources, extract factual claims ` +
        `with their source URLs.`,
    { label: `read:${a.slice(0, 24)}`, phase: 'Read', schema: CLAIMS }))))
  .filter(Boolean).flatMap((r) => r.claims)
const pool = [...new Map(claims.map((c) => [c.claim, c])).values()]

const VOTE = { type: 'object', required: ['verdict'], properties: {
  verdict: { enum: ['supported', 'refuted', 'uncheckable'] }, note: { type: 'string' } } }
const checked = await parallel(pool.map((c) => () =>
  agent(`Independently check this claim against a FRESH source (not ${c.source}). ` +
        `supported / refuted / uncheckable?\nClaim: "${c.claim}"`,
    { label: 'verify', phase: 'Verify', schema: VOTE })
    .then((v) => ({ ...c, verdict: v?.verdict ?? 'uncheckable' }))))

const survivors = checked.filter((c) => c.verdict === 'supported')
const unverified = checked.filter((c) => c.verdict === 'uncheckable')
phase('Synthesize')
return await agent(
  `Synthesize a cited report answering "${question}" from these verified claims:\n` +
  survivors.map((c) => `- ${c.claim} [${c.source}]`).join('\n') +
  (unverified.length ? `\n\nList these separately as UNVERIFIED:\n` +
    unverified.map((c) => `- ${c.claim}`).join('\n') : ''))
Known Uses
  • /deep-research itself (scope -> search -> fetch -> 3-vote verify -> synthesize); competitor scans and technology evaluations built on the same skeleton; docs-vs-code drift checking is the pattern with “the repo” as the fresh source.

Fan-Out / Reduce / Synthesize with an Adversarial Verify gate specialized to claims; the scoper applies Perspective-Diverse Verify’s roster idea to search. Via Sub-Workflow Composition, the whole pattern is available as a one-line workflow('deep-research', { question }).


Pattern 16: Test Gate

Family: Verification Also Known As: Executable Check, Oracle Stage

Intent

Follow every mutation with an executed check - compile, test, validate - as its own agent stage, and let acceptance be decided by the check’s result rather than the mutator’s self-report.

Motivation

An agent that edits a file and reports migrated: true has reported its intention, not the state of the world. The cheapest trustworthy verifier available in a codebase is not another model’s judgment but the existing machinery: the compiler, the test suite, the schema validator. A Test Gate stage runs it and returns { pass, failures }; the script branches on that. Where Adversarial Verify manufactures judgment because no oracle exists, Test Gate refuses to manufacture what the repo already provides. The two are not competitors; they cover disjoint territory, and knowing which territory you are in is the pattern-selection question of Family III.

Applicability

Use a Test Gate after any mutating stage where an executable oracle exists and is affordable per-item (or per-batch as a final whole-tree gate). Its preconditions are practical: the agents’ allowlist must cover the check commands (1.7 - pre-approve, or the gate stalls the run on a permission prompt), and the check must be meaningful (a gate on a suite that doesn’t cover the mutated behavior is a green light with no bulb).

Structure
flowchart LR
    M["mutate<br/>(agent edits, in worktree)"] --> G{"GATE<br/>agent RUNS the check:<br/>compile · test · validate"}
    G -- "pass" --> A["accepted -<br/>eligible to merge"]
    G -- "fail" --> R["rejected: discard edit<br/>or route to fix loop"]
    R -.->|"Converge-Until-Green"| M

Figure 2.16 - Acceptance by execution. The dashed edge is the promotion path: a gate iterated becomes a convergence loop.

Participants
  • The mutator - the writing stage, ideally worktree-isolated.
  • The gate agent - runs the check command and structures its outcome. Its prompt names the exact command; its schema is { pass, failures, detail }.
  • The branch - script code: accept, discard, or feed a repair loop.
Collaborations

Canonically the second stage of a Streaming Pipeline over mutated items (each item gated as soon as its mutation lands - the language-port recipe: “immediately run its tests so a failing port is flagged without blocking the others”). As a batch finale after Worktree Isolation merges: the whole-tree gate catching cross-file breakage per-tree gates cannot see. Promoted to a loop condition, it is Converge-Until-Green.

Consequences
  1. Ground truth at near-zero marginal judgment cost. The check already exists; the gate just runs it and reads it.
  2. Self-report is eliminated as an acceptance basis - the failure mode it targets is silent and common.
  3. Gate scope must match mutation scope. Per-file gates miss cross-file breakage; whole-tree gates are expensive per-item. The working compromise: cheap scoped gate per item, full gate once at integration.
  4. Suite quality bounds gate value. Gating on an inadequate suite launders untested changes as verified. The test-coverage-gap recipe exists to fix exactly this upstream.
  5. A flaky suite poisons the gate with false rejections; the flaky-test-hunt recipe is the remediation, and until it runs, per-item retry-once on gate failure is the pragmatic patch.
Sample Code
const ported = await pipeline(
  modules,
  (m) => agent(`Port module ${m} to the target language, preserving its public interface.`,
    { label: `port:${m}`, phase: 'Port', isolation: 'worktree', schema: PORT })
    .then((r) => ({ module: m, ...r })),
  (r, module) => agent(
    `Run the test suite for the ported ${module} (command: npm test -- ${module}). ` +
    `Report pass and failure count.`,
    { label: `test:${module}`, phase: 'Test', schema: TEST })
    .then((t) => ({ ...r, pass: t?.pass, failures: t?.failures }))
)
const all = ported.filter(Boolean)
return { passing: all.filter((m) => m.pass), failing: all.filter((m) => !m.pass) }
Known Uses
  • The language-port and framework-migration recipes’ Test/Verify phases; test-generation’s closing suite run; every CI-shaped workflow.

The oracle inside Converge-Until-Green. The objective sibling of Adversarial Verify (use the gate wherever execution can answer; save skeptics for where it cannot). Depends on Worktree Isolation for safe per-item gating and on allowlist hygiene (1.7) for unattended execution.


Family IV: Contract Patterns

Contract patterns govern the boundary between the deterministic script and the stochastic agents. They are small, they appear inside nearly every other pattern, and they are where beginner scripts fail first.


Pattern 17: Structured Output Contract

Family: Contract Also Known As: Schema Binding, Validated Handoff

Intent

Bind every agent whose output another stage consumes to a JSON Schema, so the agent is forced into a structured-output tool call validated at the tool layer - with automatic retry on mismatch - and the script parses objects, never prose.

Motivation

“Please return JSON” in a prompt is a request; a schema option is a mechanism. The difference is where enforcement lives: the schema is validated at the tool-call layer, and a mismatching response triggers an automatic retry before the script ever sees it. The published guidance is unequivocal: “This is far more reliable than asking an agent to ‘please return JSON’ and hoping. Reach for it whenever a downstream stage consumes the result.” Every regex over agent prose, every JSON.parse in a try/catch around free text, is brittleness the runtime already offered to absorb.

The schema is also the interface documentation between stages: reading a script’s schemas tells you its data flow the way reading type signatures tells you a module’s.

Applicability

Bind a schema whenever any downstream consumer - script code or another agent’s prompt - reads fields out of the result. Leave output free when the result is terminal prose for a human (a newsletter, a final plan) where forced structure costs quality; the synthesizing stages of Patterns 1 and 14 are the legitimate schema-free calls.

Structure
sequenceDiagram
    participant S as Script
    participant R as Runtime (tool layer)
    participant A as Agent
    S->>R: agent(prompt, { schema })
    R->>A: prompt + structured-output tool
    A-->>R: candidate output
    alt validates against schema
        R-->>S: typed object
    else mismatch
        R->>A: retry (automatic - invisible to script)
        A-->>R: corrected output
        R-->>S: typed object
    end
    Note over S: script code does field access,<br/>never text parsing

Figure 2.17 - Enforcement at the tool layer. The retry loop is the runtime’s, not yours.

Consequences
  1. Parsing ceases to exist as a failure mode. The single largest reliability upgrade available per line of script changed.
  2. Enum fields discipline the model. impact: { enum: ['high','medium','low'] } prevents the long tail of creative severity labels that would defeat downstream grouping.
  3. required is a promise the model will keep even when it shouldn’t. A required field the agent has no basis for gets hallucinated to satisfy validation (the Scout-Worker over-specification hazard). Mark required only what is always knowable; make the rest optional and null-check.
  4. Description fields are cheap prompt space. summary: { type: 'string', description: '1-3 sentence plain summary...' } steers content, not just shape.
  5. Schemas ossify. Shared schemas across stages (or across Sub-Workflow Composition boundaries) are interfaces; version them mentally like one.
Implementation
  • Define schemas as named constants at the top of the script, adjacent to the meta block: they are the script’s type layer.
  • additionalProperties: false where downstream code iterates keys.
  • Keep schemas shallow. Two levels (an object with an array of flat objects) covers nearly every recipe in circulation; deep nesting multiplies retry likelihood.
Known Uses

Every schema’d call in this catalog; every recipe in the community library. The pattern is the medium’s idiom, not an option.

Prerequisite-grade for Fan-Out / Reduce / Synthesize workers, Scout-Worker scouts, all Verification verdicts, and Model Routing downgrades (consequence 2 there). Free-text handoffs between stages are the Free-Text Parsing anti-pattern.


Pattern 18: Seen-Set Ledger

Family: Contract Also Known As: Deterministic Memory, The Dedup Register

Intent

Maintain, in plain script state, an exact keyed record of everything encountered across rounds or stages, so that dedup, convergence detection, and “new items only” prompts rest on deterministic memory rather than any agent’s recollection.

Motivation

Agents forget between calls by construction: each is a fresh context. The script does not. A Set of stable keys in the orchestration is perfect memory at zero token cost, and three distinct jobs lean on it: deduplication across a fan (the same release found by four sources), convergence detection across rounds (Loop-Until-Dry’s dry counter is a statement about the ledger’s diff), and exclusion feedback to later agents (“do not re-report these”, rendered from the ledger into the prompt). Asking an agent to “remember what you already found” is asking the one component with amnesia to do the one job the deterministic shell does flawlessly.

Applicability

Any multi-round or multi-source workflow where identity matters. The design work is entirely in the key function; the pattern is otherwise three lines.

Structure
flowchart LR
    R1["round / source results"] --> K["key():<br/>stable identity function"]
    K --> D{"in ledger?"}
    D -- "yes" --> DROP["duplicate - drop"]
    D -- "no" --> ADD["add to ledger<br/>+ pass downstream"]
    LEDGER[("Set of keys<br/>plain script state<br/>zero token cost")] <--> D
    LEDGER -- "rendered digest" --> P["later prompts:<br/>'do not re-report: ...'"]
    LEDGER -- "empty diff streak" --> CONV["convergence signal"]

Figure 2.18 - One structure, three jobs: dedup, convergence, exclusion feedback.

Consequences
  1. Exactness. Membership is not probabilistic; the loop’s stopping condition rests on real state.
  2. The key function is the whole design. Too coarse -> distinct items merge (silent under-reporting, premature convergence); too fine -> paraphrases count as new (loops never dry). Location + normalized-truncated description (${file}:${line ?? '?'}:${desc.slice(0,40)}) is the field-tested compromise for findings; canonical URL for web items; name@version for packages.
  3. Ledger scope must be “seen”, not “kept.” Rejected findings re-enter every round if only survivors are ledgered - Pattern 7’s make-or-break detail, restated here because it is a ledger-scope bug, not a loop bug.
  4. Feedback rendering costs prompt tokens but saves whole redundant rounds; digest the ledger (keys or short titles), never dump full objects into prompts.
  5. The ledger is journal-safe precisely because it is deterministic: replay reconstructs it identically from cached agent results - the determinism contract (1.6) paying rent.
Sample Code
const key = (b) => `${b.file}:${b.line ?? '?'}:${b.desc.toLowerCase().slice(0, 40)}`
const seen = new Set()

const dedupe = (items) => items.filter((b) => {
  const k = key(b)
  if (seen.has(k)) return false
  seen.add(k)
  return true
})

const exclusionDigest = () =>
  seen.size ? `\nAlready reported - do NOT re-report:\n` +
              [...seen].slice(-60).map((k) => `- ${k}`).join('\n')
            : ''
// finder prompt: `${FINDER_PROMPT}${exclusionDigest()}`
Known Uses
  • The bug sweep’s seen set; the newsletter’s URL-keyed dedupe Map; the changelog’s commit register; every Loop-Until-Dry instance by definition.

The memory of Loop-Until-Dry and the dedup of Fan-Out / Reduce / Synthesize’s reduce leg. Enabled by the determinism contract (1.6). Delegating its job to an agent is a small Monolithic Agent.


Pattern 19: Null-Tolerant Collection

Family: Contract Also Known As: Filter-Boolean Discipline, Graceful Casualty Handling

Intent

Treat individual agent death as data loss rather than run failure: every collection point filters null before use, every optional field is accessed defensively, and the script’s arithmetic acknowledges that N launched does not mean N landed.

Motivation

The runtime’s failure semantics are deliberate and uniform: a skipped or dead agent() resolves to null; a throwing parallel() thunk resolves to null instead of rejecting the call; a throwing pipeline() stage drops that item to null and the line continues. This is the right default for a 200-agent run - one casualty must not void 199 results - but it relocates responsibility: the script must expect holes. results.filter(Boolean) before every consumption, review?.findings || [] on every optional path, and denominators that count survivors, not launches. The idiom appears in literally every published recipe (“A failing thunk resolves to null, so filter(Boolean) before use”) because omitting it produces the medium’s most common crash: Cannot read properties of null.

Applicability

Universal. There is no workflow to which this pattern does not apply; it is listed as a pattern rather than a footnote because its second-order discipline - accounting for casualties, not merely surviving them - is routinely missed.

Structure
flowchart TD
    L["N thunks launched"] --> RT["runtime: failures → null,<br/>run continues"]
    RT --> RES["results: values + null holes"]
    RES --> F["filter(Boolean)"]
    F --> USE["consumption: survivors only"]
    RES --> ACC["casualty accounting:<br/>launched − survived"]
    ACC --> LOGL["log() the loss rate"]
    ACC --> POL{"loss above<br/>tolerance?"}
    POL -- "yes" --> ESC["escalate: retry cohort,<br/>flag coverage gap in return"]
    POL -- "no" --> USE

Figure 2.19 - Surviving nulls is table stakes; accounting for them is the pattern.

Consequences
  1. Robustness by default - the run’s promise (“one failure != zero results”) is honored end to end.
  2. Silent coverage erosion is the flip side. An audit that launched 80 checkers and heard from 61 has 19 unaudited items, and a bare filter(Boolean) hides that. For coverage-critical work (the entire Scout-Worker value proposition), casualties are un-checked items and must be reported or retried, not just dropped.
  3. Positional information survives filtering only if captured first. parallel results correspond positionally to inputs; carry the item into the result (.then((c) => ({ ...r, ...c }))) before filtering, or you cannot name the casualties.
  4. Retry is cheap and journal-friendly: re-run the casualty cohort once (items.filter((_, i) => results[i] == null)); completed agents are cached, so a resumed or retried pass costs only the holes.
Sample Code
const results = await parallel(routes.map((r) => () =>
  agent(checkPrompt(r), { label: `check:${r.path}`, schema: CHECK })
    .then((c) => ({ ...r, ...c }))))          // identity captured pre-filter

const survivors = results.filter(Boolean)
const casualties = routes.filter((_, i) => results[i] == null)
log(`${survivors.length}/${routes.length} checked; ${casualties.length} failed`)

if (casualties.length) {                       // one retry pass over the holes
  const retried = await parallel(casualties.map((r) => () =>
    agent(checkPrompt(r), { label: `retry:${r.path}`, schema: CHECK })
      .then((c) => ({ ...r, ...c }))))
  survivors.push(...retried.filter(Boolean))
}
return { checked: survivors,
         unchecked: routes.length - survivors.length }   // honest coverage claim
Known Uses
  • Every filter(Boolean) in every published script; the retry-and-account extension is the hardening step between a recipe and a production audit.

The runtime-failure complement to Structured Output Contract (which handles malformed success). Coverage accounting is what keeps Scout-Worker honest. Its absence has no anti-pattern name; it has a stack trace.


Pattern 20: Parameterized Command

Family: Contract Also Known As: Saved Workflow Interface, Args Contract

Intent

Design a workflow’s inputs as a documented args contract with sensible defaults, then save the script as a named command - turning one good run into a reusable, shareable, version-controlled tool invocable as /name by humans and as workflow(name, args) by other workflows.

Motivation

The save mechanic (press s in /workflows) is trivial; the design question it raises is not: what varies between runs of this orchestration? A newsletter varies by week; an auth audit by directory; a rename by from/to pair; a research run by question. Those are the args. Everything else - the phases, the schemas, the verification policy - is the invariant worth saving. The runtime supports the contract well: invocation input arrives as structured data (“Claude passes the list as structured data, so the script can call array and object methods on args directly”), meta.whenToUse is the natural place to document the shape, and the save-location choice (project .claude/workflows/ - shared, version-controlled, monorepo-aware - versus personal ~/.claude/workflows/) is an API-ownership decision.

For a team, this is where workflows become governance: a saved pr-review is a review policy that runs identically for everyone who clones the repo; a saved auth-check is a release gate whose history is git log.

Applicability

Save and parameterize when the orchestration will recur with varying targets - “a review you run on every branch,” a weekly scan, a per-release checklist. Leave one-shot explorations unsaved; a saved command is a maintenance commitment (its args shape and return shape are now interfaces, per Sub-Workflow Composition consequence 3).

Structure
flowchart LR
    subgraph script ["saved script: .claude/workflows/name.js"]
        META["meta: name · description ·<br/>whenToUse documents args shape"]
        DEF["defaults: const dir = args?.dir ?? 'src/'"]
        BODY["invariant orchestration"]
    end
    H["human: /name on issues 1024, 1025"] -- "structured args" --> script
    W["another workflow:<br/>workflow('name', { ... })"] -- "same contract" --> script
    script --> RET["structured return -<br/>parsed by parents, read by humans"]
    GIT[("git: the policy's<br/>change history")] -.-> script

Figure 2.20 - One contract, two callers. The repo is the registry.

Consequences
  1. Reuse with variation - the orchestration is the asset; args is the aperture.
  2. Dual audience discipline: the same command serves /name humans and workflow() parents, so the return value must be both readable and parseable - structured data plus a summary field is the working convention.
  3. Defaults determine ergonomics. Every arg needs a fallback (args?.dir ?? 'src/routes/'), because args is undefined on a bare invocation and a command that crashes without flags will not be adopted.
  4. Name collisions resolve project-over-personal, and monorepo resolution picks the closest .claude/workflows/ to the working directory - package-local policies shadow repo-wide ones by design, which is either the feature you wanted or a debugging session.
  5. The saved script is reviewable policy. Changes to a shared workflow’s roster (Pattern 13), thresholds (Pattern 12), or gates (Pattern 16) go through the same PR review as any code - the catalog’s Part 1.9 observation made concrete.
Sample Code
export const meta = {
  name: 'triage-issues',
  description: 'Triage a list of issue numbers: reproduce, classify, propose next step',
  whenToUse: 'Run /triage-issues on issues N, M, ... - args is the array of issue ' +
             'numbers, or { issues: [...], repo?: "owner/name" }.',
  phases: [{ title: 'Fetch' }, { title: 'Triage' }],
}

// Normalize both invocation shapes; default to a helpful error, not a crash.
const issues = Array.isArray(args) ? args : (args?.issues ?? [])
if (!issues.length) return { error: 'Pass issue numbers: /triage-issues on 1024, 1025' }
const repo = args?.repo ?? null

const triaged = await parallel(issues.map((n) => () =>
  agent(`Fetch issue #${n}${repo ? ` in ${repo}` : ''}, attempt to reproduce, ` +
        `classify (bug/feature/question/stale), and propose the next step.`,
    { label: `issue:${n}`, phase: 'Triage', schema: TRIAGE })
    .then((t) => ({ issue: n, ...t }))))

const ok = triaged.filter(Boolean)
return { triaged: ok,
         summary: `${ok.length}/${issues.length} triaged; ` +
                  `${ok.filter((t) => t.class === 'bug').length} bugs` }
Known Uses
  • The docs’ /triage-issues example (“Run /triage-issues on issues 1024, 1025, and 1030”); the newsletter’s { weekStart, weekEnd } window; every recipe in the community library reads args with a default; /deep-research’s { question } is the contract consumed by Sub-Workflow Composition parents.

The interface layer of Sub-Workflow Composition and the handoff mechanism of Phase-Gated Staging. Its parameter surface is where Budget-Scaled Depth (the +Nk on the invocation) and Scout-Worker universes (args.dir) meet the user. A saved workflow with no parameters and no documented return is a recording, not a tool.


Part III: Pattern Relationships

The GoF closed their catalog with a diagram showing how the 23 patterns lean on each other. The workflow patterns are even more tightly coupled: the structural patterns are skeletons, the behavioral patterns wrap them in time, the verification patterns gate their outputs, and the contract patterns run through everything.

3.1 The Pattern Map

flowchart TD
    subgraph structural ["Structural"]
        P1["1 Fan-Out / Reduce /<br/>Synthesize"]
        P2["2 Streaming Pipeline"]
        P3["3 Barrier Rendezvous"]
        P4["4 Scout–Worker"]
        P5["5 Sub-Workflow<br/>Composition"]
        P6["6 Worktree Isolation"]
    end
    subgraph behavioral ["Behavioral"]
        P7["7 Loop-Until-Dry"]
        P8["8 Converge-Until-Green"]
        P9["9 Budget-Scaled Depth"]
        P10["10 Phase-Gated Staging"]
        P11["11 Model Routing"]
    end
    subgraph verification ["Verification"]
        P12["12 Adversarial Verify"]
        P13["13 Perspective-Diverse<br/>Verify"]
        P14["14 Judge Panel"]
        P15["15 Cross-Checked Claims"]
        P16["16 Test Gate"]
    end
    subgraph contract ["Contract"]
        P17["17 Structured Output<br/>Contract"]
        P18["18 Seen-Set Ledger"]
        P19["19 Null-Tolerant<br/>Collection"]
        P20["20 Parameterized<br/>Command"]
    end

    P4 -- "generates the<br/>slice list for" --> P1
    P4 -- "feeds items to" --> P2
    P1 -- "its reduce leg is a" --> P3
    P7 -- "is iterated" --> P4
    P7 -- "remembers via" --> P18
    P7 -- "gates fresh finds with" --> P12
    P9 -- "adds economic exit to" --> P7
    P8 -- "promotes to loop condition" --> P16
    P13 -- "differentiates the<br/>skeptics of" --> P12
    P14 -- "applies lens diversity<br/>to generators, like" --> P13
    P14 -- "judging barrier is a" --> P3
    P15 -- "specializes to claims" --> P12
    P15 -- "is Fan-Out plus a gate" --> P1
    P2 -- "write stages use" --> P6
    P6 -- "per-tree gating uses" --> P16
    P5 -- "interface defined by" --> P20
    P10 -- "each stage is a" --> P20
    P10 -- "gateless alternative is" --> P5
    P11 -- "bounded by verifier<br/>strength rule of" --> P12
    P11 -- "stretches" --> P9
    P17 -- "underlies every<br/>schema'd handoff in" --> P1
    P19 -- "keeps coverage honest in" --> P4

Figure 3.1 - The dependency web. Contract patterns (bottom) are load-bearing everywhere; arrows shown only where the coupling is definitional.

Three readings of the map worth making explicit:

  1. Pattern 1 is the root. Fan-Out / Reduce / Synthesize is the shape everything else refines. If you internalize one pattern, make it that one; if a script feels wrong, check whether it is a malformed instance of it.
  2. The verification family is a ladder, not a menu. Test Gate where an oracle exists -> Adversarial Verify where only judgment exists -> Perspective-Diverse Verify when bias, not variance, is the enemy -> Judge Panel when the output is generative -> Cross-Checked Claims when the output is factual. Each rung answers a different epistemic situation.
  3. Contract patterns are invisible until omitted. No script “uses” Null-Tolerant Collection as a design choice; every script either practices it or crashes.

3.2 The Decision Guide

flowchart TD
    START(["I have a task"]) --> Q0{"Needs breadth, verification,<br/>or scale beyond one context?"}
    Q0 -- "no" --> SINGLE["Not a workflow task.<br/>Single session or subagent."]
    Q0 -- "yes" --> Q1{"Are the work items<br/>known upfront?"}
    Q1 -- "no - enumerable in one pass" --> P4A["Scout–Worker (4)"]
    Q1 -- "no - open-ended discovery" --> P7A["Loop-Until-Dry (7)<br/>+ Seen-Set Ledger (18)"]
    Q1 -- "yes" --> Q2
    P4A --> Q2{"Multi-stage processing<br/>per item?"}
    P7A --> Q2
    Q2 -- "yes" --> P2A["Streaming Pipeline (2)"]
    Q2 -- "no - single stage,<br/>then merge across items" --> P1A["Fan-Out / Reduce /<br/>Synthesize (1)"]
    P2A --> Q3{"Do agents write files<br/>in parallel?"}
    Q3 -- "yes" --> P6A["+ Worktree Isolation (6)"]
    Q3 -- "no" --> Q4
    P6A --> Q4{"How do I trust<br/>the output?"}
    P1A --> Q4
    Q4 -- "an executable check exists" --> P16A["Test Gate (16);<br/>iterate as Converge-<br/>Until-Green (8) if fixes interact"]
    Q4 -- "judgment only - findings" --> P12A["Adversarial Verify (12);<br/>diversify lenses (13)<br/>if failure modes span dimensions"]
    Q4 -- "judgment only - generative output" --> P14A["Judge Panel (14)"]
    Q4 -- "factual claims from sources" --> P15A["Cross-Checked Claims (15)"]
    P16A & P12A & P14A & P15A --> Q5{"Stopping condition?"}
    Q5 -- "completeness, unknown size" --> S7["convergence: Loop-Until-Dry (7)"]
    Q5 -- "declared spend" --> S9["Budget-Scaled Depth (9)"]
    Q5 -- "fixed item list" --> S0["the list itself"]
    S7 & S9 & S0 --> Q6{"Human sign-off needed<br/>between stages?"}
    Q6 -- "yes" --> P10A["Split via Phase-Gated<br/>Staging (10)"]
    Q6 -- "no" --> Q7{"Will this recur?"}
    P10A --> Q7
    Q7 -- "yes" --> P20A["Save as Parameterized<br/>Command (20); route cheap<br/>stages via Model Routing (11)"]
    Q7 -- "no" --> DONE(["run it"])
    P20A --> DONE

Figure 3.2 - Seven questions from task to composed design. Contract patterns 17 and 19 apply unconditionally and are omitted from the branches.

3.3 Selection Table

If the task sounds like…Reach forSupported by
”Check every X for Y”Scout-Worker (4) -> fanStructured Output (17), Null-Tolerant (19)
“Digest/summarize many sources into one artifact”Fan-Out / Reduce / Synthesize (1)Barrier (3) at the reduce, schema’d workers (17)
“Migrate/port/rename across many files”Streaming Pipeline (2) + Worktree Isolation (6)Test Gate (16) per item, whole-tree gate at merge
”Find all the bugs / issues / flaky tests”Loop-Until-Dry (7)Seen-Set Ledger (18), Adversarial Verify (12), a cap
”Fix errors until the build passes”Converge-Until-Green (8)Test Gate (16) as oracle, stall detection
”Audit as deep as $N buys”Budget-Scaled Depth (9)reserve margin, budget.total guard
”Research this and I’ll act on the answer”Cross-Checked Claims (15), or workflow('deep-research') via (5)three-way verdicts
”Draft the plan for a decision that matters”Judge Panel (14)angle roster, graft-aware synthesis
”Review every PR the same way”Perspective-Diverse Verify (13) roster, saved via (20)Adversarial gate (12) on findings
”Plan first, then I approve, then execute”Phase-Gated Staging (10)Parameterized Command (20) per stage
”Do X, and X needs a research/verify pass inside it”Sub-Workflow Composition (5)child’s args contract (20)
“The classification stage is 300 easy calls”Model Routing (11)schema retry (17) as the net

Part IV: Anti-Patterns

The GoF documented what to do; the workflow community has already accumulated enough scar tissue to document what not to. Each anti-pattern here names a recurring mistake, its symptom, why it happens, and the correction - which is always one of the twenty patterns above.


Anti-Pattern 1: Barrier Abuse

The mistake. parallel -> transform -> parallel where the middle transform has no cross-item dependency. Batch thinking imported from sequential programming: “first we do all the reviews, then we do all the verifications.”

Symptom. Agent slots idle in /workflows while one slow item finishes its stage; the first stage-2 result appears only after the last stage-1 result; wall clock tracks the sum of slowest-per-stage instead of the slowest single path.

Why it happens. Stages feel sequential because they are described sequentially. But “conceptually separate” is not “synchronized,” and the flatten-or-filter between them almost never needs the whole set.

Correction. Streaming Pipeline (2). Apply the smell test from Pattern 2 to every barrier; keep only those that pass Pattern 3’s three-item justification list (aggregate merge, aggregate branch, cross-item comparison). Nested per-item barriers (skeptic votes on one finding) are fine - it is the inter-stage barrier that must justify itself.


Anti-Pattern 2: The Impatient Monitor

The mistake. Trying to make a workflow wait for the world: an agent polling until a deploy finishes, a stage that watches a log stream for a condition, a loop that re-checks an external system “until it’s ready.” More generally: importing the session-level Monitor tool’s interrupt-driven use case (“tell me when the build finishes”) into the workflow runtime.

Symptom. Agents burning turns on repeated no-op checks; runs whose wall clock is dominated by sleeping-shaped agent calls; scripts that encode wait times as agent instructions (“check again in 5 minutes” - which the agent cannot honor and the determinism contract would not permit the script to time anyway, since Date.now() throws).

Why it happens. The Monitor tool legitimately serves “wait until condition, then react” in a live session, and the reflex is to reach for the same shape inside a workflow. But workflows are bounded computations over the state of the world at run time: agents are workers that must return, the script cannot sleep, and there is no event stream into the runtime.

Correction. Restructure around completion, not waiting. If a stage depends on an external event, end the workflow at that boundary and start the next one after the event - Phase-Gated Staging (10) with the world, rather than a human, as the gate. For “keep working until the check passes” where the check is your own agents’ work, that is Converge-Until-Green (8): the legitimate workflow-native loop. Session-level monitoring belongs to the session.


Anti-Pattern 3: Hidden Clock

The mistake. Smuggling non-determinism into the script: computing “this week” from the current date, shuffling items “for fairness,” generating a random sample of files to audit.

Symptom. The blunt version throws immediately (Date.now(), Math.random(), argless new Date() all throw - the runtime enforces its own contract). The subtle version survives: an agent asked to “pick 20 random files” returns a different set every run, and resume semantics silently degrade because replayed orchestration diverges from journaled reality at the point of stochastic structure.

Why it happens. Time and randomness are ambient in normal programming; the journal’s requirement (1.6) - same inputs, same call sequence - is an unusual discipline.

Correction. Time is data: pass it through args (Parameterized Command, 20 - the newsletter’s { weekStart, weekEnd } exists for exactly this reason) or have one agent resolve “today” and treat its answer as input. Variety is structure: vary prompts by index, not by dice. Sampling is policy: if the audit covers 20 files, let the scout rank and the script slice(0, 20) deterministically - the ranking is judgment (agent), the selection is orchestration (script).


Anti-Pattern 4: Context Flood

The mistake. Returning everything: the script ends with return { allFindings, allDrafts, rawSourceDumps, fullDiffs }, or worse, skips the reduce leg entirely and returns concatenated worker prose.

Symptom. The conversation receives a wall of intermediate material; Claude’s next turn summarizes it (paying again for work the script should have done); the very context-isolation that justified the workflow is undone at the finish line.

Why it happens. Thoroughness instinct - “the user should see everything” - plus forgetting that the return value is the only thing crossing the boundary, so it carries the entire responsibility for being the right thing.

Correction. The reduce leg of Fan-Out / Reduce / Synthesize (1) exists to make the return small and dense: the synthesized artifact, the filtered survivors, the honest counts (Null-Tolerant Collection, 19). Detail is not lost - it lives in /workflows, drill-down included, and in the journal. Return the answer; let the progress view hold the evidence.


Anti-Pattern 5: Mid-Run Conversation

The mistake. Designing a workflow that expects a human decision partway through: an agent that asks a question into the void, a stage that “pauses for review,” a prompt instructing an agent to “confirm with the user before deleting.”

Symptom. Runs that stall on agent permission prompts being used as ersatz decision points; agents “confirming” with nobody and proceeding on their own interpretation; destructive stages executing on a guess about what the human would have said.

Why it happens. Most orchestration frameworks have a human-input step, so its absence is assumed to be an oversight rather than a design position. It is a design position: the script is the plan, agreed before launch; there is no channel for mid-run deliberation (1.7).

Correction. Phase-Gated Staging (10): the decision point becomes a workflow boundary, and the deliberation happens in the conversation - the one place built for it - with the decision carried forward as args. For per-item judgment calls that are discovered mid-run, the needsReview flag idiom (Worktree Isolation, implementation notes): route the item to a report instead of forcing a guess.


Anti-Pattern 6: Unbounded Loop

The mistake. A loop whose only exit is the runtime’s 1,000-agent backstop: a discovery loop with no dry counter, a budget loop guarded on budget.remaining() when no +Nk directive was given (so remaining() is Infinity), a fix loop with no stall detection against oscillating fixers.

Symptom. A run that consumes its way to the agent cap; /workflows showing round 40 of a sweep that stopped finding anything at round 6; the budget.total == null footgun documented in the API itself.

Why it happens. Convergence conditions are the hard part of loop design and the easy part to defer; and remaining() -> Infinity on an uncapped run is a genuinely surprising default.

Correction. Every loop carries three bounds, belt-and-suspenders: an epistemic exit (Loop-Until-Dry’s dry counter, 7, or Converge-Until-Green’s stall detection, 8), an economic exit (Budget-Scaled Depth’s reserve check, 9 - always guarded on budget.total first), and a hard cap (rounds < 10, confirmed.length < 40). The runtime’s 1,000 is a backstop, not a stopping strategy.


Anti-Pattern 7: Free-Text Parsing

The mistake. Consuming agent output as prose: regexes over “the agent usually says PASS or FAIL,” JSON.parse in a try/catch around “please respond in JSON,” splitting on newlines to extract a file list.

Symptom. Intermittent parse failures that read as agent flakiness but are actually contract absence; downstream stages fed garbage that validates as strings; the script’s most fragile lines being string manipulation the runtime offered to eliminate.

Why it happens. Habit from working with raw model APIs, plus not knowing that the schema option validates at the tool layer with automatic retry - strictly stronger than anything the script can do after the fact.

Correction. Structured Output Contract (17) on every consumed output; reserve free text for terminal human-facing prose. If a stage’s output resists schema design, that is usually a sign the stage is doing two jobs - split it.


Anti-Pattern 8: Monolithic Agent

The mistake. A “workflow” of one giant agent: agent('Audit the entire codebase for bugs, verify each one, and write a report') - the whole task delegated into a single context, wrapped in workflow machinery that orchestrates nothing.

Symptom. No fan-out; one agent’s token count dwarfing the run; results indistinguishable from just asking in the session (because that is what it is, plus overhead); none of the coverage, independence, or verification properties the medium exists to provide. The inverse symptom also occurs: hundreds of trivial agents each doing one line’s worth of work, paying spawn overhead for no isolation benefit.

Why it happens. Decomposition is the actual design work, and the workflow syntax does not force you to do it.

Correction. The whole catalog, but concretely: split by the seams the task already has - items (Scout-Worker, 4), stages (Streaming Pipeline, 2), dimensions (Perspective-Diverse Verify, 13), rounds (Loop-Until-Dry, 7) - and grant each agent one focused job with a schema’d deliverable. The right granularity test: an agent should be doing enough work that a fresh context is a feature (isolation, focus), not just overhead. Verification deserves its own agents by construction, never the finder’s self-report (Adversarial Verify, 12; Test Gate, 16).


Appendices

Appendix A: API Quick Reference

ConstructSignatureSemanticsFailure mode
agentagent(prompt, { schema?, label?, phase?, model?, agentType?, isolation? }) -> PromiseOne subagent; schema => validated object, else final textSkipped/dead => resolves null
parallelparallel(thunks) -> Promise<any[]>Concurrent, barrier: awaits allThrowing thunk => null in its slot
pipelinepipeline(items, ...stages) -> Promise<any[]>; stage = (prev, item, i)Per-item streaming, no inter-stage barrierThrowing stage => that item null
workflowworkflow(nameOrRef, args?) -> PromiseChild workflow inline; shares caps + budget; one level deepNested workflow() in child throws
phasephase(title)Opens a progress groupRaces if called inside concurrent stages - use per-agent phase:
loglog(message)Narrator line in progress view-
argsglobalInvocation input, verbatimundefined when none passed
budget{ total, spent(), remaining() }+Nk token targettotal: null uncapped => remaining() = Infinity; agent() throws at the ceiling

Forbidden in scripts: Date.now(), Math.random(), argless new Date() (all throw); filesystem/shell access (delegate to agents); mid-run user input (restructure via staging).

Limits: ~16 concurrent agents (CPU-bound, excess queues) · 1,000 agents/run · resume in-session only · subagents always acceptEdits, inheriting the session allowlist · large-run advisory above ~25 agents or ~1.5M projected tokens.

Triggers: ultracode keyword (human-typed input only, v2.1.210+) · /effort ultracode · /deep-research · saved /<name> commands. Save: s in /workflows -> project .claude/workflows/ (shared, monorepo-aware) or ~/.claude/workflows/ (personal, honors CLAUDE_CONFIG_DIR); project wins collisions. Disable: /config toggle · "disableWorkflows": true · CLAUDE_CODE_DISABLE_WORKFLOWS=1. Model override: CLAUDE_CODE_SUBAGENT_MODEL trumps per-call model:. Cost note: long multi-phase runs benefit from the 1-hour prompt-cache TTL over the default 5-minute (reported 3-6x difference).

Appendix B: Glossary

TermDefinition
Agent (subagent)A fresh model context spawned by agent(): one prompt, one result, tools per the session allowlist.
BarrierA synchronization point where all concurrent tasks must complete before anything proceeds; parallel()’s semantics.
Budget / +Nk directiveA per-turn token target set at invocation; a hard ceiling enforced by agent() throwing.
Convergence conditionA results-derived stopping rule (dry rounds, stall detection) as opposed to a count-derived one.
Determinism contractThe requirement that a replayed script make identical calls in identical order, enforced by banning ambient time/randomness; what makes the journal valid.
Dry roundA discovery round whose entire yield was already in the seen-set.
JournalThe runtime’s record of every agent call and result, enabling in-session resume with cached results.
LensA distinct analytical perspective assigned to one member of a verification or investigation panel.
OracleAn executable check (compiler, test suite, validator) whose output enumerates remaining work or adjudicates acceptance.
PhaseA named progress group in the /workflows tree; declared in meta.phases, entered via phase() or per-agent phase:.
Reduce legThe deterministic middle of Fan-Out / Reduce / Synthesize: flatten, dedupe, filter, rank in plain code.
ScoutAn agent whose sole job is enumerating work items as structured data.
Seen-setDeterministic script state recording every item ever encountered, keyed stably; distinct from the confirmed/kept set.
SkepticA verifier prompted to refute a finding, with the unsure-default set by cost asymmetry.
StallConsecutive repair rounds without strict progress; the honest-exit condition of Converge-Until-Green.
ThunkA zero-argument function wrapping an agent call, deferred so parallel() controls execution.
WorktreeAn isolated git checkout in which a writing agent operates; merged (or discarded) at integration.

Appendix C: Sources

The catalog’s factual claims about runtime behavior, API semantics, and limits derive from the official documentation; the pattern names, sample scripts, and field observations draw on the community sources, all current as of July 2026.

  1. Official Claude Code workflows documentation - code.claude.com/docs/en/workflows. API semantics, determinism rules, limits, triggering, saving, /deep-research, the auth-audit and typecheck-convergence examples, the sign-off-between-stages guidance.
  2. Claude Code launch announcement - claude.com/blog/introducing-dynamic-workflows-in-claude-code.
  3. alexop.dev, “Claude Code Workflows: Deterministic Multi-Agent Orchestration” (May 2026) - the vue-newsletter worked example, primitive deep-dive, pipeline-vs-parallel discipline, quality-pattern condensations, the loop-until-dry seen-set warning, and the Bun Zig->Rust six-day port attribution (Jarred Sumner: “dynamic workflows and adversarial code review was part of what made it possible”).
  4. awesomeclaude.ai/claude-code-workflows (June 2026) - the 24-recipe library from which several Known Uses and sample-code shapes are adapted; the API reference card; /workflows keybindings; enable/disable surface.
  5. buildthisnow.com, “Claude Code Dynamic Workflows” (June 2026) - cache-TTL economics, schema-retry emphasis, pipeline-default rationale.
  6. developersdigest.tech, “Claude Code Dynamic Workflows: The Complete Guide” (June 2026) - script-file location under ~/.claude/projects/, per-run script inspectability.

Version-specific behaviors cited inline: v2.1.154 (feature ships, research preview), v2.1.178 (monorepo-aware workflow save), v2.1.196 (/deep-research unverified-vs-refuted distinction), v2.1.203 (/effort ultracode), v2.1.210 (ultracode keyword restricted to human-typed input).


End of catalog. Twenty patterns, eight anti-patterns, one root shape: fan out, reduce, synthesize - and never trust a finding its author verified.