termroam
TermRoam · field guide

Dynamic multi-agent workflows: an AI image pipeline

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.

The Posh Crisps homepage: an English country house across misted parkland at golden hour, with the wordmark POSH CRISPS set over it and a bowl of crisps in the foreground 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.

00

What was actually typed

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.

Shape, not steps.“Three agents generate, a grader picks the best, it can reject and repeat” is an architecture. It became a 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.
A standard, not a style guide.“Like a high quality production studio” and “elevated like wine or coffee” set a bar. The 2,000-word visual bible that made fifty images look like one campaign was written by an agent, from that sentence.
One constraint that mattered more than the rest.“Don’t filter for copyright, they’re reference only” unblocked the entire reference-research stage. A single clause, and it changed what the researchers were allowed to fetch.
No numbers, no file names, no schema.Nobody specified 19 slots, 57 candidates, or 8 counties. Those fell out of the brief. The eight English counties were proposed by the model and corrected once, in conversation.
State the constraint; let the agent find the route.“Use my existing subscription, I don’t want another image API” is a budget constraint, not an instruction. What came back was a small hardened CLI wrapper around the generation tooling already on the machine — bounded retries, rate-limit backoff, credential refresh for a run that would outlive its token, and a deterministic cover-crop to the exact target size because the model ignores requested dimensions. All 57 generations then shelled out to that single command.
The honest caveat. Prompt two is longer and more precise than prompt one, and it produced the better run — 253 agents, zero errors. The vaguer first prompt needed more course correction along the way. Precision about structure pays; precision about content is what the agents are for.

What that bought, in hours

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.

01

The checklist loop, and where it strains

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:

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.

The question is not which pattern is better. It is whether your work is a queue or a fan.

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.

50 independent slots · 4 stages each · arithmetic, not a measurement
Pick a concurrency cap.
02

The script is deterministic; the agents are not

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.

Structured output is the contract between the fuzzy part and the deterministic part. Everything the script needs in order to decide — which candidate won, which axis failed, what the corrected prompt should say — comes back as typed fields. Everything else the agent says is commentary, and the script never reads it.

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.

The cache is what makes long runs survivable

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.)

What two real runs cost

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.

two runs · measured, not estimated
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
The method has a second, public proof. The client work is not something we can show you, so the same script was pointed at an invented brand and the result was published: a complete brand site with eight regional pages and twenty-three generated images, built end to end by orchestrated subagents. It is a smaller build than the fifty-image run in the table above, but it came out of the same script. Every picture in this guide comes from that build.

Open the Posh Crisps demo →

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.

03

parallel() is a barrier; pipeline() is not

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.

the same image slots, written both ways
// 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 91-minute stall

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.

Two rules came out of that hour and a half. Put a barrier only where a step genuinely needs the whole set — a corpus-wide review does, a per-item hand-off does not. And never let an autonomous agent run anything that can raise an interactive confirmation: pass the non-interactive flag, pre-answer the prompt, or do not give it the tool. There is nobody there to press y.
Barriers are where autonomous runs go to die. Use one only when the next step truly needs the whole set.

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.

four image slots · three stages · one stalled agent
Pick a shape.

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.

04

Three takes, one grader who drew none of them

Here is the whole per-slot pipeline, in order. Every stage is one or more disposable subagents, and no stage judges its own output.

Reference research.One agent gathers real references — what a hand-cooked crisp looks like at close range, what a Norfolk drainage dyke looks like in February. These are never published. They exist only to condition the generator toward accuracy.
One visual bible, written once.A single director agent writes the style contract for the whole campaign: light, palette, lens behaviour, surfaces, what is forbidden. Every generator downstream is handed the same document. It is the only reason fifty images made by agents that never see each other's work read as one campaign.
Three generators per slot, on different briefs.Not three rolls of one prompt — three deliberately different interpretations, so the grader has real choices rather than three samples from one idea. Each generator views its own output and re-rolls obvious failures before submitting.
An independent grader.A separate agent that produced none of the candidates scores all three against a fixed rubric and returns a structured verdict: promote one, or reject all three with written corrections that feed the next attempt.
Corpus cohesion, then integration.A pass over the finished set as a set, then browser QA on the assembled pages. Both are covered below.

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.

A rejection and its replacement, same slot

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.

rejected · 15/25 A white bowl of thick, flat, pale yellow potato discs of near-identical size and shape, with no curl or blistering, sitting on a lichened stone wall beside a bunch of grey sage and a tipped-over terracotta pot, above misted chalk downland.
Automatic reject on food. The grader: “the bowl holds thick, flat, opaque near-identical discs with no cupping or blistering that read as fried potato rounds rather than thin British crisps, the orange crumb specks on the stone look digitally stippled, and the composition is inert with the bowl dead centre-left and a tipped terracotta pot as arbitrary styling.”
accepted · 23/25 A silver salver of thin curled crisps with spilled salt on a lichened stone parapet, a terracotta pot and a sprig of grey sage beside it, looking out over Wiltshire chalk downland with a beech clump on the ridge, a chalk track and a stone country house among bare trees below.
Promoted. The grader: “the only candidate with genuinely British crisps — thin, cupped, blistered, translucent at the edges, no two alike, with breakage and salt spilled off the salver — on a lichened parapet above real Wiltshire chalk downland, the house generic and unnamed and no text, badges or hands anywhere.”

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.

rejected · 16/25 The open boot of a dark green vintage car on a muddy farm track beside a flint wall and a field of oilseed rape, holding a silver salver of crisps, a tumbler of amber liquid and a piece of honeycomb on a tartan rug, with a chrome winged crest emblem mounted on the boot lid above.
Automatic reject on a badge. The grader: “a chrome-and-enamel winged crest badge sits plainly on the boot lid at the top right with a second raised emblem on the wing — exactly the car marque emblem the rules forbid — and the crisps here were the best of the three, which makes the badge all the more expensive.” Subject scored 5 of 5; artifacts scored 1.
accepted · 23/25 A shallow white bowl of crisps and a small grey mustard pot on a flint-and-brick wall in cold blue dawn mist, with a piece of honeycomb and a wooden honey dipper on a pewter plate, a drainage dyke running through frosted marsh and a flint church tower on the far skyline.
Promoted. The grader: “cold blue dawn mist over a Norfolk dyke and marsh with a flint church tower on the skyline and the anchor set bottom-left on a flint-and-brick wall — the crisps thin, curled, translucent-edged and blistered with salt and fallen pieces, honeycomb and mustard seed correct for the flavour, and no text or badge anywhere.”

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.

grader ledger · four slots · the winner and the best loser
candidatesubjartfbrandcompbelvtotalverdict

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.

05

Individually good, collectively wrong

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.

A silver salver heaped with thin blistered crisps on a grey tartan rug, resting on a lichened stone parapet above an Essex saltmarsh, with a small clinker boat grounded on the mud beside a winding tidal creek and a bank of flat grey cloud filling the upper half of the frame.
Essex saltmarsh.
A pewter salver of irregular blistered crisps and a brown salt-glazed jug standing on a mossy stone balustrade high above a Yorkshire dale, with a limestone scar, dry stone walls and a rain squall crossing the far hillside, and a plain glass of malt vinegar beside the jug.
Yorkshire dale.
A white bowl of golden crisps flecked with dark skin, a wedge of cloth-bound cheddar and a bundle of whole spring onions on a wool rug over a stone wall, with the limestone walls of a Somerset gorge and a narrow road curving away behind under flat white sky.
Somerset gorge.
A white bowl of pale crisps on a frost-touched grey rug over a stone wall, with a wedge of blue-veined Stilton on a small dish, two pears and a blue-banded stoneware jug, above misted parkland with avenues of bare trees and a distant country house on the ridge.
Midlands parkland.
A cream bowl of crisps and a green-glazed jug on a weathered wooden bench under an old apple tree, with three ribbed green cooking apples on unironed linen and a bunch of woolly garden sage, looking across a hedged Wealden orchard to an oast house under flat grey cloud.
Kentish orchard.
The open boot of a dark unbadged vintage car on a wet gravel lane beside a Worcestershire hop yard, holding a wicker hamper, a tartan rug, a bowl of peppered crisps, a dark stoneware jug, a pewter beaker, a cut-glass tumbler and fresh hop cones, under low grey cloud.
Worcestershire hop yard.

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.

06

Judge the artifact in its final context

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.

the shape of the bug — reconstructed, not the literal rule
/* 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.

Grade the artifact where it will actually be seen. A generated asset has two lives — the file, and the file in situ — and a pipeline that only inspects the first will ship a set of perfect images into a page that ruins them. Add a stage that assembles the real thing and looks at it. It is one agent and it is the cheapest insurance in the run.

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.

07

When not to run a pipeline like this

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.

08

How TermRoam handles this

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.