A dynamic workflow is a deterministic script that spawns language models as steps: the script owns the loops, the branches and the fan-out, and the agents do only the fuzzy part. For long autonomous work that shape beats looping one agent against a checklist, and this guide walks the method end to end — the two primitives that decide whether a run finishes or stalls forever, and a real image pipeline whose output you can go and inspect.
See what it built →
Posh Crisps
A complete brand site — eight flavours, each anchored to a real English county and its documented food history, with an interactive origin map and photography that never existed. Every word researched, written and fact‑checked by separate agents; every image generated, then graded by an independent judge that rejected 38 of 57 candidates. Built end to end by the workflow described below.
Open the demo site
Posh Crisps is a fictional brand, invented for this walkthrough. There is no company and nothing for sale — but the English history on it is real and sourced.
Before the method, the input — because the gap between the two is the point. Everything above came from two prompts, typed in plain English, on a phone. Nobody wrote an orchestration script by hand. Nobody wrote a spec document. These are the two, reconstructed against the demo brand:
Prompt one — the build, the copy, the research
run a dynamic workflow, spin up as many subagents as you need. build a proper brand site for posh crisps, upmarket british hand cooked crisps. each flavour gets its own page tied to a real english county and its actual food history. researched properly, i want people to feel like they're learning something, elevated like wine or coffee, not a snack. make it feel like it was produced by a high quality production studio like The Mill. absolutely world class.
Prompt two — every image on the site
give me a version with photorealistic AI images for a consistent visual identity. dynamic workflow: researchers find source photos as reference (don't filter for copyright, they're reference only), three agents generate, a grader agent picks the best on accuracy, no AI artifacts and fit for brand identity, and acts as QC including rejecting and repeating the cycle. final agent reviews images against the corpus of approved images to create a cohesive visual language across the site. scroll journeys should always end with a next button. use my existing codex subscription for the image generation, i don't want to sign up for another image api
That is the whole human contribution to a 253-agent run. Note what the prompts contain and what they do not.
pipeline() over 19 slots, each with a three-way parallel() fan-out feeding a grader with a JSON schema and a bounded retry loop. The human described the shape; the model wrote the control flow.Add up every agent’s own execution time and the image run did 27 hours 26 minutes of agent work in 7 hours 11 minutes of execution — a 3.8× compression. Across both runs it is roughly 31 hours of work in 16.5 hours of clock.
Note what it is not: 16×. The concurrency cap allows about sixteen agents at once, but a pipeline has dependencies — a story cannot be fact-checked before it is written, a candidate cannot be graded before it is generated — so the realistic ceiling for staged work is three to four. A run that fans out into genuinely independent units gets closer to the cap; one shaped like a chain gets almost nothing. That ratio, not the agent count, is the honest measure of whether the fan-out was worth writing.
The most widely used pattern for long autonomous agent work goes roughly like this. You write PRDs. An agent reads them and generates a living status checklist on disk. Then you loop a headless coding agent — claude -p, pointed at a branch, inside a persistent session — and every iteration reads the checklist, does the next unchecked unit, commits, and ticks the box, until the list is done. It is often called a Ralph loop, it costs almost nothing to build, and a great many people run one every day.
It deserves a fair hearing, because it is genuinely good at a common shape of work:
git revert rather than the run.Where it strains is structural rather than a matter of prompting harder. It is strictly sequential: wall clock scales linearly with task count, so fifty independent four-minute units is three hours and twenty minutes with your capacity idle. The producer is also the judge: the agent that did the work decides whether the box gets ticked, and self-assessment is generous — not dishonest, just unable to see the work from outside. There is no fan-out, so "make three interpretations and have someone else pick" is not a thought the loop can have. And long runs drift, because iteration ninety is reading a checklist that iteration forty rewrote.
If the units are sequential, dependent and cheap to verify, loop them — a workflow script is overhead you do not need. If they are independent, and each has to be produced several times and judged by something that did not produce it, a loop gets there eventually and does not get there well.
The two also compose. Scout with a loop and fan out with a workflow: let the cheap loop explore the repo and write the spec, then hand the enumerable parallel phase to a script that runs sixteen agents at a time and throws them away afterwards.
Most people meet multi-agent work as a prompt: one long instruction handed to one agent expected to plan, delegate, remember and finish. That breaks in a predictable place — the plan lives inside a context window the work itself is consuming, so by step forty the agent is reasoning about a plan it can no longer fully see. A dynamic workflow inverts the arrangement. The plan lives in ordinary JavaScript that you wrote, that runs the same way every time, and that has no context window at all.
The primitive underneath everything is a single function. agent(prompt, opts) spawns one subagent with its own fresh context, its own tools and its own working directory, runs it to completion, and returns what it produced. Passed a JSON schema in opts, that return value stops being prose and becomes a validated object — a verdict with a numeric score and an enum, not three paragraphs your script has to parse with a regular expression and a prayer. That is the difference between a workflow you can branch on and one that mis-reads a "no" as a "yes" at three in the morning because the model phrased its refusal differently than the run before.
Around it sits ordinary flow control: a loop over slots, an if on a score, a retry when a verdict lands below the bar, a fan-out across an array. A run of several hundred model calls has exactly one author of control flow, and that author is a file you can read. The agents are leaves; they never decide what happens next.
Two operational facts shape everything else. Concurrency is capped — roughly sixteen agents run at once and a queue of several hundred drains through that window. And every call is cached by the pair (prompt, options), so a run you stop and restart replays its unchanged prefix in seconds and executes only the steps that are new or whose prompt you edited.
That is not a performance detail. It is what makes the pattern usable, because a nine-hour autonomous run will be interrupted. On the first of the two runs below, a usage limit landed mid-flight and killed three agents outright; on resume, the thirty-five completed steps replayed instantly from cache and only the five remaining ran live. It also changes how you work: you can edit stage four and re-run the whole script when stages one to three cost nothing. (The run survives a closed laptop for the ordinary reason — it executes inside a persistent session on a machine that stays up, not in a terminal window on your desk.)
These numbers come from a client engagement for a consumer packaged-goods brand: two consecutive long runs that produced a complete deployed brand site, the build and all written content in the first, the entire photographic image system in the second.
RUN ONE — site build, copy, research orchestrated steps 40 subagent tokens ~3.4M (2,361,270 first pass + 1,068,530 on resume) tool calls ~1,150 agent time 3h 31m wall clock ~7h (one stop, one resume) interruption usage limit killed 3 agents mid-flight on resume 35 steps replayed from cache, 5 ran live RUN TWO — the whole image system agents 253 errors 0 subagent tokens 20,790,157 tool calls 6,556 agent time 27h 26m execution 7h 11m speedup 3.8× wall clock 9h 25m 29s published images 50 generated ~150 regeneration cycles 65 (grader rejected every candidate for a slot) cohesion reshoots 43 fix rounds 1 unresolved blockers 0 dead stall 91 minutes, one agent TOTAL ~293 steps · ~24M tokens · 31h of agent work in ~16.5h
What Posh Crisps is, precisely. A fictional brand, built as a demonstration; nothing about it is for sale. Its English regional history is real and was fact-checked by a separate agent whose only job was to verify claims about places, foods and dates against sources. Its photography is generated rather than shot, and carries no photographer credit, no location credit and no fabricated provenance of any kind, because there is none to give.
Two composition helpers cover almost all real work, and choosing wrongly between them is the most expensive mistake available in this style of programming.
parallel([thunks]) runs a list of agent calls concurrently and returns when all of them have returned. It is a barrier: the slowest branch sets the completion time, and nothing downstream begins until every branch resolves. That is correct when the next step genuinely needs the whole set at once — a director comparing every finished image cannot start with three of four.
pipeline(items, stageA, stageB, ...) is the other shape. Each item flows through every stage independently, so item A can be in stage three while item B is still in stage one. Its wall-clock cost is the slowest single chain — one item's own path through all the stages — rather than the sum of the slowest item in each stage, which is what a row of barriers charges you. For multi-stage work over independent items this is the right default.
// WRONG — three barriers in a row. // Nothing reaches the grader until every generator in the run has returned. const refs = await parallel(slots.map(s => () => research(s))); const takes = await parallel(refs.map(r => () => generate(r, bible))); const winners = await parallel(takes.map(t => () => grade(t))); // RIGHT — each slot walks all three stages on its own schedule. // A stall blocks one slot; the rest keep moving and keep finishing. const winners = await pipeline(slots, slot => research(slot), // reference gathering, one agent refs => generate(refs, bible), // three generators, conditioned on refs takes => grade(takes) // one independent grader, structured verdict );
The cost of getting this wrong is not theoretical. Partway through the 253-agent run, one subagent reached for a command that asked it to confirm something — an ordinary interactive prompt, waiting for a keystroke from a human who was asleep. It did not crash and did not error. It sat there holding its slot inside a parallel() barrier whose every other branch had finished. Nothing downstream could start, because "all branches resolved" stayed false, and it stayed false for ninety-one minutes while the finished work of two hundred-odd agents idled behind one that would not return. The same stall in a pipeline() would have blocked one image slot and let the other forty-nine run to completion.
Set the stall running below and watch what each shape does with it. The numbers are illustrative units, not measurements — three stages of fixed cost, four slots, one branch that hangs.
With every branch even, the two shapes finish together and the choice looks like a style preference. Variance separates them, and variance is the normal condition of a run whose steps are language models talking to image models over a network. Write the pipeline by default and you never have to predict which step will be slow.
Here is the whole per-slot pipeline, in order. Every stage is one or more disposable subagents, and no stage judges its own output.
The fourth item is where quality comes from, and it is the one thing a single-agent loop structurally cannot do. A generator asked to grade the picture it just made will approve it — not from vanity, but because the context that produced the choices is being asked to find fault with them, and it has already decided that a pointed, glassy, flat-looking crisp is a crisp. An agent that arrives with the rubric and no history looks at the same frame and says: those are fried potato discs.
On the run measured above, the grader rejected every candidate for a slot 65 times — sixty-five rounds of work a self-approving loop would have shipped as it stood.
The rubric scored five axes out of five — subject accuracy, generative artifacts, brand fit, composition, believability — for a total out of 25. The bar sat at 22, with two automatic rejects that override the total: any visible text, badge or brand mark, and food that is geometrically wrong.
Same slot, same visual bible, same three-generator fan-out. The left frame is what one confident agent ships when nobody else is looking.
The second pair is more uncomfortable, because the rejected frame is better at the thing the slot was actually about.
A hard rule that overrides an aggregate score is worth the frames it costs. A marque badge is a real-world trademark on a fictional brand's page, and no amount of excellence elsewhere makes that acceptable. Encode that kind of rule as an automatic reject and the grader cannot talk itself round.
| candidate | subj | artf | brand | comp | belv | total | verdict |
|---|
Switch that panel to self-judging and every row turns into a submission with no scores, because nothing was scored against anything. That is the honest rendering of a checklist tick made by the agent that did the work.
A per-slot grader has one blind spot it cannot fix by trying harder: it only ever sees one slot. Fifty images can each clear 22 out of 25 on their own terms and still fail as a set — the light drifted warm on some, the camera height wandered, three turned out to be the same composition.
So after the pipeline drains, one agent looks at the whole corpus at once — the one place a parallel() barrier is genuinely correct, because a review of the set really does need the entire set. It returns a list of slots to reshoot with the specific family trait each is missing. On the measured run that pass sent 43 images back. Not because they were bad. Because they were strangers.
Six agents made those, in six different places in the queue, none of them able to see the others. The flat overcast light, the low horizon, the muted greens and greys, the anchoring bowl in the near third, the total absence of anything with writing on it — all of that comes from one visual bible written once at the top of the run and handed to every generator, plus the cohesion pass that caught the ones which wandered.
The full set, on the pages it was made for, is at termroam.com/poshcrisps — eight regional pages, twenty-three generated images, and a written history per region that a separate fact-checking agent verified against sources.
The most expensive defect in the client run was not in any image. Every generated file was excellent at the source: open it, and it is a photograph. The site's own CSS was destroying them. A stack of decorative rules — heavy grayscale, low opacity, a dark overlay — sat on the image containers, and a hero photograph like the frames above rendered as a near-black rectangle with a faint suggestion of a bowl in it. Worse, the filter values differed page to page, so fifty images a cohesion pass had just dragged into one family stopped reading as one campaign the moment they were placed.
/* every generator, grader and cohesion agent passed this image. */ /* none of them ever saw it with these three lines applied. */ .hero-media img { filter: grayscale(85%) brightness(0.5); /* the photograph, gone */ opacity: 0.35; /* what is left of it, gone */ mix-blend-mode: multiply; /* and it differs per page */ }
No amount of stricter grading would have caught this, because the rubric was applied to the file and the defect only exists on the page. What caught it was a late stage doing something none of the earlier stages did: an agent opened the assembled site in a real browser, looked at the rendered pages, and reported that the photographs were black.
This generalises past images. Copy that reads well in a document reads differently in a 320-pixel column; a component that passes its unit tests can still be invisible behind a z-index. If a stage produces something a human will eventually look at, the last stage should look at it the same way.
The honest version of the argument includes the cases where this is the wrong tool, and there are several.
The failure mode to watch for is building a pipeline because pipelines are interesting. The test is whether you can name the fan: the independent things, the several attempts each one needs, and the judge who did not make them. If you cannot, you have a queue, and queues want loops.
Everything above assumes a machine still running in nine hours. That is the part people underestimate: a workflow script driving 253 subagents is not something you babysit in a terminal window, and neither is a Ralph loop that has to survive until morning. Both die when the laptop lid closes, unless the process was never on the laptop.
TermRoam is a dedicated always-on server for exactly this: one persistent session per task, one worktree and branch per agent so concurrent work cannot collide, logs you can reattach to from a phone. Start a run before dinner, close the laptop, reattach afterwards to a finished pipeline — or to the one step that failed, which the cache lets you re-run on its own.
If you are still deciding whether an agent belongs in a chat window or in a process on a server, start with what an AI coding agent actually is.