Sertaç Yıldırım field notes

Home → Engineering

Multi-Agent: Work You Can Split, Not Free Speed

“Review every rejected withdrawal from last month and give me the reason breakdown.” 34 records, one agent, 11 minutes. I split it four ways: 3 minutes. I was pleased. Then I looked at the bill: 3.4 times the tokens. Then I looked at the output: two agents had written to the same summary file and one had overwritten the other’s line. What got faster was not the work; it was only the wait.

In short
  • Parallel work does not speed up work; it speeds up splittable work. Three conditions: the parts are independent, each result fits in a short summary, and the merge rule can be written as code. Miss one and multi-agent buys tokens, not speed.
  • Two agents writing to shared state are two threads writing to shared state. The same race condition, plus model noise on top. In our second run a line disappeared and nobody noticed for two days.
  • A sub-agent has its own context, and that is half the win. Each agent only sees its 8–9 records. Less noise, no full window. The price: the detail never comes back up, only the summary does.
  • The real work is in the merge. Turning 4 summaries into one report is not addition: labels clash, wordings differ, records repeat. Give the merge to a model too and you lose in one step what four runs gained.
  • Agents should not talk to each other. Star topology: sub-agents only see the orchestrator. Free chat has no turn ceiling; ours agreed on the same wrong answer after 9 turns.
  • Fix the single agent first. 6 of those 11 minutes came from fetching the same report three times. Parallelising broken work just makes four copies of the breakage.

What exactly are we parallelising?

In the previous post we wrote the flow in code: chain, router, planner–worker. In the third pattern, a longer plan means a longer run. “Last month’s rejected withdrawals” meant 34 records, and each record got the same treatment: fetch the record, read the rejection reason, look at the customer’s earlier withdrawals, assign a label.

That looks like work you can parallelise. Three questions decide whether it really is:

QuestionOur answerIf the answer were no
Are the parts independent? Does one touch what another writes?Yes, independent: each record is a different customer, and all of it is read-onlyYou need a lock; if you need a lock, parallelism is a cost, not a win
Does a part’s result fit in a short summary?Yes: record id, label, one sentence of reasoningYou would have to carry all the detail upward, and then one agent is cheaper
Can you write the merge rule as code?Partly — this was the hard oneThe merge becomes another model step, and the errors pile up there

I should have said “partly” to the third question, and in the first run I did not. I paid for it in the merge step.

Parallel work does not speed up work. It speeds up splittable work. Work you cannot split just gets done four times.

From the field: 34 withdrawals, 4 agents, two mistakes

The first setup was simple: the orchestrator split 34 records four ways, four sub-agents ran with the same prompt, each reviewed its share and appended its result to summary.md.

One agent4 sub-agents (first try)
Wall clock11 min 04 s3 min 12 s
Total tokens96k327k (3.4×)
Tool calls7184 (13 duplicated: same customer, two agents)
Output34 lines33 lines — one overwritten
Are two runs the same?RoughlyNo: the order changes every run

The 3.4× token number is no surprise: every sub-agent carries its own system prompt, tool definitions and instructions from scratch. 4 agents means paying the fixed input cost 4 times. I could live with that. Two other things I could not.

Mistake one: writing to a shared file

Four agents appended lines to summary.md at the same time. Two read it in the same second, both added their line and wrote it back; the second one deleted the first one’s line. This is exactly the story in why stock goes negative — read, modify, write. The only difference is that there it was two threads and here it is two models. The model being clever does not win this race. What wins it is every agent writing its own output somewhere separate, and code doing the merge.

Wrong and right
# WRONG: four agents, one file
for part in parts:
    spawn(agent, prompt=part, output="summary.md")   # race

# RIGHT: each agent returns its record, code merges
results = run_parallel(
    [lambda p=p: sub_agent(p) for p in parts], concurrency=3
)
report = merge(results)     # code: sort, deduplicate, check the count

Mistake two: duplicated reads

13 tool calls were duplicates. The cause was the split rule: I had cut the record list into four equal slices, but two withdrawals from the same customer landed in two different agents. Both fetched that customer’s history. The fix was to change the split key: split by customer, not by record. All records for one customer go to one agent. This is the same rule as in worker sharding: the partition key is the natural key of the work.

Sub-agent context: the other half of the win

The benefit of multi-agent that nobody talks about is not speed; it is isolation. When one agent reviews 34 records, everything piles into the same window: the detail of 34 records, 71 tool replies, notes in between. By record 20, the detail of the early ones has either dropped out or is muddying the decision. (A post of its own.)

In a sub-agent the window is clean: 8–9 records, its own tool replies, nothing else. We measured the quality difference against my own hand-labelling of all 34: the single agent got 27 labels right, the four-way split got 31. Same model, same prompt; the only difference is the stack put in front of it.

The price shows up here too: the detail a sub-agent saw does not come back up. Only its summary does. So the sub-agent’s output must be a record with fixed fields, not free text:

Sub-agent contract
{
  "record_id": "WD-24817",
  "label": "kyc_incomplete",        # closed set: 6 labels + other
  "reason": "Address document is older than 6 months.",  # one sentence, 200 chars
  "confidence": "high",             # high | low
  "records_read": 3                 # how many tool calls it made
}

The confidence field was added later and turned out to be the most useful one: the merge step pulled out the 5 low-confidence records and gave them to a person. Instead of asking the model to be certain, it is cheaper to give it a field where it can say it is not.

Do not ask a sub-agent for text. Ask it for a record. You cannot merge text; you can merge records.

Merging: this is the real work

Four summaries arrive and the report does not write itself. What broke in the merge:

  • The count did not add up. 34 went out, 33 came back. The first job of the merge step is counting: does the number of parts sent equal the number of records returned? If not, no report is produced and the run fails loudly.
  • Two names for one thing. One agent wrote kyc_incomplete, another wrote document_missing. A closed label set fixed this; as long as the set is open, every agent invents its own vocabulary.
  • Duplicate records. When two withdrawals of the same customer landed in two agents, the customer was reported twice. Fixing the split key ended it.
  • Order. In a parallel run, results come back in random order. If the report is sorted differently every run, you cannot diff two reports. The merge step sorts deterministically, by record id.

None of this is model work: counting, mapping, deduplicating, sorting. The first time I gave the merge to a model, 2 of the 34 records never made it into the report, and it took me two days to notice. When code merges and the same thing happens, the program stops.

Topology: a star, not a chain

In most multi-agent examples online, the agents talk to each other: a “critic agent”, a “writer agent”, a “manager agent”. I tried it once. Two agents — one labelling a record, one reviewing the label — wrote to each other for 9 turns and ended up agreeing on the same wrong label. The reviewer read the reasoning behind the thing it was reviewing, and was convinced by it.

Star topology
  • Sub-agents only see the orchestrator, never each other.
  • If one needs another’s output, it gets it from the orchestrator as a fixed-field record.
  • Turns are bounded from above: 3 per sub-agent, 8 per run.
  • If you need review, the reviewer does not see the reasoning, only the record and the data.
Free chat
  • Two agents talk until they agree; there is no ceiling.
  • Cost cannot be worked out in advance.
  • You lose track of which sentence changed the decision.
  • A reviewer that reads the reasoning tends to approve it; it is not independent.

Fix the single agent first

The most important point came last: I went through the 11-minute single-agent run. 6 minutes of it went into fetching the same report three times. With repeat protection and a better tool contract, the single agent went from 11 minutes to 5 minutes 40 seconds — with no parallelism and no extra tokens.

Then I split it four ways: not 3 minutes 12 seconds but 1 minute 50 seconds. In other words, the fix before parallelising was worth more than the parallelising itself, and it was free. Order matters: parallelise broken work and you get four copies of the breakage and four times the bill.

StageTimeTokensCorrect labels (out of 34)
One agent, first version11 min 04 s96k27
One agent, repeat protection + tool fix5 min 40 s71k29
4 sub-agents, shared file (first try)3 min 12 s327k31, one line lost
4 sub-agents, split by customer + code merge1 min 50 s198k31

In the last row the tokens are still 2.8 times the single agent. Was it worth it? This job runs once a month and has to be ready for the morning meeting: yes. If it ran 400 times a day, the answer would be no. Cost per task exists to answer exactly this question.

What to watch

  • Parallel efficiency. Single-agent time ÷ (number of sub-agents × parallel time). Close to 1 means a good split; under 0.5 means the parts are not independent.
  • Duplicate tool call rate. The one number that tells you the split key is wrong. Ours went 18% → 0.
  • Tokens per sub-agent. If one is double the others, the parts are unbalanced.
  • Records rejected in the merge. If the count does not match, no report is produced; anything above zero has its root cause in the sub-agent contract.
  • Share of low-confidence records. The work that reaches a person. If it is rising, the input got harder, not the model.
  • Same input, same report. After deterministic sorting, the diff should be clean.

Checklist

Before you add the second agent
  • Did I fix the single agent? How much of its time is wasted?
  • Are the parts really independent? Which one touches what another writes?
  • What is the split key? Does the same entity land in two parts?
  • Is the sub-agent output a fixed-field record or free text?
  • Is the label set closed? Is there an “other” and an “unsure”?
  • Does code do the merge? Is there a count check?
  • Is the report sorted deterministically? Is the diff of two runs clean?
  • Do sub-agents see each other? If so, why?
  • What is the concurrency limit? Can the database behind the tools take it?
  • Is the token budget per run, per agent, or both?
  • If one sub-agent dies, what does the run do: wait, or produce a partial report?

Conclusion

Four agents took 11 minutes down to 3 and I was pleased. That was the wrong place to be pleased: the real win came when we found the 6 wasted minutes in the single agent. Parallelism was added on top of that and the result was good — but in the other order we would have made four copies of broken work and tripled the bill.

What I learned when two agents wrote to the same file was not new either: it is a race condition, and model intelligence does not solve it. Sub-agents return their own records, code does the merge, and if the count does not match the program stops. There is no model in that part, and that is a good thing.

The sentence to remember: multi-agent is not a speed technique, it is a splitting technique. If you can split the work, the number of agents is an engineering decision. If you cannot, then however many agents you add, you are doing the same work several times.