If you’re waiting for Agent A to finish before starting Agent B, you’re wasting time. Define the contract between them and dispatch both now.
The default way to run agents on a multi-step codegen task is sequential. Agent A builds the data layer, then Agent B builds the thing that consumes it. B has to wait, because it doesn’t know the shape of A’s output. So the task stalls on the slowest producer.
There is a faster way. Write the contract first, then run both agents against it in parallel.
The Setup
Last night I was building a green-screen meme rendering pipeline. The work split cleanly into two:
- Task A: Ingest pipeline. Scrape 2,150 Patreon green-screen memes, sample 100 backgrounds, 100 music tracks, and 47 sound effects from GCS, run ffprobe on each, compute quality scores, and emit a local manifest.
- Task B: Agentic state machine. Duplicate the existing slideshow state machine for green-screen memes. Add tools to search local assets, choose a composite (green-screen + background + music + captions), and render via ffmpeg with a deterministic critic.
The naive plan: ship A, then ship B. A is a multi-hour scrape plus a non-trivial ingest script. B is a state machine rewrite plus new tools. Run them back-to-back and I burn a day.
Instead I wrote a contract and ran two Codex agents simultaneously.
The Contract Is the Decoupling Point
A contract is not just a TypeScript interface. It is the set of types, schemas, folder shapes, and provider interfaces that describe exactly what the producer emits and exactly what the consumer expects. For this task:
export type SeedAsset =
| GreenScreenTemplateAsset
| BackgroundAsset
| MusicAsset
| SoundEffectAsset;
export interface LocalAssetProvider {
searchGreenScreens(q: AssetSearchQuery): Promise<GreenScreenTemplateAsset[]>;
searchBackgrounds(q: AssetSearchQuery): Promise<BackgroundAsset[]>;
searchMusic(q: AssetSearchQuery): Promise<MusicAsset[]>;
searchSoundEffects(q: AssetSearchQuery): Promise<SoundEffectAsset[]>;
getAssetById(id: string): Promise<SeedAsset | null>;
}
Plus a folder layout (artefacts/seed-assets/<kind>/<asset-id>/source.ext with metadata.json), a manifest schema with schemaVersion: 1, a render input schema, and a critic output schema.
That is the contract. Agent A produces things that satisfy it. Agent B consumes things that satisfy it. Neither agent needs the other to exist.
Two Agents, Parallel Runs
While Agent A was running its 2,150-post overnight Patreon scrape and later writing the ingest CLI behind pnpm assets:ingest-seed, Agent B was:
- Duplicating
state-machine.tsintogreen-screen-state-machine.ts - Building a
LocalAssetProviderthat reads the manifest - Writing search tools that delegate to the provider
- Writing
render_green_screen_assetwith standardized ffmpeg args (scale, chromakey, overlay, drawtext) - Writing a render critic that parses ffprobe output and checks caption bounds, dimensions, and audio policy
Agent B did not wait for ingest to finish. It mocked the three real assets I named (the gorilla clip, Madonna Vogue, John Cena SFX) as a local manifest that already satisfied the contract. This worked because the interface hides where the assets came from. Local files, DB rows, or API responses behind the same interface are interchangeable.
Agent A never had to read a single line of Agent B’s code. It shipped a CLI that writes files into the agreed folder shape and a manifest validated against the agreed schema.
When Agent A finished, I pointed Agent B’s provider at the real manifest. Nothing else changed.
Why This Works
Contracts reduce coordination cost to near zero. Both agents work against a frozen reference, not against each other’s in-flight code. You get three properties for free:
- Independent progress. A crash or rewrite in one agent does not stall the other. Agent B’s state machine was rendering against mock assets for hours before Agent A’s real manifest existed.
- Cheap mock implementations. The first consumer of the contract is a fake. That fake becomes your integration-test fixture, because it already has the shape Agent A will eventually produce.
- Swap-in upgrades later. When ingest moves behind an Effect API route and a Postgres table with pgvector embeddings, the provider interface does not change. The state machine does not notice.
This is the same reason microservices with OpenAPI specs let product and platform teams ship in parallel. The contract is the seam. Agents exploit seams better than humans because they do not need any context from adjacent work. They only need the shape.
Where to Put the Contract
A contract is cheap to write and load-bearing once written. Three decisions matter:
- Who owns it. Not the producer, not the consumer. The human dispatching both agents owns it. Otherwise each agent drifts toward a shape convenient for itself and the seam breaks.
- Where it lives. In the repo both agents can see. Not in chat, not in a scratch file. The contract is a first-class module (
src/core/green-screen/types.ts) both agents import from. - How strict it is. Strong enough that a producer cannot emit garbage a consumer will accept. I used Zod schemas plus TypeScript discriminated unions. The parser at ingest-time and the parser at state-machine-time is the same parser.
Over-specify early. Under-specifying feels cheap (“just typed JSON”) but bites later when one agent assumes a field exists and the other never emits it.
Limits
Contracts do not parallelize everything:
- Design ambiguity. If the shape of the data is the real question, writing the contract is the real work. Both agents should block on that.
- Leaky seams. If B routinely needs producer internals (raw ffmpeg output, not the probe summary), the contract is wrong. Widen it or redraw the seam.
- Moving APIs. Breaking the contract mid-run requires re-dispatching both agents with the new version. Version contracts explicitly.
The question I ask before splitting work: can I describe every value crossing this seam without reading either side’s implementation? If yes, ship two agents. If no, write the contract until yes.
The Meta-Pattern
This is how I think about stacking agent runs now. Every multi-agent task has a dependency graph. Wherever there is an edge, there is a potential contract. The sum of contracts defines how wide my parallel lane is.
Three agents means three contracts at minimum, probably one per shared seam. Each contract is cheap. Each unlocks a parallel lane.
The human role shifts. I am not writing the code the agents write. I am writing the contracts the agents code against. That is the compound leverage: one hour of contract design buys ten hours of parallel agent compute.
Sequential is the slow path. Contract-first is how I stack runs.
See Also
- Agent-Driven Development – The three pillars and PRD-driven loops that contracts slot into
- Function-Driven Development – Let agents spec their own tool contracts
- Projects – Where these patterns get exercised in production

