We Removed the Barrier. Nothing Got Faster.

TL;DR

  • Barrier vs pipeline() on the identical two-stage job (fix, then verify), 43 real files: 154.5s vs 156.8s, a 1.5% tie, not a win for either shape.
  • The expected pipeline advantage didn’t show up because a mostly-uniform, mostly-fast field and the min(16, cores-2) concurrency cap dominate the wall clock more than the scheduling shape does.
  • The smell test (avoid unnecessary barriers) still holds. Its payoff is job-shape-dependent, not automatic. Measure before you refactor for it.

📊 Result, up front. Same 43-file corpus, same two-stage job, two scheduling shapes. Barrier (parallel() then a wait then parallel()): 154.5s. pipeline(): 156.8s. A 1.5% difference, not a win for either shape.

Snapshot B: await parallel(fix) -> BARRIER -> await parallel(verify)
Snapshot C: await pipeline(files, fix, verify)

That’s the whole experiment: the agent pipeline pattern tested against a barrier on the identical fix-then-verify job, run on the identical 43 files Part 3 already published. Part 3 fanned that job out behind a parallel() barrier and defined the barrier itself: nothing in the next stage starts until every thunk in the current stage returns. Part 4 priced the edge on the other side of that barrier and closed on the obvious next question: does the barrier between fix and verify cost anything on this job? This post answers it with a real run, not a diagram.

The promise: watch the same two-stage job run barrier-style and pipeline-style, read the honest result, and walk away with the two reasons the expected advantage didn’t show up here, so you know what to check on your own job before refactoring for a barrier you can’t yet prove is costing you anything.

Prerequisites:

  • Finished Part 3 (the 43-file fan-out): the barrier definition, the min(16, cores-2) concurrency cap, and the incident on scripts/run-content-series.sh. This post reuses all three without re-deriving them.
  • Finished Part 4 (the reduce step is free code): the immediate predecessor, whose close promised this exact measurement.

Step 1: Locate the barrier this post prices

This post answers the question Part 4’s close promised: does the barrier between the fix and verify stages cost anything on this job? Three posts built up to it: the loop’s spine ran one step per round in series, Part 3 removed that behind a parallel() barrier, and Part 4 made the return edge free and closed on this question.

  • The loop’s spine ran one step per round, awaited in series, nothing to overlap (Loop Part 2).
  • Part 3 removed “in series” from dispatch. parallel() is a barrier: it awaits every thunk before the next stage starts, and it defines the min(16, cores-2) concurrency cap this post reuses without re-deriving.
  • Part 4 made the return edge free and closed with the exact question this post measures: does the barrier itself cost anything?

Verify: if you’ve read Parts 3 and 4, none of the three bullets above are new. This post adds nothing about them, only the price of the edge sitting between fix and verify.

Step 2: The agent pipeline pattern vs a barrier

Only the edge scheduling changes between the two shapes tested here. Snapshot B dispatches all 43 fixes through parallel(), waits for the barrier, then dispatches all 43 verifies through parallel() again. Snapshot C uses pipeline(files, fix, verify): each file’s own verify starts the moment that file’s own fix returns, no global wait.

// Snapshot B -- parallel() barrier
const fixed = await parallel(files.map(fixStage));
const verified = await parallel(fixed.map(verifyStage));
// Snapshot C -- pipeline()
const verified = await pipeline(files, fixStage, verifyStage);

Same 43 tracked .sh files as Part 3’s corpus (35 findings across 12 dirty files, 31 already clean), same two-stage job, same isolation method: each snapshot runs against its own scratch copy so neither run sees the other’s edits. The only variable is whether a barrier sits between the fix stage and the verify stage.

Why this is the obvious next refactor: every “avoid unnecessary barriers” design doc points here. A parallel -> transform -> parallel shape with no cross-item dependency in the transform is the textbook case for pipeline(), since file A’s verify never depends on file B’s fix.

Verify: draw your own two-stage job as a data-flow line, the same test Part 4 used for its reduce step. If stage two’s work on item N depends only on stage one’s output for that same item N, and never on any other item, you have a pipeline candidate.

Step 3: Run both and read the near-tie

Barrier and pipeline() landed within 1.5% of each other on this job: 154.5s vs 156.8s. pipeline() was not faster. That’s the whole headline, and it’s the honest one: not a win for either scheduling shape, on this corpus, this run.

Snapshot B (barrier)Snapshot C (pipeline())
Agent calls86 (43 fix + 43 verify)86 (43 fix + 43 verify)
Wall clock154.5s156.8s
Delta+1.5% (C slower)
Subagent tokens3,364,3993,368,690
Tool calls253245
Verified-clean files42 / 4343 / 43

Snapshot B reached 42 of 43 files verified clean, not 43, because its fix-stage agent for scripts/run-content-series.sh broke the file’s syntax and a separate verify-stage agent caught it. That incident is fully disclosed in Part 3 and isn’t a barrier-vs-pipeline finding: both shapes leave the fix agent zero chance to self-correct before its own output gets checked.

Verify: read the delta column before the wall-clock column. A 1.5% gap on an 86-call job sits inside the range you’d expect from ordinary run-to-run noise, not a result either shape should be quoted as winning.

Step 4: Explain why the expected pipeline advantage didn’t show up

Two properties of this job explain the near-tie, both readable from Part 3’s own published dispatch-timestamp data: a mostly-uniform, mostly-fast field of 43 files, and a min(16, cores-2) concurrency cap that binds regardless of which scheduling shape is running, since both snapshots hit the identical 16-slot ceiling.

The field is mostly uniform and mostly fast. 31 of the 43 files are clean and return in roughly 15 to 22 seconds no matter which shape runs them; only 12 carry real fix work. A barrier’s real cost is bounded by how much longer its slowest item takes than the rest of the batch, and here that slowest item, scripts/run-content-series.sh, is an outlier either way: 122.9s in Snapshot C, the single longest node in the whole trace. The barrier waits for a straggler that would have been a straggler under pipeline() too.

The concurrency cap dominates more than the scheduling shape. Both snapshots hit the same min(16, cores-2) cap, confirmed directly off Part 3’s own dispatch-timestamp evidence for the fix stage: 16 agents launch essentially at once, and every file after the 16th queues behind them. Both shapes’ fix stage dispatches through the same call shape from Step 2:

Snapshot B: await parallel(fix) -> BARRIER -> await parallel(verify)
Snapshot C: await pipeline(files, fix, verify)

With 43 items moving through 16 slots on either line, most of the wall clock on either shape is queueing through that cap, not the difference between waiting for a barrier and starting early. pipeline()’s cap behavior here follows from the tool’s own shared-pool semantics; this run didn’t independently re-measure the cap under pipeline(), only under parallel() in Part 3, so treat that half as expected, not separately confirmed.

Verify: check your own job’s cap-to-item ratio before assuming a pipeline refactor will pay off. If your item count sits well under your concurrency cap, the cap isn’t the bottleneck, and the scheduling shape has more room to matter.

Step 5: Keep the smell test, drop the guaranteed win

No, this result doesn’t retire the pipeline smell test. What it shows is that the payoff is job-shape-dependent, not automatic: pipeline() is still the right call for an unnecessary barrier, precisely the mechanism the tool’s own design docs name below, it just wasn’t measurably faster on this particular job.

The design-doc smell test survives intact: a parallel -> transform -> parallel shape with no cross-item dependency in the transform should be a pipeline. Microsoft’s own Agent Framework docs name the mechanism this post measures, precisely: “the workflow does not advance to the next superstep until every executor completes.” That’s the cost of an unnecessary barrier, stated as a guarantee about mechanism, not a promise about wall clock on any particular job. The smell test is about avoiding that guarantee’s cost when you don’t need it, not about a measured speedup every time you remove it.

What would plausibly show pipeline()’s advantage clearly: a job with more uniformly-slow items instead of a mostly-fast field with one outlier, or a job with fewer items than the concurrency cap so queueing stops being the dominant cost. Neither was run here. That’s a real gap in this post’s evidence, named as a future measurement, not filled in with a number that doesn’t exist.

Verify: before refactoring a barrier out of your own job, check both conditions: are your items closer to uniformly slow than mostly-fast-with-one-outlier, and does your item count sit comfortably under your concurrency cap? If neither holds, expect a result closer to this post’s tie than to a clean win.

FAQ

Does removing a barrier in the agent pipeline pattern always make a job faster?

No. Measured here at 1.5% apart, essentially a tie, because a concurrency cap and a mostly-uniform field dominated the wall clock more than the scheduling shape did.

When does pipeline() actually beat a barrier design?

When items vary widely in duration, or when there are fewer items than the concurrency cap so queueing isn’t the dominant cost. Neither condition held in this run; it’s flagged as a future test, not a measured result.

Why didn’t the concurrency cap favor one shape over the other?

Both Snapshot B and Snapshot C hit the identical min(16, cores-2) cap. With 43 items moving through 16 slots, cap-bound queueing dominates either way.

Is barrier scheduling a safety net against bad agent output?

No. parallel() is a barrier, not a correctness guard. The one incident in this series happened under the barrier design, and the identical two-stage split gives the fix agent zero chance to self-correct regardless of scheduling shape.

Close

Barrier and pipeline() ran the identical two-stage job on the identical 43 files within 1.5% of each other: 154.5s vs 156.8s. That’s the verified outcome. Removing an unnecessary barrier is still a correct design instinct, the smell test holds, but its payoff is job-shape-dependent, not automatic. Measure before you refactor for it.

One pitfall to carry forward: don’t read a near-tie like this one as evidence that scheduling shape never matters. It matters more on jobs with uniformly-slow items or item counts under your concurrency cap, neither of which this corpus has. A second: don’t let a single run stand in for a distribution; n=1 per shape is a real measurement, not a statistically powered claim.

Part 6 ships next in this series.

{
"@context": "https://schema.org",
"@type": "Article",
"headline": "We Removed the Barrier. Nothing Got Faster.",
"author": { "@type": "Organization", "name": "ShipWithAI" },
"datePublished": "2026-08-17",
"description": "The agent pipeline pattern measured against a barrier on 43 real files: 154.5s vs 156.8s, a 1.5% tie. The cap decided it, not scheduling shape.",
"image": "/images/blog/graph-barrier-vs-pipeline-cover.png",
"articleSection": "tutorial",
"keywords": "claude-code, automation, ai, tutorial, english",
"mainEntityOfPage": "https://shipwithai.io/blog/graph-barrier-vs-pipeline/"
}
{
"@context": "https://schema.org",
"@type": "HowTo",
"name": "We Removed the Barrier. Nothing Got Faster.",
"description": "The agent pipeline pattern measured against a barrier on 43 real files: 154.5s vs 156.8s, a 1.5% tie. The cap decided it, not scheduling shape.",
"image": "/images/blog/graph-barrier-vs-pipeline-cover.png",
"step": [
{
"@type": "HowToStep",
"name": "Step 1: Locate the barrier this post prices",
"text": "This post answers the question Part 4's close promised: does the barrier between the fix and verify stages cost anything on this job?"
},
{
"@type": "HowToStep",
"name": "Step 2: The agent pipeline pattern vs a barrier",
"text": "Only the edge scheduling changes between the two shapes tested here."
},
{
"@type": "HowToStep",
"name": "Step 3: Run both and read the near-tie",
"text": "Barrier and pipeline() landed within 1.5% of each other on this job: 154.5s vs 156.8s. pipeline() was not faster."
},
{
"@type": "HowToStep",
"name": "Step 4: Explain why the expected pipeline advantage didn't show up",
"text": "Two properties of this job explain the near-tie, both readable from Part 3's own published dispatch-timestamp data: a mostly-uniform, mostly-fast field of 43 files, and a min(16, cores-2) concurrency..."
},
{
"@type": "HowToStep",
"name": "Step 5: Keep the smell test, drop the guaranteed win",
"text": "No, this result doesn't retire the pipeline smell test."
},
{
"@type": "HowToStep",
"name": "Close",
"text": "Barrier and pipeline() ran the identical two-stage job on the identical 43 files within 1.5% of each other: 154.5s vs 156.8s. That's the verified outcome."
}
]
}
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Does removing a barrier in the agent pipeline pattern always make a job faster?",
"acceptedAnswer": {
"@type": "Answer",
"text": "No. Measured here at 1.5% apart, essentially a tie, because a concurrency cap and a mostly-uniform field dominated the wall clock more than the scheduling shape did."
}
},
{
"@type": "Question",
"name": "When does pipeline() actually beat a barrier design?",
"acceptedAnswer": {
"@type": "Answer",
"text": "When items vary widely in duration, or when there are fewer items than the concurrency cap so queueing isn't the dominant cost. Neither condition held in this run; it's flagged as a future test, not a measured result."
}
},
{
"@type": "Question",
"name": "Why didn't the concurrency cap favor one shape over the other?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Both Snapshot B and Snapshot C hit the identical min(16, cores-2) cap. With 43 items moving through 16 slots, cap-bound queueing dominates either way."
}
},
{
"@type": "Question",
"name": "Is barrier scheduling a safety net against bad agent output?",
"acceptedAnswer": {
"@type": "Answer",
"text": "No. parallel() is a barrier, not a correctness guard. The one incident in this series happened under the barrier design, and the identical two-stage split gives the fix agent zero chance to self-correct regardless of scheduling shape."
}
}
]
}
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Blog", "item": "https://shipwithai.io/blog/" },
{ "@type": "ListItem", "position": 2, "name": "We Removed the Barrier. Nothing Got Faster.", "item": "https://shipwithai.io/blog/graph-barrier-vs-pipeline/" }
]
}