2026-08-28 · Updated 2026-08-28 · 9 min read
A complete parallel coding-agent workflow
Carry one batch of independent agent tasks from standing start to closed evidence, stage by stage, with readiness checks, bounded fan-out, worker isolation, and aggregation proof at every step.
By Juno AI INC · parallel-agents · fan-out · yylo
Starting many coding agents at once is the easy half of parallelism — a shell loop over task IDs does it in one line. The workflow is everything around the launches: deciding which tasks may enter the batch, how many run at the same moment, where each one does its work, and what proof remains after the pool drains. Skip that surrounding work and you do not merely get slow parallel agents — you get workers that collide, overruns nobody noticed, and a directory of logs too shapeless to close a single task from.
This guide walks one complete cycle, stage by stage, with a worked batch: six independent tasks dispatched over a three-worker pool, carried from a standing start to closed, reviewed evidence. The stations do not change with scale; two investigation tasks and twenty implementation tasks pass through the same loop. Two neighbors divide the surrounding territory: Choose Workflow Runner or Parallel Runner decides whether your work is a pool or a chain, and safe parallel execution owns the safety depth behind each station. This page owns the sequence — the whole cycle, in order, with the evidence each stage hands to the next.
Shape the batch before anything launches
A parallel batch is only as sound as its smallest unit. Every task you intend to fan out must hold one verifiable outcome — a change with its own testable surface, an investigation with its own deliverable — because the pool cannot split an oversized unit for you. Write each task body as that worker's complete prompt: a worker reads its own task and nothing else, so a unit that leans on unstated context yields an unsupported answer.
Declare ordering between units while you create them, not after something runs twice. A dependency is an edge you add once; it costs nothing until queried:
Keep related_tasks out of this decision — a related task is a reading list, never an ordering edge. And when one unit needs another unit's response rather than just its completion, the work is a chain and belongs to an ordered workflow instead. Prefer many small units over a few large ones at this stage: small units fail small, retry cheap, and keep every lane busy to the end of the batch instead of idling the pool behind one straggler. That is the bounded loop discipline, multiplied.
Admit work through ready, not guesswork
Admission is a query you run, not a judgment you make at launch time. The ledger answers it:
ready answers with the tasks in backlog, todo, or in_progress that have no unresolved blocker left — exactly the set the pool may start. When an expected task is absent, deps lists the blockers still standing in its way, which turns a silent gap into a two-second check.
Pass the admitted IDs to the runner explicitly. That two-step form — query ready, forward the IDs — is the dependency-aware input:
The runner's other queue input, --kanban-filter, takes a quoted flag string — --kanban-filter "--tag frontend --status todo --limit 6" — and resolves it through the ledger's list command before anything launches. It selects a queue; it never evaluates dependencies, so its output is candidate material for a ready pass, never the admission decision itself. Keep the flags real flags: a word like ready is a sibling command, not a list option, and a filter that forwards it reports the ledger error and stops the launch before the first worker starts.
Bound the fan-out to limits you can name
The --parallel flag sets a hard ceiling on how many workers run at once (its default is 3), and every task in the pool is one agent invocation, so the ceiling doubles as your concurrent provider-session count. Name the real limits before the batch starts and set the cap to the smallest of them:
- Provider concurrency and rate limits — how many sessions you may hold simultaneously.
- Machine headroom — what the checkout plus the workers' own commands cost together.
- Shared external quotas — anything every worker draws on by design.
- Review capacity — the pool can drain in minutes while its evidence takes an hour to read.
Quota handling rides with the whole pool through --subagent-args, which forwards raw, shell-split arguments to every worker invocation:
With -s pi, an hourly provider limit parks the worker instead of failing it mid-batch — a rate-limit response becomes a pause, not a lost task. One exception belongs in the plan from the start: Pi's interactive --live mode under tmux is valid only with --parallel 1, because a single interactive pane cannot serve several workers.
Cost is part of the bound, not an afterthought. The batch's aggregation record sums each task's reported cost into a total, so after a run you can compare its parallelism against its wall time and total cost — and treat raising the cap as the spending decision it is, not merely a scheduling one.
Isolate every worker lane
Each task in the pool executes as a separate process, with a prompt materialized for it alone, a log named task_<TASK_ID>.log, and a result record of its own; the combined log the runner prints is a view over the pool, never a shared workspace. Two details keep the lanes real:
- Custom prompts must name the unit. Nothing injects a task into a prompt file by itself: the placeholder
{{task_id}}must appear in the text (Do ##{{task_id}}is the shipped form), and it is that placeholder which points a worker at the task body to read. Keep the prompt file itself reviewable. - A process wall keeps workers out of one another's way, but it does nothing about state they share: two tasks reaching for one file, or one shared external quota, stay coupled whatever the board says — declare the edge or serialize the work.
For batches worth watching, the tmux layouts put each lane on screen: --tmux windows, panes, or tabs. Handoff mode goes further — --tmux panes --tmux-handoff gives every task a pane of its own and keeps that pane after the task ends, so the place where a task ran remains a review surface. --max-panes-per-session 4 splits a large batch across auditable child sessions and records the split in tmux_handoff_manifest.json. Name deliberate sessions with --name and end them just as deliberately: --stop --name NAME ends one session, and --stop-all is for intentional broad shutdown only.
Launch, wait, and read what the batch wrote
An unattended batch should start in the background and finish on a clock, not on your attention:
The wait helper polls parallel_runner_status.json, the file that records whether the run completed and how it exited, while --verbose streams the combined log beside it. The runner itself has no --verbose flag; when you watch by hand, tail the log it prints. Once the helper returns, the batch's evidence waits in the printed output directory (--output-dir, otherwise /tmp/yylo-sessions/<date>/<run_id>):
- One JSON per task — its exit code and session id, wall time and cost, the worker id that ran it, and the extracted response.
aggregation_*.json— the batch-level record: task and failure totals, the parallelism used, wall time, summed cost, and a session row per task.parallel_runner_status.json— the completion truth the helper read.
For items that owe you structured output, launch with --strict --file-format json: each worker's fenced block is extracted to <task_id>.json, and a missing block fails that item, so an extraction problem becomes a visible task failure rather than a blank in the batch.
Retry the failed item, never the batch
A drained pool leaves two questions: which items failed, and what happens next. The aggregation answers the first by name — failed IDs and, where one exists, each failure's session id. The second is a rule: continue or rerun exactly the failed item.
Continue the failed item's session when the failure is conversational — the agent lost the thread, hit a limit, stopped early — because the session keeps every decision already made. Rerun the task ID alone when the task itself was wrong: fix the body or its missing acceptance check first, then relaunch that one ID. The succeeded majority stays untouched; relaunching it spends the budget twice and buries good evidence under redundant runs.
Close on evidence, then feed the next batch
A parallel batch is finished when its work is closed, not when its processes exit. Closing means reading each task's response, its diff, and the commit that passed its checks — the batch summary is a map to that reading, never a substitute for it. Mark each task done on what you actually verified, and let its record carry the response that closes it.
Then the loop turns. Follow-up tasks the workers created sit on the board with their own dependencies; the next ready query admits them exactly as the first one did, and each new aggregation record lands beside the last for comparison. Scale by repeating the cycle with more admitted and better-shaped units — not by inflating the worker cap, which only multiplies the failure surface you rehearsed above.
When the fan-out items are not tasks
Two input modes cover fan-out whose units are not kanban tasks, and both keep the pool, the isolation, and the aggregation evidence — only the unit changes. When the units are records — rows of a spreadsheet, JSONL entries, filenames — --items-file accepts JSONL, CSV, TSV, or XLSX, detects the format from the extension, gives each record a generated ID, and substitutes it into the prompt through {{item}}:
When the units are complete commands — including several whole workflow files — a commands file fans them out over the same bounded pool while each command keeps its own behavior. Generate and lint the file before any expensive dispatch, so schema mistakes surface at the desk instead of mid-batch:
Composing a capped fan-out with an ordered workflow — a pool inside a chain, chains inside a pool — is a composition question with its own guide.
Start with one bounded task
The first parallel workflow worth running is a batch of one. Install YYLO, initialize a project around a single verifiable outcome, and prove the unit shape — the task body, its checks, its evidence — at one worker before trusting it at six:
When that single loop produces a change you would merge, the pool is the same discipline repeated: admit what is ready, bound what runs, isolate every lane, read everything the batch wrote. The operational reference behind every flag on this page is the Parallel Runner documentation, the wider script catalog starts at the YYLO documentation, and the single-agent habit the whole cycle multiplies is the bounded agent loop.