Earlier this year I was one of five engineers building a backend for a global CPG manufacturer. Each of us had our own Claude Code account, our own machine, and our own context window. None of the assistants could see what the others had read, decided, or built. The codebase was the only thing all five of us could see. That one fact shaped every process decision we made, and I think it generalises to any team that moves past one person using an AI coding tool.
The first principle, written down on day one
The operating-model doc that governed the build opens with a sentence I still quote:
Claude accounts cannot see each other’s conversations or memories. The only reliable shared state is the repo.
Everything else followed from that. The working documents moved into a docs/ folder in the repo, so every machine booted from the same versioned truth instead of a desktop folder or a shared drive. Per-account memory was allowed for personal context, but the rule for anything that mattered was explicit:
any decision, scope change, or finding goes into the committed docs, same commit as the work, or it doesn’t exist for the other two machines.
“Same commit as the work” is the part people skip. If the decision lands in a later commit, or in a chat message, or in one assistant’s memory, there is a window where another machine is building on the old assumption. We had that window bite us more than once, and I wrote about those incidents separately.
Five machines, one integrator
We called the machines M1 to M5. Each was one person plus one Claude account plus one checkout. The roles were not symmetric, and that asymmetry was the point.
M1 was the integrator. It owned every merge, every database migration, every ruling on ambiguity, the status board, and stakeholder reporting. When we re-split the story points mid-build because one machine ran out of tokens, M1’s number barely moved. The doc explains why, and I think the explanation is the most useful line in it for anyone leading this kind of team:
its load is not SP, it is the serialization duties that cannot be shared, every merge, every alembic migration, and the 🔴 reviews routed to it.
Giving the integrator more implementation work would have slowed all the other machines, because the queue it serviced was the queue everyone waited on.
M2 was the contract steward for one half of the scope. I was M3, carrying the other half at a reduced load because I also had a separate product to look after. M4 joined mid-build when M2 ran out of tokens, and took over M2’s queue plus a seat in the review rotation. M5 joined later still with zero story points by design: investigate first, then self-select work. Adding a machine with no allocation sounds wasteful until you watch what a fresh context does when it reads the codebase without a deadline. It finds things.
Claiming work is a commit, not a message
The claim protocol was simple. You claim a package by adding its row to the status board, in the first commit on your branch. If the row already exists, somebody else has it. There was no other way to claim.
Before a machine could claim anything at all, it had to post a READY row with a commit hash that matched the test baseline, and the integrator verified it:
No machine claims a Phase-1 package before its own READY row exists.
When M4 joined two weeks in, the gate was applied unchanged. The doc says so directly: “it is not waived for being late.” That sentence did more for trust between the machines than any amount of goodwill, because every machine knew every other machine had passed the same bar.
One package, one machine
Packages were the unit of work. The rule was:
One package = one machine. Packages are ≤13 SP with story-scoped contracts, never split a package across machines.
We started with 8 SP as a cap. Then a single 13 SP package covering four stories shipped cleanly, and the plan was revised: 8 SP became a target rather than a cap, because the cap “was not load-bearing on its own merits.” What actually decided whether to split a package was a ruling we called R-B:
whether the package contains two coherent contracts with a clean, story-defined seam, and whether only one of them needs 🔴 treatment.
The red marker meant the package touched something risky: an external write, authentication, untrusted input, or the state machine that gated side effects. Risky packages got a full review on a different machine than the author’s. Splitting a package along the seam between its risky half and its safe half meant the scarce review slot went only where the risk was. That is a better reason to split than a point count.
Review happens somewhere else
The cross-review rule was one line:
every 🔴 package’s Opus review runs on a different machine than the author’s. Non-🔴 can self-review.
The reviewer’s context had not built the thing, had not rationalised its shortcuts, and had been told to be adversarial. On one of my packages the reviewer came back with six findings and a list of test gaps. All six were right. I had spent a day with that code and my own assistant had spent the same day agreeing with me. A second context with a different brief does not agree with you by default.
The team contract lives in CLAUDE.md
The repo-level CLAUDE.md was not a developer guide. It was a contract. It listed the mandatory boot order (four documents, in sequence, before any work), the non-negotiables (one package one machine, all migrations through the integrator, risky packages reviewed cross-machine, thresholds through the config service, external writes through the state machine, secrets in the vault only), and one rule for uncertainty:
Blocked or unsure? Write it to
docs/newbuild/waiting-confirmation/and flag it, never assume.
The onboarding note for M4 added the cultural version of the same thing:
this project rewards disclosed limitations over confident summaries. Rulings get overturned by evidence, including M1’s own, twice on 2026-08-12. If something looks wrong, say so with the command that shows it.
“Say so with the command that shows it” is the whole method. An assistant that reports “all tests pass” is making a claim. An assistant that reports the command and the count is giving you something you can check.
What I’d tell you to do
- Decide, before the first line of code, what your only shared state is. If it is the repo, then nothing outside a commit counts, and you should say so in writing.
- Give one machine the serialization duties (merges, migrations, rulings) and do not load it with implementation. Its queue is everyone’s queue.
- Make claiming work a visible commit on the shared branch, with a readiness gate everyone passes, including late joiners.
- Split packages where the risk is, not where the point count is. Send only the risky half to cross-machine review.
- Put the rules in
CLAUDE.mdand treat it as a contract. Reward disclosed limitations. Ask for the command, not the summary.