Pattern taxonomy, agent role combinations, model routing, unit-of-work sizing, review-findings settling, and resilience discipline for Claude Code dynamic workflows. ALWAYS load this skill before authoring or running any Workflow tool script, and ALWAYS load it when the user mentions "workflow" or "ultracode" in any form -- or when the task calls for multi-agent orchestration such as fan-out, tournaments, adversarial verification, triage at scale, ranking large lists, deep verification of cla...
Scanned 9/5/2026
Install to Claude Code
npx -y skills add alex-feel/claude-code-artifacts-public --skill dynamic-workflow-patterns --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Dynamic Workflow Patterns?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/alex-feel-dynamic-workflow-patterns)More formats (shields.io, HTML) on the badges page.
---
name: dynamic-workflow-patterns
description: Pattern taxonomy, agent role combinations, model routing, unit-of-work sizing, review-findings settling, and resilience discipline for Claude Code dynamic workflows. ALWAYS load this skill before authoring or running any Workflow tool script, and ALWAYS load it when the user mentions "workflow" or "ultracode" in any form -- or when the task calls for multi-agent orchestration such as fan-out, tournaments, adversarial verification, triage at scale, ranking large lists, deep verification of claims, or root-cause hunting. Load it again before touching a run that is already launched -- while one is in flight, and before recovering, resuming, or relaunching one that failed or stalled. Do not hand-roll a workflow from memory when this skill applies.
---
# Dynamic Workflow Patterns
The Workflow tool description already teaches the script API, the opt-in rules, and the execution mechanics; every mention of those below is a one-line anchor, never a re-teach.
This skill adds what that description lacks: which pattern to pick, which agent roles to combine for each task family, which model to give each role, how large to cut each agent's unit of work, how to behave between launch and completion, and how to keep a workflow alive through server errors, stalls, and interruptions.
## Why Single Contexts Fail
Pattern choice and prompt design follow from knowing which failure mode the workflow defends against, so diagnose the threat before picking the shape.
**Agentic laziness.** The model declares done after partial progress, for example addressing 35 of 50 items in a review. Counter: the deterministic script, not the model, decides when work is done -- explicit item lists, loop-until-done stop conditions, and a logged record of every dropped item.
**Self-preferential bias.** The model favors its own output when asked to verify or judge it. Counter: assign verification to agents that did not produce the work -- verifiers, refuters, skeptics, and judges who never grade their own attempt.
**Goal drift.** Fidelity to the objective decays across many turns and lossy compactions, dropping edge-case requirements and don't-do-X constraints. Counter: each subagent lives in a short fresh context with the objective restated verbatim in its prompt, the script pins the original goal in args so no compaction ever touches it, and the deterministic script body holds the authoritative item list, bracket, or rule set, which survives compaction because it lives in the persisted script text and is re-read in full on every relaunch, whether or not a single call replays from cache.
### When Not to Use a Workflow
Workflows multiply token cost by the number of agents, so apply the does-it-really-need-more-compute test first: most routine coding tasks show none of the three failure modes, and for them the default harness is cheaper, faster, and just as correct. A routine change does not need a panel of five reviewers.
The test applies per phase, not once per task: a task large enough to earn a wide research fan-out still does not earn a review panel for the three-paragraph edit that falls out of it. Size each phase's agent count to that phase's own delta and risk, and verify small deltas inline -- an ultracode or high-effort session raises the ceiling for phases that need the compute, never the floor for phases that do not.
## The Six Patterns and How to Choose
| Pattern | Shape | Reach for it when |
|--------------------------|-------------------------------------------------------------------------------------|--------------------------------------------------------------------------|
| Classify-and-act | A classifier labels the task or item, code routes on the label | Heterogeneous inputs need different treatment, or routing models by cost |
| Fan-out-and-synthesize | Split into independent pieces, one agent each, barrier, merge | Many pieces would cross-contaminate one window and you need one result |
| Adversarial verification | A separate agent tries to refute each output against a rubric | The producer must not grade its own work |
| Generate-and-filter | Generators produce candidates in volume, a rubric-plus-dedupe filter keeps the best | Quality comes from quantity: naming, design, ideas, taste |
| Tournament | N agents attempt the same task, fresh judges compare pairwise | Competing attempts or ranking beat dividing the work |
| Loop until done | Keep spawning agents until a stop condition holds | The volume of work is unknown up front |
Classify-and-act deserves detail because the tool description never covers it: a classifier agent decides what the task or item IS, then deterministic code routes to specialized agents or behaviors; make the classifier return a structured schema label so routing switches on data and never string-matches free prose, and remember the same move works at the END of a workflow to grade or select output.
Generate-and-filter also gets detail: several generator agents produce candidates in volume, deliberately varied in angle, then a filter step applies a rubric plus dedupe and EXPLICITLY discards the losers -- a visible discard, never a silent drop. Do not use it when every item must be processed; that is fan-out's job.
Tournament gets detail too: N agents attempt the SAME task, each prompted to try a deliberately different approach -- they compete, they do not divide the work -- and a FRESH judge agent runs each pairwise comparison while the deterministic loop holds the bracket, so only the running order stays in context. Pairwise comparative judgment is more reliable than absolute scoring, which is the whole reason the pattern exists. For ranking tasks, keep comparing until the order is complete: the output is a full sorted list from first to last, not only a top-1 winner.
Real workflows chain patterns -- classify, fan out, adversarially verify, synthesize. Quarantine is classify-and-act with a privilege boundary; deep verification is fan-out where each unit is one claim.
## The Role Vocabulary
A role is nothing but an agent() prompt persona, but naming the role in the prompt sets the cognitive frame -- an agent prompted to DISPROVE pushes far harder than one asked to check -- while keeping each context single-purpose.
- **classifier** -- routes work or grades output; keep its output schema a tiny label so routing stays cheap and unambiguous.
- **generator** -- produces candidates or hypotheses; run several with deliberately different angles.
- **worker** -- executes one unit of the task in its own context.
- **verifier** -- checks one output against one rule or rubric; one concern per verifier.
- **refuter** -- prompted to DISPROVE, so a surviving claim means evidence, not agreement.
- **skeptic** -- re-reads each flag asking real violation or false positive; the false-positive filter.
- **judge** -- pairwise comparator or panel scorer; never judges its own attempt.
- **synthesizer** -- merges structured outputs after a barrier; a reduce role, so it holds whole what every producing role saw one slice of.
- **settler** -- the terminal stage that decides which findings are acted on; it verifies each candidate itself and returns the rejected ones with the evidence that killed them, which makes it the one reduce role allowed to duplicate the verification before it.
- **hypothesis agent** -- generates a root-cause hypothesis from one disjoint evidence slice (logs, files, or data) so hypotheses never cross-contaminate.
- **quarantined reader** -- reads untrusted content with read-only tools and no privileges; emits a structured summary only.
- **trusted actor** -- holds the privileges; acts on summaries, never on raw untrusted content.
- **claim extractor** -- decomposes a document into atomic checkable claims.
- **claim checker** -- verifies exactly one claim against sources.
- **source auditor** -- audits source quality, not the claim itself.
## Use-Case Playbook
- **Migrations and refactors.** Scout inline to discover the worklist, then one worker per fix in worktree isolation, an adversarial reviewer per fix, then merge; instruct workers to avoid resource-heavy commands (full builds, container spins) so parallelism stays high on one machine.
- **Deep research.** Fan out searches across modalities, fetch sources, adversarially verify claims, synthesize a cited report; the same shape works beyond the web -- compiling a status report from team chat history, or researching how a feature works by exploring a codebase in depth.
- **Deep verification of factual claims.** A claim extractor identifies every factual claim, one claim checker per claim verifies it against sources, an optional source auditor checks that each source is itself high quality, and the results merge into a verified report; each claim flows independently through its checker and auditor stages (per-claim isolation prevents cross-claim contamination), and the final report merge is the only barrier.
- **Sorting and ranking large lists.** Pairwise tournament (fresh agent per comparison, deterministic loop holds the bracket) or bucket-rank in parallel then merge; 1000+ rows neither fit one context nor survive absolute scoring, while comparative judgment holds; the deliverable is a full ranked list.
- **Rule adherence.** One verifier agent per rule over the diff, each with a clean context, because rule blending is why single-context rule checks miss; flagged lines go to a skeptic who re-reads each flag asking real violation or false positive; only confirmed violations reach the output.
- **Rule mining (the reverse direction).** Mine recent sessions and review comments for corrections the user keeps making, cluster them with parallel agents, adversarially verify each candidate rule (would it have prevented a real mistake?), and distill the survivors into durable memory rules.
- **Root-cause investigation.** Hypothesis agents each fed a disjoint evidence slice (separate agents for logs, files, data) so no single narrative forms, then a panel of verifiers and refuters per hypothesis until one theory survives the evidence; applies beyond code -- sales drops, pipeline failures, any post-mortem.
- **Triage at scale.** The quarantine composition (next section), run continuously.
- **Exploration and taste.** Generate-and-filter against an explicit rubric -- elicit the rubric from the user first; the task completes when the review agent says the criteria are met; order or select finalists via tournament.
- **Lightweight evals.** Parallel attempts in worktrees, then comparison agents grade the outputs against a rubric -- for example evaluating and refining a just-built capability against fixed criteria.
- **Model and intelligence routing.** A classifier agent researches actual complexity BEFORE routing; see Model Routing below.
When the user names a pattern or roles in the request, honor them; when the request is vague, pick the composition from this playbook.
## The Quarantine Security Pattern
Backlog content -- support tickets, bug reports, user feedback -- is untrusted and may embed prompt injection aimed at whoever reads it.
Quarantine zone: reader agents, one per item, run with read-only tools and no privileges; they read the untrusted content and classify it, and a dedupe step checks each item against what is already tracked.
Only structured summaries cross the boundary out of quarantine -- raw untrusted content never does.
Trusted zone: a single high-privilege actor agent acts on the summaries and never sees raw content; when an item is fixable it attempts the fix and opens a PR, otherwise it escalates to a human; pair the whole workflow with recurring-interval execution to run continuously.
The reason this works: readers of untrusted content hold no privileges, so prompt injection in that content can never reach high-privilege tools -- the summary boundary is the trust boundary, and the security boundary is the workflow structure itself, not model vigilance.
## Reviewing an Artifact: Scope the Lenses, Then Settle the Findings
Reviewing is two problems, and the roles above only solve the first. Lenses, refuters, and skeptics PRODUCE findings; a separate terminal stage decides which of them are ACTED ON, and that stage is where the expensive failure lives.
**A finding is a hypothesis until something independent confirms it, and applying a false one costs more than missing a true one.** A missed defect leaves the artifact exactly where it already was, while an applied false finding converts correct content into wrong content -- and it arrives wearing the authority of a review, which is precisely when scrutiny relaxes. The asymmetry compounds because a finding that survives into a fix is the one nobody re-examines afterwards. Seen in practice: a lens returned a confident must-fix asserting that a documented sort order had been stated backwards, and supplied replacement wording; the settling stage killed it by reading the upstream source and running a build, and applying it would have put a false claim into a reference whose whole purpose was correcting false claims. That review's other findings were genuine and were applied, so the lesson is not that lenses are unreliable -- it is that nothing downstream re-reads whatever the settler promotes.
Give the settling stage the standing and the tools to VERIFY rather than only to rank: it must be able to run the build, read the source, or re-derive the claim itself, which means real tools and a prompt that says checking is its job. Make rejection a first-class output -- the contract carries an explicit rejected set with the evidence that killed each dismissed finding, so rejecting reads as a normal result rather than as a sign the review failed. Promote nothing on plausibility: a finding the settler could not check goes out at a lower tier with its uncertainty named, never as a must-fix. And route the stage by the same chain test the tier section uses, because a settler nothing re-checks is chain-critical by construction.
Bound the verification rather than dropping it, since the stall risk behind one-phase-one-job is real: verify only the findings that would CHANGE what is acted on -- the must-fix candidates, and anything two lenses contradict each other about -- and let agreed, low-stakes findings through on the evidence they already carry. That keeps the settler's tool phase proportional to the decisions it is actually making instead of to the volume it received.
**Scope the lenses so consecutive rounds do not inherit one blind spot.** A round pointed at what just changed goes blind to everything that did not, so the defects that survive longest live in the code nobody edited because nobody looked. Keep at least one lens unscoped, told to read the whole artifact as if for the first time and to prefer ordinary spellings over exotic ones: across five rounds on one security-guard parser, every scoped round walked past an ordinary one-liner that defeated all of its guards and had been broken from the start, and only the unscoped lens found it. Point the LAST round at the fixes the earlier rounds wrote, because a fix is fresh code written under the pressure of a finding and is the one part no round has examined -- a later review reached zero findings only after doing exactly that, having already caught a one-line bug introduced by an earlier round's own fix. Tell every lens that reporting nothing is a good outcome, and stop on consecutive empty rounds rather than on a fixed count.
## Model Routing
Fan-out multiplies token cost by width, so concentrate intelligence where judgment concentrates: routing spends tokens where they buy quality and never saves them at the price of a wrong answer. Two rules make that concrete, and both are absolute -- every agent() call carries an explicit model option, and a workflow routes only within the permitted three-tier menu: sonnet and opus carry the run, and fable is reserved for its chain-critical stages.
**Explicit always, inherited never.** An agent() call with no model option does not pick a sensible default; it silently inherits whatever the session's main loop happens to be running, which leaves the fan-out's cost and capability invisible in the script, flips both the moment the user switches session model, and hands a top-tier context to roles that only read a file. Omitting the option is therefore a defect, not a default -- and it stays a defect in a session on a cheaper tier, where the same silence quietly starves a judgment role instead. This standing rule overrides the Workflow tool description's advice to omit the option and inherit.
Inheritance also couples every agent in the run to a single point of failure: when the session's own tier becomes unavailable mid-run, every inheriting agent dies with it at once, and the journal shows a run-wide wipe -- no result event anywhere -- rather than the isolated casualty a badly cut unit produces. An explicitly routed script localizes that blast radius, because only the roles named on the vanished tier fail while the rest keep returning, and the reroute is then a one-word edit to a tier the script already names instead of a guess about what the session happened to be running.
**Three tiers, and the top one is earned per stage, never per task.** For most units the routing question is binary -- is this output judgment-bearing or mechanical -- and sonnet versus opus answers it, which keeps every call decidable at a glance and the cost of a fan-out predictable. haiku stays off the menu, and that closure is categorical rather than economic: sonnet is the floor, because haiku's savings are not worth a wrong label or a missed finding in a stage everything downstream depends on, and no differently-shaped unit satisfies an exception that does not exist. fable, the frontier tier, sits above opus and is admitted by a different question entirely -- not how hard the unit is, but what happens downstream when its output degrades. Route a role to fable only at a chain-critical stage: a point in the run's information-transfer chain where a lost detail, a misinterpretation, or a wrong or incomplete conclusion propagates irreversibly, because nothing downstream re-reads the source material that would expose it. Cost disciplines fable's width, not its gate: a frontier price multiplied across a fan-out's bulk is misrouting, while the same price on the one or two calls the run's entire output funnels through is the cheapest insurance the run can buy.
- **sonnet, the default and the workhorse** -- classification labels, dedupe checks, pairwise comparisons, quarantined readers spawned in bulk, standard workers, verifiers, refuters, readers, and claim checkers. Each generation's sonnet commonly lands near the previous generation's opus on agentic work, so bulk roles lose almost no quality here while costing a fraction. Start every role here and move it up only for a stated reason.
- **opus, the expert tier** -- synthesis, judging, trusted acting, ambiguous taste calls, and the hypothesis or root-cause stage the whole run's value rides on: the roles where a wrong verdict costs real rework downstream. Escalate a role to it when sonnet, given full context and a well-scoped unit, would still judge wrongly.
- **fable, the apex tier, reserved for chain-critical stages** -- the few points where everything the run learned funnels through one context and a defect there is unrecoverable: the final synthesis that becomes the deliverable, the terminal judge whose verdict nothing re-checks, the hand-off that compresses a finished run's results into the args a continuation workflow will treat as ground truth. The admission test interrogates the chain, not the unit: if this stage dropped a caveat, misread a finding, or drew a wrong or incomplete conclusion, would any later stage catch it? When a downstream verifier, judge, or re-read exists, the stage is not chain-critical and opus is its ceiling; when nothing downstream would catch the degradation, fable is what keeps the hand-off lossless. Expect one or two fable calls in a well-shaped run: a script whose fable count grows with its fan-out width has mistaken unit difficulty for chain criticality and is misrouted.
Structure is the cheaper quality lever, and it substitutes for tier: when a fan-out moves its bulk roles down to sonnet, spend part of the savings on an adversarial stage the run did not have before, because a sonnet producer paired with an independent sonnet skeptic beats a single unverified agent of any tier -- a second context catches what the first was blind to, which no amount of capability inside one context ever does. Read a tier downgrade as the prompt to add verification rather than as a quality cut to absorb, and treat any unverified single-agent phase as under-designed no matter which tier it runs on. The fable gate is this same principle read backwards: where a verification structure can catch a stage's defect, add the verifier instead of raising the tier; fable is for the stages where no downstream structure exists to do the catching.
The tier is a capability dial, not a rescue: when the failure is mechanical instead -- skipped items, exhausted exploration, an agent that never returned -- fix the unit's scope and budget (next section), because a higher tier does not rescue an oversized unit, it only burns longer before dying. That ordering binds hardest exactly where the tier matters most, so shape a chain-critical stage before routing it: a run whose apex-routed synthesis was killed on every one of its attempts lost the entire deliverable the tier was bought to protect, and the stage failing all of them failed the run.
The model option takes Claude Code's model aliases, and a workflow uses exactly three of them -- sonnet, opus, and fable -- each resolving to the current recommended model of its tier, so a script names the tier and stays current as models advance; never pin dated model IDs inside a workflow script.
A model override is validated against session-level settings the script never sees -- the effort level and the thinking configuration -- and tiers differ in which combinations they accept, so a routed tier can be rejected at launch with an instant, zero-token 400 even though the session's own model runs fine. That rejection is deterministic -- retrying the same route fails identically -- so treat any instant zero-token failure as a configuration rejection, never as a dead server, and reroute the role to another permitted tier, preferring one that has already succeeded in this run. Dropping the option to inherit is not an available fallback: when every permitted tier is rejected, stop and report the configuration mismatch rather than launching agents whose tier nobody chose.
fable's availability is never a given, and the routing discipline must survive its absence. A plan may not carry the tier at all, fable's own usage limits are separate from the general ones and can be exhausted independently while everything else keeps running, and other causes produce the same surface -- a rejection or a dead call where a routed agent should have launched -- so from inside a run the cause is mostly undiagnosable and never worth diagnosing. The response is uniform whatever the reason: opus is fable's designated substitute, the adjacent tier, so a chain-critical stage whose fable call is rejected or denied runs on opus rather than dying, stalling the run, or silently dropping the stage -- and the substitution is logged, so the final report can say which tier actually produced the deliverable. Where the shape allows, compensate the downgrade structurally: a chain-critical stage is by definition one nothing downstream re-checks, so when it must run below the apex tier, adding a downstream re-read or verifier it did not have restores by structure part of what the tier would have bought. And unavailability is an event, not a verdict: limits reset and plans change, so a failed fable call downgrades that call, never the tier -- the next run routes fable again wherever the chain test earns it, and a template is never edited to hard-code the downgrade.
Because every call names its tier, the session's own model never leaks into the fan-out: the orchestrator's tier governs the main loop -- it authors the script, reads every result, and writes the final synthesis -- while the script's model options alone govern the agents, which is what keeps a wide fan-out from silently paying top-tier rates to read files. Grep the script for agent( before launching and confirm every hit carries a model, in the same pre-launch pass that hunts bare await agent( in the resilience section below; a call missing its tier is the same class of defect as an unwrapped await.
The routing-by-research move: a classifier agent first investigates the task's actual complexity -- how many files the module spans, the shape of the codebase -- and only then routes the work to sonnet or opus, because complexity is invisible from the prompt alone: "explain how the auth module works" can be a cheap task or a hard one depending on what the classifier finds. When the two workhorse tiers genuinely tie for a judgment role after that investigation, break the tie upward to opus, because a wrong verdict costs more than the tokens sonnet saves; a tie on a bulk mechanical role breaks downward to sonnet, because opus buys no quality there. fable is never a tie-break destination: uncertainty about a unit's difficulty argues for opus, and only the chain-criticality test -- degradation nothing downstream would catch -- admits the apex tier.
## Sizing the Unit of Work
Patterns decide how agents are arranged; sizing decides what one agent's unit of work contains and how much of it the agent may hold. Both stall deaths and hour-long wall-clock tails trace to badly cut units far more often than to models or patterns, so shape the unit before tuning anything else.
- **Code does the mechanical part; the agent keeps the judgment.** Pairing every failure in a log with what followed it is a deterministic join a script performs perfectly in a second, while an agent performs it slowly, partially, and at stall risk. Precompute joins, correlations, and groupings into the input, and hand the agent only the interpretation.
- **Split by the data's natural structure, never by count.** Cutting an overloaded task into groups the data itself suggests -- failure families, modules, time windows -- gives each agent a self-contained set and an independently useful answer; cutting the same work in half gives two agents that each lack context.
- **Put the exploration budget in the prompt, in words.** One sentence -- use pointed greps instead of reading large files whole, stay within about 25 tool calls, and a complete answer with three grounded findings beats an exhaustive search that never returns -- is the difference between an agent that finishes and one the runner kills.
- **Cap the output in the prompt and in the schema descriptions** -- characters per field plus maxItems on every array -- because an unbounded structured answer is itself a stall mechanism: one long, silent generation. "An unbounded answer is a failed answer" belongs in any prompt whose schema contains an array of rich objects. Keep prose-length caps OUT of the schema's validation constraints: a hard maxLength on a free-text field is a death spiral, because character arithmetic is the one correction a model cannot perform -- it shaves a few words per attempt, stays over the cap, and burns the harness's entire StructuredOutput retry budget on an agent whose work was already complete and correct. Validation-enforced constraints are for what a pointed retry can actually satisfy -- maxItems, enum, required, integer bounds -- since a structural error names the offending property and corrects in one attempt. When a string cap must be hard, set it at 3-5x the length the description asks for, so it catches runaway generation rather than a diligent answer a few hundred characters over.
- **Keep every stage emitting, because silence is what the stall detector measures.** The timer resets on progress events rather than on effort, so the agent likeliest to be killed is often the one doing the most valuable work: a very large prompt followed by a long tool-less synthesis is a single silent generation with nothing to reset the clock, which is exactly the shape of a final design or synthesis stage. Split that stage into retrieve-then-write so the retrieval half emits tool calls and the writing half starts from a short prompt; pass bulk evidence by reference -- a path, or an id the agent fetches from durable storage itself -- instead of inlining it, which shortens the prompt and buys a tool call in the same move; and prefer several short agents to one long one, because every retry restarts its agent from zero and a short agent loses less when that happens. When a stage's long silence is legitimate work rather than a badly cut unit -- a build, a full test suite -- also widen that call's stall window with the `stallMs` option the resilience section covers, so the default window does not price the stage's honest quiet as death.
- **One phase, one job -- for as long as another phase still follows.** Merge deduplicates, verify refutes, synthesize ranks: a merger also told to verify findings against the code becomes the largest input, the longest tool phase, and the longest emission in the run at once -- three stall risks stacked -- and it duplicates the adversarial phase that follows it. Duplication is what carries that rule, so the rule expires with the phase it names. Where the reduce step is TERMINAL and its output is what gets acted on, there is nothing left to duplicate, and refusing to verify there means nothing verifies at all: the chain test the routing section applies to tiers applies here to jobs, and a stage nothing downstream re-checks needs the verification built in. Bound it instead of dropping it -- a terminal settler checks only the findings that would change what is acted on, which keeps one job's worth of tool calls in a phase that has earned two.
- **A fan-out closes on its slowest unit, not its median.** When most units finish in minutes and one runs for an hour, the phase runs for an hour. Equalize unit sizes across a fan-out: one under-split unit forfeits the wall-clock benefit of splitting all the others.
- **Merge input grows with fan-out width, and nothing caps it.** Width is bounded by the concurrency limit; the reduce step's input is not, so N agents each emitting a few thousand tokens of findings hand the synthesizer N times that, and the barrier that made the fan-out safe makes the reduce step the largest single context in the run. Project each result down to the fields the merge actually needs, merge in groups and then merge the group outputs, and give every reduce step a deterministic identity fallback -- plain concatenation of its inputs -- so a dead merger degrades the answer instead of destroying the run.
- **Verification width is a product you do not directly choose: findings times verification concerns.** You pick the number of workers, but the data picks the number of findings, and five workers returning 200 findings spawn 600 verifiers at three concerns per finding. Bound data-derived fan-out explicitly -- cap it or filter before it, and log what the cap dropped -- and budget the run by expected finding count, never by worker count.
- **Name the operator's own traffic.** When the corpus under analysis contains traffic the operator generated while testing -- deploy probes, smoke checks -- name it in the shared context and exclude it explicitly, or every behavioral statistic the run produces is poisoned by the measurement itself.
## Resilience: Surviving Server Errors, Stalls, and Interruptions
Two different failure surfaces reach the script, and they behave differently. A terminal API error -- an HTTP 529 overloaded, a 502 -- surfaces as a null return from agent() after the harness exhausts its own retries, and parallel() and pipeline() convert a thrown thunk to null, so inside them a dead worker costs one item. A stalled agent is the other surface: when an agent emits no event for a few minutes, the harness aborts and retries it internally several times over, and once those attempts are spent the call THROWS -- a bare await agent() in the script body lets that exception escape and destroy the whole run, including every sibling result already paid for. An agent can even complete all of its work and still die returning the result when the connection drops at the last step, so a null does not mean the attempt was worthless, only that its output never arrived.
The stall is the more expensive surface because it is invisible while it happens: through the internal retries the run looks identical to a healthy long-running phase, no null ever reaches a retry helper because the agent never returns, and one badly scoped agent can consume an hour of wall clock and millions of tokens before the run fails or limps on. Design so the agent cannot stall -- bound its input, its output, and its exploration (see Sizing the Unit of Work above).
The detector behind it works on silence, not on effort, and its window is set per call: agent() accepts an undocumented `stallMs` option (milliseconds), the default is a hardcoded 180000 ms, an attempt whose window elapses with no streamed event is aborted, the harness retries up to five more times -- each attempt opening a fresh context with the same full window, carrying nothing over -- and only then does the call throw `agent stalled on all 6 attempts (no progress for 180000ms each)`. The timer re-arms on streamed progress events, throttled to one re-arm per min(window*0.1, 1000) ms, and is cleared outright when an assistant message carrying tool calls arrives, staying unarmed until the next streamed event; what the stream emits during tool execution or a long silent generation is unestablished. The `CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS` environment variable never reaches this detector -- its one read site is the separate stall watchdog for background subagents, default `600000` milliseconds -- so raising it changes nothing for workflow agents, however plausible the name makes that look. All of this was read out of the workflow runner in the Claude Code v2.1.170 binary; none of it is documented, so any value can move between versions -- re-verify against the running build before leaning on the exact numbers.
Set `stallMs` deliberately on the stages whose long quiet is legitimate work -- a build, a full test suite, a large retrieval -- and know what it does not buy: a wider window keeps the harness from killing a healthy quiet agent, it does not make an oversized unit healthy, so shape the stage first and widen the window second. Three mechanics reward knowing. The option is excluded from the resume-journal cache key, so adding or raising it while repairing a stalled run invalidates none of the results already cached. A stall abort that lands after a schema agent's StructuredOutput was already captured is converted into a success that keeps the result, so a late stall on a schema-bearing agent costs nothing. And zero, though it disables that call's detector entirely, is not the safe extreme it looks like: a disabled detector turns a genuinely wedged agent into an unbounded wall-clock sink, and it makes the runner's separate throttle heuristic -- an almost-empty response arriving slowly -- fire more eagerly, so a bounded window sized to the stage's real quiet stretch beats both extremes. The cost of getting this wrong is what it always was -- one design-synthesis stage died on all six attempts and took its whole workflow down after roughly an hour and millions of tokens, having returned nothing -- and the failure text stays diagnostic rather than descriptive: the attempt count is what the harness spent, never what the agent needed.
Grep the script for await agent( and await tryAgent( before launching; in practice the hits are exactly the reduce steps -- merge, cluster, synthesize, final judge -- the most expensive and latest agents in the run. A bare await agent( outside parallel() or pipeline() is a single point of total failure: wrap it in a retry helper that catches the throw. A wrapped call no longer throws but still returns null on final failure, so give its result the deterministic identity fallback from the sizing section.
Make every retry strictly cheaper than the last: reduce the scope, lower the tool-call budget, and say so in the retry note, because an identical retry of a task that failed on size fails the same way, and it multiplies with the harness's own internal attempts. Retry only what is transient: a deterministic rejection -- an invalid model-and-settings combination, a schema the endpoint refuses -- repeats exactly on every attempt, so change the failing parameter instead of retrying it. Keep the first attempt's prompt and opts byte-identical to the plain call so a resumed run replays it from cache, and give every later attempt a distinct label suffix and retry note so it gets its own cache identity. This stays resume-safe because the retry decision depends only on prior agent results, so control flow remains deterministic and cache-replayable.
Apply null discipline everywhere: filter(Boolean) after every parallel or pipeline harvest, and null-guard every property access on agent results (result?.field), because one dead agent must never crash the script and destroy all sibling work.
Two shapes of loss reach a harvest, and where the mapping SITS decides whether they stay two problems or collapse into one. A call that THROWS never reaches the mapping: the rejection leaves the thunk, and parallel() substitutes a null ELEMENT for the object the mapping would have built. A call that RETURNS nothing does reach it, so the mapping dutifully wraps the null and passes on a well-formed object carrying a null FIELD. Map inside the thunk -- the single-argument .then the tool description's own examples demonstrate -- and you owe two guards from then on, a rejection handler and a null check, because filter(Boolean) drops the first shape while the second sails straight through it. Map AFTER the harvest instead and both losses arrive as the same null element, which one filter(Boolean) covers; parallel() preserves input order, so an index re-pairs each survivor with its input.
```javascript
// WRONG: the mapping sits inside the thunk, so the array only LOOKS like {...f, verdict} objects
const checked = await parallel(findings.map(f => () =>
agent(verifyPrompt(f), { label: `verify:${f.id}`, model: 'sonnet', schema: VERDICT })
.then(v => ({ ...f, verdict: v }))))
const confirmed = checked.filter(c => c.verdict.real) // a throw left null here, a null return left verdict null
```
```javascript
// RIGHT: harvest first, so both losses are null elements; then map, and account for what died
const rawChecks = await parallel(findings.map(f => () =>
agent(verifyPrompt(f), { label: `verify:${f.id}`, model: 'sonnet', schema: VERDICT })))
const checked = rawChecks.map((v, i) => (v ? { ...findings[i], verdict: v } : null)).filter(Boolean)
if (checked.length < findings.length) log(`verify quorum ${checked.length}/${findings.length}`)
const confirmed = checked.filter(c => c.verdict.real)
```
After every barrier, check quorum -- how many results arrived against how many you launched -- and when a required input is missing, re-run it or stop loudly, never continue silently on partial inputs. And notice that filter(Boolean) does not merely drop a dead voter -- it silently rewrites the decision rule: a majority-of-three threshold written as >= 2 becomes unanimity-of-two when one voter dies. Express thresholds relative to the surviving quorum, and carry the quorum into the result so a later reader knows how many voters actually spoke. An item whose voters ALL died is a third state, not a refutation: a survival test like total > 0 && real >= 2 silently files the zero-verdict item with the refuted ones, so carry it separately as unverified and re-run it, because a claim nobody judged is not a claim that was judged false.
Worktree isolation adds a silent input hazard of its own: the worktree an isolated agent receives can be based on a different commit than the branch the session is working on -- observed in practice as every worktree snapshotting the last PUSHED merge commit while the working branch sat many commits ahead -- so a finder or verifier that trusts its checkout silently audits stale code and returns confident findings about defects already fixed. The countermeasure costs one line: state the expected base commit hash in every isolated agent's prompt and make its first action verify git rev-parse HEAD against it, reporting the mismatch loudly instead of proceeding when they differ. Treat a later implementer's "this is already fixed at HEAD" as the detection signature that a find or verify phase ran on a stale base, and re-verify any finding produced there against the real tree before spending a fix unit on it.
Accumulate partial results: push each completed item's output into a results array as it finishes so completed work survives any later failure, and design each phase's output as the accumulated survivors, never an all-or-nothing computation. Log every dropped item with its identity, because silent truncation is indistinguishable from completion and reintroduces agentic laziness at the script level.
```javascript
async function tryAgent(prompt, opts, attempts) {
for (let att = 1; att <= attempts; att++) {
const toolBudget = 16 >> (att - 1) // halves per attempt, so every retry is strictly cheaper
let r = null
try {
r = await agent(
att === 1 ? prompt : `${prompt}\n(Retry ${att} after a failure: reduce scope below the previous attempt -- deliver the core result in at most ${toolBudget} tool calls, keeping every field short.)`,
att === 1 ? opts : { ...opts, label: `${opts.label}:retry${att}` },
)
} catch (err) {
log(`${opts.label}: attempt ${att} threw: ${String(err).slice(0, 200)}`)
}
if (r) return r
log(`${opts.label}: attempt ${att} of ${attempts} produced no result`)
}
return null
}
const results = []
const dropped = []
const raw = await parallel(items.map(it => () => tryAgent(promptFor(it), { label: `work:${it.id}`, model: 'sonnet' }, 3)))
raw.forEach((r, i) => (r ? results.push(r) : dropped.push(items[i].id)))
if (dropped.length) log(`quorum ${results.length}/${items.length}; dropped: ${dropped.join(', ')}`)
const rawVotes = await parallel(refutePrompts.map((p, i) => () => tryAgent(p, { label: `refute:${i}`, model: 'sonnet' }, 2)))
const votes = rawVotes.filter(Boolean) // three refuters launched; maybe fewer spoke
const refuted = votes.filter(v => v.refuted).length
const unverified = votes.length === 0 // nobody judged it: re-run it, never file it with the refuted
const survives = !unverified && refuted < Math.ceil(votes.length / 2)
const verdict = { survives, unverified, quorum: votes.length, refuted }
```
Every guard above protects a phase; the terminal reduce and the return protect nothing, and they run exactly once, after every token in the run has been spent. An exception in that last block discards the whole return value however carefully each phase accumulated, which makes it the most expensive line of code in the script and the one least likely to have been exercised. So the terminal block must be TOTAL: no unguarded dereference, no assumed shape, no field read from an element the harvest could have left null. Better still, do not write a fragile one at all -- return the raw harvested results and perform any join that can fail outside the script, in the conversation, where a type error costs one edit instead of the entire run. Project that return down to what its reader actually needs, though, because the completion payload has been seen to arrive truncated, and the journal is where the full per-call values keep waiting for whatever the return had to leave behind. A join belongs inside the script only when a later phase consumes its output; a join that merely shapes the deliverable belongs after the run.
Checkpoint at phase boundaries by keeping each phase a pure function of prior agent results, so a crash in phase three leaves phases one and two replayable from cache -- replayable, never guaranteed, since one failed attempt among them is a hole the replay cannot fill. For a phase expensive enough to hurt twice, additionally have each agent persist its own result durably as it completes -- a file at a known path, or a context-server entry -- so the phase is recoverable even where the replay cache is not.
Never kill a running workflow to inject new information. New knowledge almost always changes the cheap final phases -- the panel, the synthesis -- while killing re-runs the expensive gathering phase, so the cost and the benefit land on opposite ends. Let the run finish, then feed its output plus the new information to a short follow-up workflow.
Guard budget-scaled loops on budget.total being set, because it is not set when the user gave no cap, and inside long loops check budget.spent() to stop cleanly and emit accumulated results before the hard ceiling makes agent() throw.
### Recovering a Failed Run
Before recovering, resuming, or relaunching a run that failed or stalled -- the frontmatter's own trigger for reloading this skill -- read recovering-a-failed-run.md in full: the harvest-vs-resume decision rule, journal and transcript reconciliation, the `[Request interrupted by user]` stall-signature trap, the cache-identity and checkpoint-encoding rules, and the nine-scattered-failures replay-cost example all live there, intact.
### The Mutating-Agent Replay Hazard
Classify every agent as read-only or shared-state-mutating at authoring time, before writing its prompt. The recovery calculus differs in kind between the two, not merely in degree: an unintended replay of a read-only agent re-buys tokens for work whose result is thrown away, while an unintended replay of a mutating agent -- one that edits a shared git branch or worktree, writes to a database, or calls an external API with side effects -- re-applies its mutation onto state its own earlier, completed run already mutated. The prefix-shaped cache-invalidation hazard -- an edited or failed call invalidates every call journaled after it, across every parallel chain -- turns this into a live risk on every recovery: repairing a partially-failed run by editing the failed calls' prompts and relaunching with a resume path can silently push a later-journaled, already-completed mutating call back onto the live path, where it re-executes with a prompt written for a clean slate against state its first run already changed.
Write every mutating agent's prompt resume-aware and idempotent by construction, never assuming a clean slate. Its first action must assess the current shared state relevant to its own step -- for example the git status and diff of the exact files that step owns, or a durable record of whether its write already landed -- and then continue from whatever that assessment finds: skip work already done, finish work left partial, and never blindly re-apply a mutation whose precondition may no longer hold. This makes an accidental second execution a no-op or a safe continuation instead of a corruption.
Never repair a partially-failed mutating run by editing prompts and relaunching with a resume path when any edited or failed call precedes a completed call in the journal. For mutating workflows the recoverable state does not live in the journal at all -- it lives in the shared state itself (per-step durable records, the worktree, the database rows) -- so the journal-harvest recovery pattern does not even apply here. Author a fresh continuation workflow instead: one keyed to those durable state records rather than to journal cache keys, whose prompts open by reading the durable record and the current shared state for each step and proceed only from what is actually still missing.
If a run shows signs of unintended mutator re-execution -- a new transcript appearing for a cache key that already carries a completed result, or a mutating agent starting work its prompt's own state assessment should have shown was already done -- stop the run immediately rather than letting it finish. Then verify every completed step's actual on-disk or persisted state against its durable record before deciding how to continue, because the run's own progress reporting cannot be trusted once a mutator may have executed twice.
## After Launch: Completion Is Delivered, Never Awaited
The Workflow call returns as soon as the run starts, the runtime executes the script in the background while the session stays responsive, and when the run finishes the harness injects a completion notification carrying the result into the conversation on its own, waking the model for the next turn. Delivery is push, never pull: nothing the model does makes the report arrive sooner, and the same push contract covers every background primitive -- background subagents and backgrounded Bash commands equally notify on completion.
So after launching a run, either continue genuinely independent work or end the turn with a short status note that the workflow is running; an ended turn is the correct idle state, because the completion notification resumes the conversation automatically the moment the run finishes. Waiting is not an activity the model performs -- it is the absence of one, so "the run is still going, I will wait" is a correct thing to say and then STOP, and becomes a defect the moment anything is executed to enact it.
Never simulate waiting -- no Bash sleep timers, no watcher subagents dispatched to wait for the run, no status-polling loops -- because a fake wait does not merely waste tokens, it DELAYS the very result it claims to await. Asynchronous output reaches the model at a turn boundary, and a blocking foreground command holds that boundary shut: the run still finishes on its own schedule, but nothing happens with its result until the timer expires, so every second the timer outlasts the run is dead time added in front of the answer. The timer also cannot be sized, and not merely because the finish time is unknowable in advance -- a foreground command dies at its own timeout, which is measured in minutes rather than hours and is configurable rather than fixed, so any run outlasting it guarantees a chain of sleeps, each one re-deciding to wait on no new information, and a user who breaks the chain with an interrupt hands the model an ambiguous stop signal easily misread as canceling the whole task.
Mid-run inspection is diagnosis, never a completion check, and the line between them is the TRIGGER rather than the file being read: a legitimate read answers something that has already happened -- an arrived notification whose payload looks truncated or premature, or a journal that has gone unwritten for far longer than one agent's work takes -- while a read scheduled for a moment that has not come yet is a poll whatever it opens. The signature to catch in your own draft is therefore a timer bolted to a journal read: the timer is the poll, and the diagnostic command after it does not launder it. Partial progress is not a delivery event either, because the harness notifies on the RUN rather than on its agents -- an agent counter sitting at three of four is nothing to act on, and needing one phase's output before the rest means building that into the script or a follow-up run rather than watching for it. The /workflows view plus the task panel below the input box exist so the user can watch live progress -- point the user there when they ask how the run is going, and otherwise leave the run alone until the notification arrives.
## Operations: Budgets, Quick Workflows, Recurring Runs, and Prompting
Token budgets work from the request side: phrasing like "use a 10k token budget" sets the hard ceiling the harness enforces, so surface this phrasing to users who worry about cost, and scale agent count and depth to fit the cap rather than overrunning it.
Workflows are not only for large tasks: a quick workflow, such as a fast adversarial review of one assumption, buys the anti-bias structure at small cost, so offer it when a full harness would be overkill but one failure mode still threatens.
Pair repeatable workflows -- triage, research, verification -- with recurring-interval execution and set a hard completion goal, so scheduled runs neither drift nor stop early.
When authoring a workflow or shaping a user's request into one, name the pattern, the roles, the stop condition, the output schema and its length caps for any structured result, the exploration budget per role, and the explicit model tier per role -- sonnet, opus, or fable, on every single agent, never left to inheritance: the more the request mirrors this taxonomy, the closer the generated script lands to the intended architecture.
## Saving, Sharing, and Templates
Save a good workflow by pressing "s" in the workflow menu, and check saved scripts into the user-level workflows directory so they persist across sessions and machines.
Distribute a workflow by shipping its JavaScript script files inside a skill and referencing them from that skill's instructions.
Treat shipped scripts as templates, never as scripts to run verbatim, and say so in the shipping skill's prose: adapt file paths, rule lists, rubrics, and model choices to the task at hand before running, because verbatim reuse forfeits the tailor-made advantage that makes dynamic workflows outperform static harnesses, and frozen scripts rot as tasks drift. Adapting model choices means re-deciding the tier of every role against the routing section's tests -- sonnet, opus, or fable, explicitly named on every call, never left to inheritance and never widened below the sonnet floor -- and re-earning every fable call in particular, because chain criticality belongs to the task's information chain, not to the template, and a fable route copied verbatim is exactly how the apex tier leaks into stages that never earned it.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!