LEARN · PHASE 15 · LESSON 100

Blocking Edges — What's Actually Parallelizable

You can tell a hard dependency from a preference, and turn the hard ones into

Type
Learn
Prerequisites
Lesson 99 (Task Graphs); Phase 12, Lesson 85 (When NOT to Parallelize)
Time
~35 minutes

Learning Objectives

  • Sort every edge in a graph into blocking or soft
  • Say why only blocking edges constrain what can run at once
  • Convert a blocking edge into a soft one by naming an interface
  • Say when not to convert — when the reconciliation costs more than the wait

The Problem

A plan had eight agents and seven arrows between their tasks. The runner treated every arrow as a hard wait. So eight agents ran, one at a time, four minutes each.

Six of the seven arrows meant "I would prefer this order." The frontend section "should" land before the docs, said the plan. Nobody had a reason. The runner did not ask; an arrow was an arrow. A job that could have finished in two waves took eight sequential steps, and the parallelism the team paid eight agents for never happened.

The opposite failure is worse. In the five-job feature from Lesson 98, one arrow was real: the tests needed the schema. Marked soft, ignored, and the tests were written against a schema that did not exist. The collision was cheap to prevent and expensive to unwind.

Both failures come from the same gap: an edge is drawn as one thing but means two.


The Concept

An edge is a claim that one node needs another. But "needs" hides a decision, and it has two answers.

A blocking edge is a real dependency. B's correct output cannot exist until A's output exists. The migration needs the schema, because the migration edits the schema, so it literally cannot run before it. Marked blocking, B waits.

A soft edge is a preference. A first is nicer — tidier, more legible, lower rework — but B can start without A. The docs "should" follow the API, but a docs writer can begin against the agreed response shape. Marked soft, B runs when it is ready.

flowchart LR
    S[Schema] -->|blocking| M[Migration]
    A[API] -->|blocking| T[Tests]
    A -.->|soft| D[Docs]
    D -.->|soft| R[Release notes]
Mermaid source — renders as a diagram where mermaid.js is available

Only blocking edges constrain the frontier. Soft edges reshape the graph's readability and change the order you'd prefer, but they do not add waves. If you mark every edge blocking, you have paid for eight agents and received one. If you mark none, you get the tests-in-the-void collision.

The move that buys parallelism: name an interface

The interesting part is that a blocking edge is not fixed. The reason B cannot start is that A's output does not exist yet. So create a stand-in for that output — an interface: a schema, a function signature, a response shape, a stub — agree it, and now both sides can be built against it, in parallel.

flowchart LR
    S[Schema] --> M[Migration]
    S --> A[API]
    I["Response shape<br/>(agreed, one node)"] --> A
    I --> T[Tests]
Mermaid source — renders as a diagram where mermaid.js is available

The team spent one small node defining the interface. Before: API, then tests, two waves. After: API and tests in the same wave. That single node bought a wave.

This is sometimes called interface-first: build the contract before either implementation, so neither waits on the other. It is the same idea as the cycle fix in Lesson 98 — a shared interface removes the dependency, it does not just relabel it.

When not to convert

An interface is a bet. You are agreeing a shape that neither side has validated, and at the fan-in (Lesson 103) the two sides have to reconcile — and the stub might have been wrong.

So the rule is not "convert every blocking edge". It is:

  • Convert when the two sides are large, slow, or owned by different workers — the wait is expensive and the interface is cheap.
  • Leave it blocking when the dependency is small and fast, or when the shape is genuinely unknowable until A exists, or when a wrong interface would send both sides down a long dead end.

The critical path is the longest chain of blocking edges. Every blocking edge you keep is a sequential step you paid for. Every soft edge you left marked blocking is a step you did not have to pay for. The design is choosing, per edge.

The reframe: two tasks are parallel exactly when no blocking edge joins

them — so every blocking edge you turn into an interface is parallelism you

bought, and every soft edge you left marked blocking is parallelism you paid

for and never used.

"Blocking" and "soft" are this course's words. The underlying distinction is older than any tool; the mechanism pages move weekly, so confirm runner behaviour against Run agents in parallel.


Build It

Take the graph you built in Lesson 99, as tasks.json.

1. Label every edge

For each needs entry, answer one question in a table: can B produce a correct output if A never runs?

FromToBlocking or softWhy
schemamigrationblockingthe migration edits the schema
schemaapiblockingthe API reads the columns
apitestsblockingthe tests call the real endpoint

✅ You pass when every edge has a label and a reason, and the reason is about B's output, not about tidiness.

2. Drop the soft edges and recount the waves

Remove every edge labelled soft and re-run waves.mjs.

✅ You pass when the critical path shortens, and you can name the exact edge that did it.

3. Convert one blocking edge

Pick the blocking edge whose wait costs the most. Name the interface that could stand in for the producer's output — a schema, a response shape, a signature, a stub file. Make the interface its own node with edges to both sides.

✅ You pass when the two formerly serial nodes land in the same wave, and the interface node is a real artifact (a file you could commit), not the word "agreement".

4. Defend leaving one blocking

Pick one edge you are not converting and write one sentence for why the wait is cheaper than the reconciliation.

✅ You pass when the sentence names a concrete cost — an hour of building on a wrong stub, a two-sided rewrite — rather than "it felt safer".


Use It

SituationMove
B's output is literally made of A's outputBlocking. Leave it, and put it on the critical path.
B "should" follow A, and nobody can say whySoft. Remove the edge and run.
Two large nodes owned by different workers, one small interface between themConvert: build the interface first.
The interface shape is unknowable until A existsLeave blocking. A wrong stub is worse than a wait.
An edge you cannot classifyFind the artifact it carries. If no artifact moves across it, it is soft.
Everything looks blockingYou are describing a pipeline, not a graph. Check for preferences dressed as needs.
The critical path is too longFind the blocking edge whose wait is longest and cheapest to convert. Not the longest node.

Exercises

  1. Take a real plan of yours with at least six edges and label every one blocking or soft, with a reason. ✅ You pass when at least two edges are soft and you can defend each removal by naming what B does without A.
  1. For the longest blocking chain, write out the interface that would split the worst edge — an actual file, schema, or signature. ✅ You pass when the interface exists as something you could hand two implementers, and both could start from it today.
  1. Find a plan where someone converted an edge the wrong way — built on a stub that turned out wrong, or serialized work that never needed to wait. ✅ You pass when you can say which classification was wrong and what it cost.

Key Terms

TermWhat people sayWhat it actually means
blocking edge"a dependency"B cannot produce correct output until A exists. It adds a wave.
soft edge"also a dependency"A preference for order. B can start without A. It adds no wave.
interface"a stub"An agreed stand-in for a producer's output, so consumers do not wait for it.
interface-first"design first"Build the contract before either implementation, so both sides run in parallel.
critical path"the long pole"The longest chain of blocking edges. Its length is your floor.
over-blocking"being safe"Marking preferences as hard waits. It costs parallelism and buys nothing.
reconciliation"merging"The fan-in cost of running two sides against an interface that may be wrong.

What this lesson skipped

Claiming. Even a graph full of soft edges can have two workers grab one node. That is Lesson 101.

The fan-in cost in detail. How parallel children reconcile their outputs is Lesson 103.

Failure. A blocking edge whose producer fails — as opposed to merely running late — is Lesson 104.

Runner configuration. Whether a specific runner lets you declare an edge soft is version-gated; check the docs linked above.

Check yourself

4 questions. Answer before you reveal — recognising the right option is not the same as being able to produce it.

  1. A plan draws an edge from the marketing copy task to the release-notes task, with the note "copy first reads better." The release notes can be drafted against the feature list today. What kind of edge is it, and what should happen?

    1. Blocking — an edge was drawn, so the wait is real
    2. Soft — the notes can be produced without the copy, so the edge should be removed and the two run in parallel
    3. Blocking, but only because release notes are a sink node
    4. It should be converted to an interface, because every blocking edge must be converted
    Show answer

    B. Soft — the notes can be produced without the copy, so the edge should be removed and the two run in parallel

    Whether B can produce a correct output without A is the test; here it can, so the edge is a preference and should be soft. Option A is the over-blocking failure — an arrow drawn is not a dependency. Option C mistakes a structural position for an edge property. Option D is the opposite error: not every blocking edge should be converted, and this one should not be blocking at all.

  2. Two large teams — one on the API, one on the client — are serialised because the client needs the API's response shape. The shape is a small, stable object both sides could agree in ten minutes. What is the strongest move?

    1. Leave it blocking; the client genuinely cannot start without the API
    2. Make the API team finish first and hand over the shape
    3. Define the response shape as its own node and build both sides against it, in parallel
    4. Mark the edge soft and let each team guess the shape
    Show answer

    C. Define the response shape as its own node and build both sides against it, in parallel

    This is the textbook interface-first case: the interface is cheap, the wait is expensive, so agreeing it converts a blocking edge into a soft one and buys a wave. Option A is tempting because the client really does need the shape, but the shape, not the API implementation, is what it needs. Option B pays the full serial cost. Option D removes the dependency without replacing it, which is how the two sides diverge.

  3. A graph's worst blocking edge sits between two one-line edits in the same file. Converting it would mean inventing a file format both edits could target. What should you do?

    1. Convert it — every blocking edge is wasted parallelism
    2. Leave it blocking; the wait is tiny and a wrong interface would cost more than the serialisation
    3. Mark it soft without an interface
    4. Add a wave and hope the difference disappears
    Show answer

    B. Leave it blocking; the wait is tiny and a wrong interface would cost more than the serialisation

    The conversion rule is a comparison: convert when the wait is expensive and the interface cheap, which is not the case for two one-line edits. Option A is the tempting slogan and it is wrong — every interface is a bet that must pay for its reconciliation. Option C removes a real dependency without a stand-in, which will break. Option D changes nothing about the dependency.

  4. After labelling edges, a team finds their graph is a single chain: every node's output feeds the next. They conclude the graph tool is broken and there is no parallelism to get. What is the better conclusion?

    1. The tool is fine; the project is a pipeline and genuinely has no parallelism as decomposed
    2. At least one edge must be mislabelled, so mark three edges soft and re-run
    3. Parallelism is impossible in any graph with a critical path
    4. They should run every node at once and reconcile later
    Show answer

    A. The tool is fine; the project is a pipeline and genuinely has no parallelism as decomposed

    If every edge is a true blocking edge, the chain is real and the honest answer is that this decomposition has no parallel work — which is valuable to know before spending on agents. Option B is tempting because plans often over-block, but assuming a mislabel without evidence invents parallelism. Option C is false: a critical path always exists; what varies is how wide the graph is. Option D ignores blocking edges and produces the collision this lesson is about.