Build a Fan/Join Graph
Project10 min · Map → Contract → Route → Verify · Updated 2026-09-15
You end this lesson with: A working fan/join graph — node contracts, route table, join rule — applied to a real task, plus a measured before/after against the chained version.
Chapters
- What You'll Do01
- Tools You'll Use02
- Steps03
- How to Measure Results04
- What to Submit05
- Key Takeaways06
Project 01. Build a Fan/Join Graph
Related Lecture: Lecture 02. Auditing a Real Task's Edges
A four-person contract-ops team at a regional insurance broker ran their weekly contract review as eleven steps typed one after another into a shared runbook: fetch the contract, pull the parties, pull the liability clause, pull the termination clause, pull the data-handling clause, check each pulled item against policy, file a risk note. Forty contracts went through every Tuesday, strictly in that order, because that was the order the steps happened to be written down. One Tuesday a scanned PDF came through corrupted, and the step reading it sat retrying for six minutes. The other thirty-nine contracts — every one of them already read, already checked, fully done — waited behind it anyway, because nothing in the process let a finished contract move past a stuck one. The batch that usually cleared before lunch was still running at four that afternoon, and nobody on the team could say with confidence which of the forty had actually been checked.
Illustrative composite. The broker, the corrupted PDF, and the six-minute stall are a teaching device; the failure it stands in for — steps run strictly in the order they were written down, with no way for finished work to move past something stuck — is the one this project measures a real fix for.
What You'll Do
Take one real multi-step task you currently run as a chain and rebuild it as an explicit fan/join graph: audit its real dependencies, write a contract for every node, classify the result into the four shapes, and add a join that makes an explicit coverage decision instead of just waiting. When you're done, you'll have a measured before/after — the same work, run two ways — and a graph.md you can hand to someone else and have them understand the shape without asking you a single question.
Tools You'll Use
- Claude Code, Codex, or any agent harness you already run tasks through
- A text editor or diagramming tool (a
mermaidblock or a hand-drawngraph.mdboth work — the point is getting the structure onto paper, not the tool) - Git, to keep the chained and fanned versions as separate, comparable runs
Steps
Prep
- Pick a real recurring task with at least five independent-seeming units of work — reviewing several documents, researching several sources, checking several files against a rule set. If you don't have one on hand, use this course's running example: reviewing a batch of contracts (or substitute any batch of similar documents you actually have).
- Note how you currently run it. If it's already a chain — one unit after another — that's your baseline. Record how long a representative batch takes end to end.
Worked Example First: The Contract-Review Batch
Before auditing your own task, work through this course's running example once, filled in completely — the reasoning is what to copy, not just the shape.
The audit, filled in.
| Node | Reads | Writes |
|---|---|---|
| fetch contract | (nothing, entry point) | contract text |
| extract liability | contract text | liability clause |
| extract termination | contract text | termination clause |
| check liability | liability clause | liability verdict |
| check termination | termination clause | termination verdict |
| write risk note | both verdicts | risk note |
Read straight down the Reads column: extract termination never reads anything extract liability wrote. Both only share fetch contract as a parent, so the arrow between them in a strictly-ordered runbook was never a real dependency — just the order someone typed the steps. Two real edges survive out of what looked like a straight chain; the rest are habit edges, exactly what Lecture 02 names.
The join, decided.
write risk note is the one place a join earns its wait here — it needs every contract's verdicts to say how many were flagged. That's the decision the corrupted-PDF stall in this project's opening actually turned on.
Decide now: does the batch report block until every one of the forty contracts settles, or does it proceed once most of them have?
interface BatchJoin {
expected: number; // 40
settled: ContractResult[]; // 39 verdicts landed before the corrupted PDF's retry timed out
failed: ContractId[]; // [the one corrupted-PDF contract]
}
// fail-closed: reportOnJoin waits for settled.length === expected — blocks on the one stuck contract
// fail-open, 95% floor: reportOnJoin fires once settled.length / expected >= 0.95 — this batch clears at 39/40
Fail-closed is the answer that feels safest, and it's the one that reproduces the opening's stall exactly: one corrupted contract blocks the other thirty-nine finished verdicts from ever reaching the report. A 95% coverage floor lets the report go out with thirty-nine settled and one named incomplete — honest about what didn't finish, and nobody waits until 4pm for a PDF that was never going to parse.
Now run the same audit, and make the same join decision, on the task you picked in Prep.
Step 1: Audit the Real Edges
Apply Lecture 02's method directly.
- List every node in the current chain.
- For each node, write down exactly what it reads and what it writes — specific fields or artifacts, not "the previous output."
- Draw an edge only where a write feeds a read. Count how many of the original chain's edges survive.
- Identify the independent units — the ones that don't read each other's output. These are your fan candidates.
Step 2: Write Node Contracts
For every node that survives the audit, write its contract using Lecture 03's four-line format:
Node: <name>
Job: <one job, nameable in three words>
Input: <explicit — what it reads, and from where>
Output: <a structured shape, not prose>
Failure states: <named, not thrown — what does "didn't work" look like as a value?>
Where a node fails this test — its job needs "and" to describe, or its output can't be written as a shape — split or merge nodes until every one passes.
Step 3: Classify the Shape
Using Lecture 04's four shapes, label every segment of your redrawn graph: chain, fan, router, or controlled cycle. You should have at least one real fan — the independent units from Step 1 running concurrently instead of sequentially.
Step 4: Design the Join
Following Lecture 05:
- Identify the one place (or places) your fan genuinely needs to rejoin — where the next step needs the complete set, not an individual result.
- Write the join's failure policy explicitly: fail-closed, fail-open with a coverage floor, or fail-open with per-branch retry. State which you picked and why in one sentence.
- Implement (or pseudocode, if you're not wiring this up in a real framework) the join so it tracks
settledandfailedseparately, the wayBatchJoindoes in Lecture 05 — not a bare wait-for-all.
Step 5: Run Both Versions
- Run the original chained version on a representative batch. Record wall-clock time and, if you can, total model calls or tokens.
- Run your fan/join version on the same batch. Record the same two numbers.
- Deliberately fail one branch (a malformed input, a forced error) in the fanned version and confirm your join's failure policy behaves the way you designed it — it doesn't hang forever, and it doesn't silently drop the failure without recording it.
How to Measure Results
| Metric | Chained (before) | Fan/join (after) |
|---|---|---|
| Wall-clock time for the batch | ||
| Real edges vs. total edges in the original design | — | |
| Node contracts written / nodes needing them | — | |
| Did the join's failure policy fire correctly on a forced failure? | — | |
| Total model calls or tokens (same work, either shape) |
The last row is deliberately there to keep Lecture 09 honest: a faster run is not automatically a cheaper one. If your fan/join version costs meaningfully more, say so in your retro — don't just report the speedup.
What to Submit
graph.md: your audited dependency map (Lecture 02), node contracts (Lecture 03), shape classification (Lecture 04), and join design with its stated failure policy (Lecture 05) — one document, readable start to finish.- The list of fake edges you found and removed from the original chain.
- Your before/after measurements: wall-clock time and, if available, total model calls or tokens for both versions.
- A short record of the deliberate branch failure you ran and what the join actually did.
- A retro, three to five sentences: did the fan/join version turn out faster, cheaper, both, or neither — and did anything in the redesign surprise you?
Key Takeaways
- Prove every edge with a read/write match, not a feeling of relatedness.
- Give every surviving node a contract before wiring it to a neighbor.
- Label each segment's shape so a reader knows what to verify.
- Pick a join failure policy on purpose; never default to wait-for-all.
- Measure cost alongside speed — a faster batch isn't automatically cheaper.
What You Have Now
A working fan/join graph — node contracts, route table, join rule — applied to a real task, plus a measured before/after against the chained version. It lives in graph.md alongside your original chained baseline, ready to hand a reviewer without a walkthrough. It closes out the Map and Contract stages of this course's loop and sets up the Route stage that Project 02 extends with a verifier and durable state.
Related Lectures
NO-QUIZ
Quiz coming to this lesson
This lesson doesn't have a graded quiz yet — completion is still tracked, just not verified against questions.
Reading is free — your progress isn't being saved
Nothing you did on this page is recorded. Paid students get saved progress and verified completions, recorded quiz answers, the spaced-repetition review queue, labs, and a certificate for each course they finish.
30-day money-back guarantee. One-time payment — lifetime access and updates.