Two teams, one job, one difference

A plain-language walkthrough of the coordination experiment we are about to run — on 29 books of political philosophy, using Codex agents inside Herdr.

org-scalability · explainer · plan: .ai/tasks/001_spike_bookops_coordination_experiment_task_plan.md

01The question in one sentence

When a team of AI agents gets bigger, what should hold the team together — conversation, or a shared set of files everyone reads and writes?

That's it. That's the whole experiment. Everything below is about how to answer it honestly instead of just believing the answer we already like.

Messages communicate.
Files establish organizational reality.

That sentence is the claim under test. We think it's right. The experiment is designed so it can come out wrong.

02Explain it like I'm five

Imagine two teams doing the same job in the same office, with the same boss structure — a manager, team leads, and workers under each lead.

Team A — the talking team

everything lives in conversation

The boss tells a lead what to do. The lead tells the workers. Workers report back by talking. When a rule changes — "actually, keep the old chapter numbers" — the boss says it out loud, and it spreads by people telling each other.

  • Fast when the team is small
  • Nothing to maintain
  • But: if someone leaves, what they knew leaves with them

Team B — the whiteboard team

everything important is written down

Same boss, same leads, same workers. But the objective lives in a file. The current state of the work lives in a file. Every decision gets written down and given a number. Talking still happens — but only to say "go look at the board, something changed."

  • Slower to set up — someone has to keep the board current
  • But: if someone leaves, the board is still there

Both teams have the same boss structure. That's deliberate. We're not testing whether hierarchy helps — both teams have it. We're testing one thing only: where the team's memory lives.

Why this might matter With three people, talking is obviously fine. The interesting question is what happens at ten, or across four separate projects at once. Conversation is what a manager has to hold in their head. A whiteboard is not. If the whiteboard team's costs grow more slowly as the team grows, that's a real result. If they don't, we've learned something equally useful and cheaper than finding out later.

03The actual job: 29 books

An experiment needs real work, not a toy puzzle — otherwise you learn about the puzzle, not about coordination. Our teams will do a genuine job that is already queued up and waiting.

29 books of political philosophy — Plato's Republic through Foucault's Discipline and Punish — have been acquired, extracted, and frozen into 1,610 "source units" (chapters, or fixed 10-page slices for PDFs). Every one of them is stuck at the same place, waiting for the same next step.

Where they are on the ladder

A book climbs an eight-rung ladder. A checker program decides which rung it's on — and it stops at the first rung the book can't prove, so nobody can claim progress that isn't there.

1 · mappedall 29 books are stuck here
2 · gap-acceptedcoverage reviewed
3 · graph-approvedstructure agreed
4 · sections-authoredtext written
5 · renderednotes built
6 · decoder-acceptedquality gate
7 · authoring-completebook done
8 · release-eligibleour ceiling — no publishing

verified 2026-08-22 · node scripts/booklib/derive-milestone.mjs examples/republicnone (blocked at mapped)

What "mapping" actually asks for

To clear rung 1, each of a book's source units needs a written record with a heading, a synopsis, and a role — what this chunk of the book is doing. Rung 2 then asks for a coverage record per unit, and requires that a second agent independently reviewed it.

The checker refuses filler The validator has a function called meaningful() that rejects any field reading unknown, pending, tbd, todo, placeholder, none, or n/a. An agent cannot type "TBD" 1,610 times and claim a rung. This is why we can trust the score.
Hard limit Rung 8 — publishing — is excluded from every trial. It touches real infrastructure and needs Remy's explicit approval per book. No agent under measurement goes near it.

04How the team is wired

Below is the map. It answers one question: who talks to whom, and where does the team's memory live? Use the buttons to trace one flow at a time — the rest fades but stays in place so you don't lose your bearings.

THE ONE RULE · TRUE IN BOTH TEAMS Messages communicate. Files establish organizational reality. WHO WANTS IT AUTHORITY ONE BOOK, ONE CELL HANDS EXECUTION 👤 Remy sets priorities approves risky calls Chief outside the fleet talks to leads only — never to a worker — never by "current pane" Issue Lead one per book splits up the work — sole writer of that book's state Scout reads, never writes Mapper the only writer Reviewer reads, never writes The book itself git worktree per book source-unit-map.json coverage-ledger.json checked by derive-milestone.mjs — the scorekeeper 1 2 3 4 5 REALITY PLANE · $FLEET_ROOT · TEAM B ONLY brief.md the objective and the constraints state.md what we believe now — lead writes, all read decisions/D-017.md rules with numbers so they can be cited reports/ what each worker actually found 6 7 8 NOT PART OF THE FLOW ✕ worker ↔ worker messaging banned in both teams — side channels make coordination unreconstructable BookOps' own files (leases, blockers, events) identical in both teams — task bookkeeping, deliberately excluded from the comparison
The organization under test. Both teams share this entire picture except the blue Reality Plane, which exists only in Team B.
giving work (down) reporting back (up) reading & writing shared state excluded from comparison banned
1Remy → Chiefpriorities and anything the chief isn't allowed to decide alone
2Chief → LeadASSIGN — one book, its objective, its constraints
3Lead → Workersa task contract: what to do, what you may touch, what counts as done
4Workers → LeadREPORT what was found, or BLOCK with the question
5Lead → Chiefstructured handoff, or escalation when it's outside the lead's authority
6Chief ↔ fileswrites priorities and cross-book decisions
7Lead ↔ filessole writer of state.md — synthesizes what workers found
8Workers ↔ filesread the brief and decisions; publish their own report
The single most important detail on this map Every mutable file has exactly one writer. The lead owns state.md; each worker owns only its own report. Nobody edits anyone else's file. This turns "who's in charge of what" into something the filesystem enforces rather than something agents negotiate — and it means two agents can never silently overwrite each other.

05What each agent is actually told

An agent only knows what its briefing tells it. We deliberately stripped away every background instruction this machine would otherwise inject — so the briefing is now the entire specification of how an agent behaves.

Every briefing has the same ten parts. And here is the part that matters: seven of them are word-for-word identical between the two teams. That isn't tidiness. It's the experiment.

The tension this solves The briefing has to do two opposite things at once. It must be matched between the teams — otherwise we'd be measuring which briefing was better written, not which architecture works. But it must also carry the difference, because Team B's whole method is the sentence telling agents where the shared files are. The only way out is to make every part explicitly same-or-different, and then check it mechanically instead of trusting ourselves.
Team A · talking
Team B · whiteboard
1
Who you areyour agent name, your role, which book, your folder, and the one person you report to
2
What you may decide alonedecide freely inside your task; anything wider goes up. “Escalate exceptions, not activity.”
3
What you may touchthe exact files you may write. Read-only means read-only — and your lead checks the diff
4
What “done” meansyou don’t decide this. The checker program does, and it rejects filler like “TBD”
5
Where your context lives“Your canonical context is this conversation… if two messages disagree, the later one wins.”
Where your context lives“Your canonical context is on the filesystem… if a message and the files disagree, the files win.”
6
How to speakevery message starts with its type — ASSIGN, REPORT, BLOCK, UPDATE — and names every file path you used
7
When to speakwhen something is finished or you found something that changes someone else’s work. Never “still working”
8
Where your report goes“…complete in the message itself, with nothing held elsewhere.”
Where your report goes“…complete in the file itself, then tell your lead that path.”
9
How you finish“…send the chief everything a reader would need who has not seen this conversation.”
How you finish“…update state.md, write the final handoff, tell the chief both paths.”
10
Neverno publishing, no creating other agents, no side conversations, no reporting output you didn’t actually run

three parts differ · seven are byte-identical · full text in .ai/analyses/006_spike_agent_contract_architecture.md

The trap we nearly walked into

Part 5 is the difference — so the tempting thing is to simply leave it out of Team A's briefing. Team A has no shared files, so why include the section?

Why that would have quietly ruined the result A briefing missing a section reads as vague. Team A would then do worse — not because talking is a weaker way to coordinate, but because we gave it a sloppier briefing. And it would have looked exactly like the result we're hoping for. So Team A's part 5 is a real instruction of equal weight: your context is this conversation, re-read it, and here's what to do when two messages disagree.

We check this with a program, not with good intentions

Before every single run, a linter renders both briefings and refuses to let the run start if any supposedly-identical part differs by even one character, if a differing part says more things in one team than the other, or if any file path is written as a shortcut instead of in full.

It caught us on the first draft We wrote the rule "differing parts must be within 10% of each other in length," then measured our own draft: part 8 was 21% apart. The underlying problem was that we'd never said how to count — a long file path could be one thing or eight things depending on your mood, which made the rule unenforceable. Fixing it also improved the writing: both teams are now told their report must stand on its own, one in a message and one in a file.

The rulebook itself

The four kinds of message — and when to send them

Every message begins with its type as the first word, in both teams. This is also how we later count what kind of coordination was happening.

ASSIGN <book>   work handed down
REPORT <book>   findings handed up
BLOCK  <book>   stopped — with the question that would unblock you
UPDATE <book>   something you already know about has changed

Cadence. Report when a deliverable is complete, or when you found something that changes what someone else should do — not on progress. Block immediately when stuck, never silently and never by working around it. Send no acknowledgements, no status pings, no "still working." Don't ask a question you could answer from the sources part 5 gave you.

The original design says what the message types are but never when to send them. Left unstated, the two teams would end up differing in chattiness for reasons that have nothing to do with where files live — which would corrupt the main measurement directly.

Who may decide what, without asking
Worker     how to do the assigned task; what evidence to gather;
           reversible changes inside its own scope
Lead       how to split the book up; who does what; the order;
           the approach within this one book
Chief      priorities across books; anything with reach beyond
           one book; retiring or reframing a book
Remy       strategy, anything irreversible, and publishing

The closing line is identical in every briefing: “Escalate exceptions, not activity.” A system where everything escalates doesn't scale; a system where anything can be decided anywhere doesn't stay aligned.

What a report has to contain

The shape is the same in both teams — only where it gets delivered changes.

REPORT <book>
OBSERVATION      what is true, in a sentence or two
EVIDENCE         full file paths, commands run, and their actual output
CONFIDENCE       high | medium | low — and what would raise it
RECOMMENDATION   what you think should happen next
OPEN QUESTIONS   anything you could not resolve

Why evidence must be raw output, not a summary of it. One of the things we're measuring is what gets lost when a lead compresses several workers' findings into one shared view. If the worker already summarized the detail away, there's nothing left to measure the lead's compression against.

The one rule the filesystem enforces for us

Every file has exactly one writer. The lead owns the shared state file; each worker owns only its own report; nobody edits anyone else's.

We're honest in the briefing about what this is: policy plus the lead reviewing the diff — not machinery. An earlier version of this system tested it directly by asking a read-only agent to edit a file, and it complied. So the lead's briefing carries an explicit obligation to review diffs before accepting work, and read-only agents are told to report it if they break the rule.

An agent that violates its briefing is data, not a broken run — those are precisely the coordination failures we're counting. Only two things void a run: a briefing that failed the linter, and an agent that turned out to be running the wrong model.

06How we keep score

An experiment is only as good as its scoreboard. Ours has one rule: the agents don't get to grade themselves.

The score: rungs climbed

We call the score Y. It is the number of ladder rungs the books climbed during a trial, added up across every book in the batch. If a book slips backwards, that counts against us.

Why not just "did it finish?" Because a yes/no score is useless the moment both teams finish — every comparison collapses to nothing. And partial progress is exactly what we care about at larger scale, where nobody finishes everything. Rungs give us partial credit that still can't be faked.

There is one honest catch, and we've written it into the plan: progress within a rung is invisible. A team can do six hours of genuine work and score zero if it didn't cross a boundary. That's why the seed books are chosen sitting just below a rung, and why each book's starting rung is recorded before the trial begins.

The two costs, never added together

What we countWhere it comes fromWhy it's separate
coordination
on the wire
the text of every message sent between agents, plus the bytes of shared files read and written This is "how much talking did it take." Team B pays a file cost here that Team A doesn't — and it still has to win on the total.
context
consumed
tokens the chief and leads burn per turn, taken from Codex's own accounting This is "how much did the manager have to hold in their head." Team B may lose here — pointing agents at files means they read files.
The trap we're avoiding If you mash these into one "tokens" number, you can report a win by hiding a cost. Worse — if we counted "how many raw worker transcripts did the manager read," Team B would win by definition, since reading files instead of transcripts is literally what Team B is. That's not a finding, it's a tautology. So the costs stay split, and the file cost stays inside the headline number.

07How we watch without interfering

Here's a nice piece of luck. Codex writes a complete diary of every session to disk on its own — ~/.codex/sessions/…/rollout-*.jsonl. We don't have to instrument the agents at all. We read the diaries afterwards.

agents worknothing added to their path — no wrappers, no probes
Codex writes rolloutsevery prompt, every tool call, every token count, per turn
we harvestmatch sessions to the trial by working directory and time window
three logsmessages · file writes · human interventions
metricsderived, never hand-written

Each diary entry records the working directory, the exact prompt text, the tokens used — split into fresh, cached, output and reasoning — and, on every single turn, which model and which reasoning effort was actually running.

Why that last one matters more than it sounds We ask for gpt-5.6-sol at high effort. But asking isn't getting — we found that the flag commonly used to set effort is silently ignored, so agents quietly inherit whatever the machine is set to. Because the rollout records what actually ran, we can check instead of trusting. Any trial whose turns weren't all gpt-5.6-sol/high is thrown away.

Two more deliberate choices. We never ask agents "what do you think the current state is?" mid-run — that question is itself a coordination message, and it would inflate the very cost we're measuring. We work it out from the record instead. And the raw diaries are archived somewhere the agents cannot read — because in Team A the raw worker output only exists there, and if agents could read it, Team A would have accidentally been given a whiteboard.

08What would prove us wrong

This is the part that makes it an experiment rather than a demo. We wrote these down before running anything, so we can't move the goalposts later.

The decision rule, fixed in advance We only claim a result if it points the same way in at least 3 of 4 paired runs and the gap is bigger than the random noise we measured beforehand. Anything that fails that bar is reported as not supported — not re-analyzed with a different denominator until it looks better.

And the strongest possible result isn't "Team B won." It's "the gap grew as the team got bigger." A win at three agents could be luck. A widening gap across scale is a scaling property, which is the thing actually worth knowing.

09The order we do it in

P0 · prerequisitespin tool versions; clean-shell launcher so agents don't inherit stray API keys
P1 · scaffoldfolders, run manifest, gitignore
P2 · seedfreeze one real book as a hash-checked tarball — everything gates here
P3 · instrumentsthe harvester and the three logs
P4 · protocolthe contracts, matched in length across both teams
P5 · dry runTeam B only, on a throwaway demo book — measures nothing, proves the loop runs
P6 · noise check5 identical runs to learn what random variation looks like gates P7
P7 · the actual A/B4 paired runs, both teams, same book, same start
P8 · scale sweepmore workers, then more books at once
The riskiest step is P2, and we know why Restoring a saved book also restores BookOps' old leases — its record of who was working on what. The system is built to refuse a takeover from an owner that might still be alive. So a restored seed could land in a permanent stand-off. The bar before anything gets measured: restore, check, confirm the book reads back at exactly its recorded rung, and successfully take the lock — twice in a row from the same tarball. If that fails, we stop and fix it rather than measuring on top of it.

10Four things we already learned by checking

Before writing a line of harness code, we ran the commands. Each of these was a belief that turned out to be wrong.

We assumedActuallySo
Setting reasoning effort on the command line works reasoning_effort isn't a real key — it's ignored, and the machine default (xhigh) wins. The real key is model_reasoning_effort. Use the right key and verify per turn from the rollout
Codex agents would be harder to observe than Claude ones The opposite. Rollouts record working directory, session id, model and effort per turn, and token counts split four ways. Better instrumentation than the original design assumed
There's a coordinator process to disable so it doesn't compete with our org bookops coordinator run is documented but not wired into the CLI at all. No conflict to resolve — our org just drives the CLI
A tracked git baseline could reseed the books 121 of 123 book folders are gitignored, so they're local-only. And BookOps' own doctor reports the pipeline blocked because its private folder isn't ignored — while 387 of its files sit tracked in git. Hence the tarball seed, and a hygiene fix to hand back upstream
The honest bias, stated up front BookOps keeps some of its own state in files — blockers, events, leases — and both teams use it. So Team A gets a little bit of whiteboard for free, which makes the two teams more alike than the ideal experiment would want. That shrinks any difference we might find. We're recording it because it pushes the result toward "no difference" — the conservative direction — rather than flattering the thing we're hoping to show.