Harness engineering for a fleet: how I run Claude, Codex and pi across a dozen repos

In brief

A harness is everything around the model: the instructions it reads, the tools it may call, the memory that survives a context reset, the checks that decide whether a run passed, and the gates that stop it doing what the rules forbid. On a normal day here there are ten to fifteen agent panes open across six or seven repos, three providers and several accounts. This is the setup as it stands on 24 October 2026, and every rule in it has a receipt.

Title card reading 'Harness engineering for a fleet', with the line 'Codex builds, Claude coordinates, pi attacks' beneath it.

A harness is everything around the model: the instructions it reads, the tools it may call, the memory that survives a context reset, the checks that decide whether a run passed, and the gates that stop it doing what the rules forbid. For one agent on one task that is a contract, a map, external memory, sensors and mechanical gates. On a normal day here there are ten to fifteen agent panes open across six or seven repos, three providers (Claude Code, Codex and pi), and several accounts with separate cloud identities. At that scale the harness has to answer three more questions: who builds, who talks to whom, and what the coordinator does.

This is the setup as it stands on 24 October 2026. Every rule in it has a receipt, meaning an incident that cost money or a weekend.

1. One instruction file, and it is a map

There is a single global instructions file. It is canonical in one place and symlinked into every agent's config directory, so Claude, Codex and every account profile read the same text. Nobody forks a copy.

It holds rules about how to work, and nothing else. Reference material (the server estate, cloud traps, deploy models) lived in it until today, when it was split out into separate docs with a one-line pointer each. The file is loaded into every session, and a session started in the home folder loads it twice, so every paragraph of reference in it was paying rent in every conversation. The cloud traps now live in a path-scoped rule that loads only when an agent touches a deploy manifest, an env file, a Dockerfile, Terraform or a workflow.

2. Memory in three tiers, with a hard wall between them

Global memory holds cross-project facts about tools and access. Each repo has a HANDOFF.md at its root that holds state, decisions with attribution and date, assumptions, people, next steps and how to unwind. Repo-specific lessons go in that repo's project memory. Client content never goes in the global tier; the database and the file layout enforce that, not goodwill.

A session-start hook loads the memory index. A stop hook reminds the agent to update the handoff. The rule is that any session that changed state, a decision, an assumption, a person or a blocker updates HANDOFF.md and commits it with the change, without being asked. A fresh session starts from that file, which is also how a long conversation gets retired before it degrades.

3. Codex builds, Claude coordinates, pi attacks

Any work order with a written spec goes to Codex. Claude coordinates and inspects the result once, through a read-only inspector subagent that returns findings as claim, verdict, evidence and correction, and never fixes anything itself. pi is the security reviewer: threat models, auth, secrets, injection, attack paths through a diff. Claude and Codex keep correctness and design review. Nobody certifies their own changes.

Review loops are capped at two fix rounds per reviewer. After that the branch is held and the open findings are reported. Reviews run in batches, one Codex review and one pi review per integration round covering every branch in it. The receipt is a session on 19 and 20 October that hit the weekly usage limit after one fix ran fifteen review rounds and Claude subagents did most builds at 300,000 to 620,000 tokens each.

4. The model is chosen on every dispatch

A subagent dispatched without a model inherits the main session's model, which is Opus. One repo spent 77 per cent of its account on Opus subagents that way. The rule is Sonnet by default for searches, tests, CI waits, merges, drafting and well-specified builds; Opus only when a wrong call is costly, with the reason in the brief; Haiku for trivial lookups. As of today a PreToolUse hook refuses an Agent dispatch with no model set.

5. Sensors, and what counts as green

CI is the gate. A push is not finished until the run completes, and the agent reads the failure before judging it. A green lint job next to a red integration job is a red run. A green local suite is not the same claim, because CI builds the database and environment from scratch and local runs never do. Where a change could shift runtime behaviour, the agent runs both paths against main and diffs the outputs, because passing tests do not prove the behaviour changed correctly.

6. Dual encoding: the rules that bit twice became hooks

Every rule above existed as prose. Prose fails under load: the model reads the instruction, parses twenty other constraints, and skips it when the context is full. Today the five rules that have cost the most were given a mechanical gate in Claude Code's PreToolUse hooks:

  • An Agent call with no model is refused.
  • A docker run without --rm is refused.
  • A deploy command (Cloud Run, Ansible, Wrangler, Vercel, a provisioning script) is refused unless the latest CI run on the current branch is completed, green, on the same commit as HEAD, with nothing unpushed. If the CI state cannot be read, it is refused.
  • A gcloud login is refused unless the folder is mapped to a cloud identity or the command sources the repo's env file first. The receipt is a login that silently repointed a different estate.
  • Reading a .env file prompts. Sending or forwarding mail prompts, even where the connector is otherwise allowed without prompts.

The prose stays in the instructions file so the agent understands the goal. The hook makes the failure impossible rather than unlikely. The gates fail closed where the action is rare and expensive, and prompt where the action is sometimes legitimate.

7. The fleet layer: panes, peers and one coordinator

Each agent runs in its own pane in a terminal workspace manager (Herdr). Agents in the same repo or across repos message each other directly with a small peer tool, by pane, by repo or by agent kind. Handoffs, second opinions and review requests go peer to peer instead of through me. A send receipt is not an acknowledgement, so a relay stays alive until the peer answers or a bounded timeout expires.

One pane is the coordinator. Its job is status, dispatch and decisions, not building. A status skill reads every pane's Herdr state, session transcript and repo state and reports in three bands: needs me, working, done or idle. It never prompts a working or blocked agent, because that interrupts the turn or answers its pending approval. From a phone, the coordinator can relay a decision, answer a blocked agent's prompt one keypress at a time, and queue work for the agents that are idle.

Delegation never widens authority. A subagent drafts freely but never sends mail, posts, deploys or changes production without the same confirmation I would have to give the main session. Messages between agents do not expand what any of them may do.

8. Account and identity routing is part of the harness

Each folder group maps to an account: a Claude profile, a Codex login, a cloud configuration directory, a browser profile. The mapping is set per folder automatically, in agents and in shells, and an unmapped folder gets no cloud login at all. The wrong cloud identity fails as a 403 that reads like missing IAM, which cost hours before the routing existed. Terraform's state backend uses application default credentials rather than the active account, so the routing covers that separately.

9. Disk and spend are sensors too

A session checks free disk before any run that writes large artefacts, and stops under a threshold. Every ephemeral container runs with --rm and a named volume the teardown deletes. A worktree is removed once its pull request merges. The receipt is a clean-up on 16 October that recovered 313 GiB of frozen input copies left in 79 merged worktrees, and 234 GB of anonymous Docker volumes from test databases, 1,376 of them.

What to copy first

If you run one agent, the first six sections are the list. If you run several, start with three things. Put the model choice on every dispatch and gate it. Give every repo a handoff file that a fresh session can resume from, and make updating it part of finishing. Then take the three rules that have already cost you something and turn each into a hook. The prompt guides the intention. The gate protects the system.

Questions and answers

What is harness engineering?

A harness is everything around the model: the instructions it reads, the tools it may call, the memory that survives a context reset, the checks that decide whether a run passed, and the gates that stop it doing what the rules forbid. Harness engineering is the design of those parts, and at fleet scale it also decides who builds, who talks to whom and what the coordinator does.

Who builds and who coordinates?

Any work order with a written spec goes to Codex. Claude coordinates and inspects the result once, through a read-only inspector that never fixes anything itself. pi is the security reviewer. Nobody certifies their own changes.

Why use hooks rather than instructions alone?

Prose fails under load: the model reads the instruction, parses twenty other constraints, and skips it when the context is full. The prose stays so the agent understands the goal, and the hook makes the failure impossible rather than unlikely.

What should I copy first?

Put the model choice on every dispatch and gate it. Give every repo a handoff file that a fresh session can resume from, and make updating it part of finishing. Then take the three rules that have already cost you something and turn each into a hook.

If you are running more than one agent and want to talk through the harness around them, I would welcome the conversation.

Get in touch