The Reduce Step Should Cost Zero Tokens

TL;DR

  • The edge between fan-out and synthesis is plain code: flatten, dedupe, count, filter. Measured on Part 3’s 43 returns: 0 tokens, 0.066ms.
  • The same reduce as one agent: 29,606 tokens and 7.7s for output that matched the code variant field for field. The agent got every number right.
  • So the case for code is cost and determinism, not accuracy. The test for your own merge: does it decide, or only arrange? Arranging is code. Deciding is a node, and a node needs a contract.

📊 Result, up front. Identical reduce, identical 43 returns, two implementations. Code: 0 tokens, 0.066ms. One agent: 29,606 tokens, 7,665ms, every number correct. Output compared programmatically, not eyeballed.

same input: 43 × {file, before, after, exitCode} + 35 × {file, code}
as code: 0 tokens 0.066ms → {totalFixed: 30, stillDirty: 1, ...}
as one agent: 29,606 tokens 7,665ms → {totalFixed: 30, stillDirty: 1, ...}

Two runs of the same agent workflow reduce step, over the same fan-out returns. One of them is 20 lines of JavaScript. The other is an agent call that produced the identical object and billed 29,606 tokens for it.

The problem: Part 3 fanned one fix node per file across 43 real files and collected 43 schema-validated returns. The edge that has to fan in those agent results is the agent workflow reduce step, and every orchestration guide draws it as another agent. This post prices that edge both ways, on the same data.

You are here: Loop Engineering’s state-file post made the same discovery on the memory axis. The loop’s bookkeeping, moving each work unit from ## open to ## passed, was never model work. It was a for loop with extra steps. This is the graph version: the wiring between nodes should cost what code costs.

Prerequisites:

  • Finished Graph Part 2: node contracts. The returns are objects because a schema enforced them, and that is the whole reason this post’s reduce is short.
  • Finished Graph Part 3: the 43-file fan-out whose returns this post consumes.
  • Helpful, not required: Graph Part 1 for the series glossary.

Step 1: Locate the agent workflow reduce step

The reduce step in an agent workflow is the edge that turns N fan-out returns into one summary a human or a downstream node can act on. In Part 3’s run, that means turning 43 {file, before, after, exitCode} objects into four facts: how many findings got fixed, how many files are still dirty, which shellcheck codes dominated, and what failed.

Written as a data flow, the whole step is one line:

43 × {file, before, after, exitCode} → code → {totalFixed, stillDirty, byCode, failures}

Notice what is not in that line: a model. Nothing between the returns and the summary requires judgment. Summing before - after is arithmetic. Counting files where after > 0 is a filter. Grouping findings by code is an accumulator. Anyone who has written a SQL GROUP BY or a jq pipeline has done this exact work without wanting an LLM in the middle of it.

The guides mostly point the other way. The top-ranking orchestration-patterns piece for this space puts it plainly: “The synthesis agent is the critical component” (thinking.inc’s 2026 guide), and prices its fan-out/fan-in pattern at 2 to 5 times single-agent cost.

Developers Digest’s coordination guide is more careful. “Most teams underestimate the merge step,” it warns, and the merge “requires a dedicated aggregator - either another agent or a deterministic merge function.” Both options named, neither measured. This post is the measurement.

Verify: write your own fan-out’s summary as a data-flow line like the one above. If every arrow is a rename, a sum, a count, a group, or a filter, you’re looking at code.

Step 2: Write the reduce as plain code

To combine agent outputs here takes four expressions over two machine artifacts: the 43 returns, plus the 35 {file, code} findings from the pre-run shellcheck -f json1 sweep. The node contract carries no finding codes, four fields only, so the by-code table reads from the sweep. Edges reduce the artifacts that exist. They don’t conjure fields the contract dropped.

const totalFixed = returns.reduce((s, r) => s + (r.before - r.after), 0);
const stillDirty = returns.filter((r) => r.after > 0 || r.exitCode !== 0).length;
const byCode = {};
for (const c of sweep.comments) {
const code = `SC${c.code}`;
byCode[code] = (byCode[code] || 0) + 1;
}
const failures = returns.filter((r) => r.exitCode !== 0);

One provenance note, stated in the same breath as the numbers, per this series’ rules. The fan-out was not re-run for this post. The 43 returns were rebuilt from the Part 3 trace’s documented per-file outcomes, on a corpus verified byte-identical: same repo commit (54a8f8fe), same shellcheck 0.11.0.

A fresh sweep reproduced the published split exactly: 35 findings across 12 files, 31 clean, same per-file and per-code counts. One field is a documented agent self-report rather than a verified count, and Step 4 is about that field.

Running it:

$ node reduce.js
{
"totalFixed": 30,
"stillDirty": 1,
"byCode": {
"SC2164": 7, "SC2181": 1, "SC2064": 18,
"SC2034": 2, "SC2016": 2, "SC2295": 5
},
"failures": [
{ "file": "scripts/run-content-series.sh",
"before": 4, "after": 4, "exitCode": 1 }
]
}

Measured cost: 0 tokens, because no model runs. Pure compute time for the four expressions: 0.066ms. The whole node process, interpreter startup included, lands at 33 to 38ms across three timed runs. And it returns this exact object every single time, which is a property no agent call has.

Verify: run the reduce twice and diff the outputs. Byte-identical is the point.

Try It Now. The reduce is small enough to be three jq one-liners against your own fan-out’s collected returns:

  1. jq 'map(.before - .after) | add' returns.json (on this post’s dataset: 30). Verify: equals your totalFixed.
  2. jq '[.[] | select(.after > 0 or .exitCode != 0)] | length' returns.json (here: 1). Verify: matches your count of dirty files.
  3. jq '[.[] | select(.exitCode != 0)]' returns.json (here: the one run-content-series.sh row). Verify: lists exactly the returns you’d escalate to a human.

Step 3: Price the same reduce as one agent

Agent result aggregation, done as an agent: handing the identical two inputs to a single subagent, same output shape requested, tools forbidden, cost 29,606 tokens and 7,665ms of wall clock, harness-reported. The output matched the code variant field for field, verified programmatically. The agent did the arithmetic on 43 objects correctly, first try, with 0 tool uses.

The invocation, and what came back:

prompt: "You are the reduce step of an agent fan-out. Do NOT use any tools.
Compute everything yourself, from the data below. [output shape] [43 returns] [35 findings]"
return: {"totalFixed": 30, "stillDirty": 1, "byCode": {...}, "failures": [...]}
usage: subagent_tokens: 29606 tool_uses: 0 duration_ms: 7665
CodeOne agent
Tokens029,606
Wall clock0.066ms (33 to 38ms with Node startup)7,665ms
Tool callsnone0
totalFixed3030
stillDirty11
byCode6 codes, 35 findingsidentical
failures1 fileidentical

Be precise about what this run does and does not show. It does not show that agents miscount. This one didn’t, and writing “the agent will get your numbers wrong” would be inventing evidence against the run’s own result. One call is also n=1, the same sample-size honesty Part 3 applied to its wall-clock headline: enough to price the edge, not enough to establish an accuracy rate either way.

What the run does show is rent. 29,606 tokens buys, in this graph, one summary that 20 lines of JavaScript produce for free. And the graph pays it per round: every time the sweep re-runs, the code edge costs zero again, and the agent edge bills again.

Determinism compounds the same way. The code edge output is reproducible byte for byte. The agent edge was right once, and the next call is a fresh draw.

There is prior evidence about agents and this corpus’s numbers, and it’s worth citing precisely. Part 3’s trace records the fix-stage agent for scripts/run-content-series.sh self-reporting before: 4 when the true count was 5, the same undercount in two separate runs.

That was a node miscounting its own input mid-job. It was not a reduce step over clean JSON. It’s evidence that self-reported numbers drift, which argues for verifying what enters your reduce, wherever the reduce itself runs.

💰 The trade, in raw numbers. One reduce over 43 returns: 0 tokens and 0.066ms as code, 29,606 tokens and 7.7s as an agent, identical output. Per round, every round the graph runs. No dollar conversion here; pricing the fleet is Part 11’s job.

Verify: if you already run an aggregator agent, replay one of its inputs through a code reduce and diff the two summaries. Agreement means you’re paying for arithmetic. Disagreement means one of them is wrong, and only one of them can be unit-tested.

Step 4: Read the number no reduce can fix

Both variants report totalFixed: 30, and both are correct given their input, because the input itself is short one finding. The dataset’s before values sum to 34 against a true 35. Part 3’s documented undercount, the fix agent’s self-reported before: 4 on a file that truly had 5 findings, is inside the returns this post reduces.

The reduce surfaced the failure cleanly:

"failures": [
{ "file": "scripts/run-content-series.sh",
"before": 4, "after": 4, "exitCode": 1 }
]

That row is the reduce doing its job: the one file worth a human’s attention, isolated by a filter on exitCode. What the reduce cannot do, as code or as agent, is know that before: 4 should have been 5. Both variants passed the undercount through identically, because a reduce sees fields, and no field carries the truth the node failed to report.

The boundary matters for where you spend effort. Bad numbers in the returns are a node problem: the contract’s numeric fields are self-reported, and Part 3’s run showed a self-report drifting twice on the same file. Catching that takes verification with no stake in the answer, a later part’s territory. The edge’s job is narrower: carry what the nodes returned, faithfully, for free.

Verify: jq 'map(.before) | add' returns.json against your pre-run sweep’s finding count. A mismatch is a node lying somewhere upstream, and no reduce will surface it for you.

Step 5: Promote deciding merges to nodes, keep arranging merges as code

Decide or arrange is the test that separates the two. Arranging covers flatten, dedupe, count, group, sort, filter, and threshold checks: all of it is code. Deciding covers ranking findings by impact, reconciling contradictory claims, and choosing what to escalate: that work needs a model, which makes it a node, and a node needs a contract.

This run’s 29,606-token price tag hangs on getting that split right.

Arrange (edge, code)Decide (node, contract)
Sum before - after across returnsRank the 12 dirty files by refactor risk
Count returns with exitCode != 0Choose which failure blocks the release
Group findings by shellcheck codeJudge whether two findings are duplicates in meaning
Filter failures for a human to readWrite the human-facing incident summary

Steelman the synthesis-agent guides before dismissing them, because their examples are often genuinely on the right side of this line. The thinking.inc guide’s synthesis agent “must reconcile contradictions (Agent A says market is growing, Agent B says it is shrinking)”. That is decide work. Staffing it with a model is correct, and then the model’s output needs the same schema discipline as any other node:

{ "type": "object",
"properties": { "ranked": { "type": "array" }, "escalate": { "type": "array" },
"rationale": { "type": "string" } },
"required": ["ranked", "escalate", "rationale"], "additionalProperties": false }

The failure mode this post names is quieter: taking that advice as the default for every fan-in, and paying agent rent on merges that only arrange. Developers Digest’s own example blends the two in a single sentence, “reconciling contradictory findings, deduplicating information, and producing a coherent final output”. Reconciling contradictions decides. Deduplicating by key arranges. Split them, and only one half needs a model.

The conceptual case for multi-agent coding systems is real. This post is one mechanical step of it, measured: when the merge is arrange-work, an agent bills real tokens for output that code produces identically at zero.

Verify: for each output field of your merge, ask whether two careful engineers would always produce the same value from the same input. Yes for every field means it’s an edge. Any no means you’ve found the node hiding in your merge, and it should get its own contract.

FAQ

Q: Should the reduce step in an agent workflow be an agent?

A: Not when it only arranges (flatten, count, group, filter). Measured here: an agent reduce cost 29,606 tokens and 7.7s for output identical to code at 0 tokens and 0.066ms. When the merge must decide (rank, reconcile, escalate), it’s a node, and it needs a contract.

Q: What does a code reduce over agent results look like?

A: About 20 lines. A sum for totals, filters for dirty and failed items, an accumulator for grouping. It works because the fan-out returns are schema-validated objects (Part 2’s contract), so there’s no parsing step to get wrong.

Q: Can an LLM aggregator get the arithmetic right?

A: This run’s did: every field matched the code reduce, on 43 objects, first try, n=1. The argument for code is cost and determinism. The code edge is free and returns the same output every run; the agent edge bills per round and offers no reproducibility guarantee.

Q: Does the reduce step catch wrong numbers in the returns?

A: No. This run’s input carried a documented undercount (a node self-reported before: 4 against a true 5), and both variants passed it through identically. A reduce is faithful to its input. Catching self-report drift is verification work, not edge work.

Close

You now have the measured pair: the identical reduce over 43 real fan-out returns, free as 20 lines of JavaScript, 29,606 tokens as a single agent that got every number right. And you have the test that generalizes it: arrange-work is an edge and should cost zero, decide-work is a node and earns its contract.

Pitfalls to carry forward. Don’t argue the code edge from accuracy; this run’s agent was flawless, and the argument survives on cost and determinism alone. Don’t let a correct reduce launder bad input; the totalFixed: 30 here is faithful arithmetic over a documented node-side undercount. And watch for merges that blend both kinds of work in one sentence; split them, and staff only the deciding half with a model.

Part 5 stays between the stages: what the scheduling of these edges actually costs, measured on the same corpus. Ships next in this series.