BLUF: When Agents Coordinate, Coordination Fails First
The intuitive model of multi-agent failure places the risk inside the agent: a bad model weights decision, a hallucinated tool call, a reasoning error that compounds through a long context. That model is correct as far as it goes, but it misses where most production failures actually happen. When agents are composed into a system — when one agent's output becomes another's input, when agents compete for shared resources, when an orchestrator depends on worker self-reports — the failure surface is not inside any agent. It is in the interfaces between them.
The production evidence for this claim is not hypothetical. In the keller-platform reference implementation, the concurrency semaphore that prevents OOM from multiple Claude Code CLI subprocesses is a module-level primitive initialized before any agent runs, not a property of any individual agent. 1 The stop-loop protection that interrupts a runaway agent is tested against a threshold of more than three stop events, not against the internal logic of what any single stop event means. 2 And in the orchestration practice documented in the whitepaper build pipeline, the failure mode that forced the byte-level verification pattern was not a reasoning error inside an editor agent — it was a self-report that a provenance footer was already present, when it was not. 5 Each of these failures is a coordination failure, not a capability failure.
The five patterns this paper names are responses to that observation. Each pattern addresses a specific coordination failure mode: resource exhaustion at the concurrency boundary, write conflicts at the shared-state boundary, trust violations at the verification boundary, and corruption at the workspace boundary. Together they constitute an architecture that treats agent failure as normal and builds containment around it rather than hoping individual agents do not fail.
Single agents are bounded by capability. Multi-agent systems are bounded by coordination. Production failures occur where agents meet — not where any single agent reasons.
Pattern One — Concurrency Caps via Semaphores
The first failure mode is resource exhaustion. When multiple agents run in parallel against shared infrastructure — a database connection pool, a file-handle limit, a subprocess budget — each individual agent may behave correctly while the system as a whole collapses. No single agent caused the failure; the failure is an emergent property of their concurrent arrival.
The pattern for this failure mode is a semaphore: a bounded counter that caps the number of agents that may proceed past a resource gate simultaneously. Agents that arrive when the semaphore is exhausted block and queue rather than contending for the resource. The mechanism is intentionally crude — it does not inspect agent priority, task type, or expected duration — because crudeness is a feature here. A semaphore that caps concurrent agents at N works correctly regardless of what any agent is doing or what it thinks it is doing. It cannot be overridden by a well-intentioned agent that believes its task is urgent.
The keller-platform reference implementation instantiates this pattern at the module level. _kai_semaphore is initialized as asyncio.Semaphore(settings.max_concurrent_kai_processes) at line 40 of src/server/src/agents/kai/client.py, with a default of two concurrent processes and a comment that makes the reason explicit: "Cap concurrent Kai processes to avoid OOM from multiple Claude Code CLI subprocesses. Additional requests queue until a slot opens." 1 The semaphore is not a property of the ClaudeKellerClient instance — it is a module-level primitive. An agent cannot exempt itself from the cap by constructing a new client with different parameters; the constraint is outside the agent's scope.
The test suite makes the acquire-and-release contract explicit. TestSemaphoreConcurrencyLimiting verifies that aenter decrements the semaphore value by one, that aexit restores it — including when disconnect raises — and that when twice the configured limit of clients attempt to enter simultaneously, the maximum concurrency observed is exactly the configured limit. 3 The release-on-exception behavior is the load-bearing test: a semaphore that leaks slots when an agent crashes converts a transient failure into a permanent reduction in available concurrency, draining the system toward a halt.
The sizing rule is not obvious. The whitepaper build pipeline caps parallel agents at five — "Agent concurrency cap = 5. Wave dispatches accordingly" — a number sized to avoid exhausting the available Claude Code subprocess budget and the underlying model API rate limits. 4 The keller-platform CLAUDE.md states a complementary rule for the database layer: the connection pool minimum must be at least the concurrency limit plus five, because advisory-lock acquisition inside a pooled session can queue inside the pool and deadlock if no additional connections are available to drain the queue. 8 The semaphore cap and the pool size are not independent choices; they must be co-designed against the same resource constraints.
A concurrency cap that lives inside the agent can be overridden by the agent. A cap that lives at the module level cannot. The constraint must be outside the scope of the thing it constrains.
Pattern Two — Orchestrator/Worker Hierarchy
The second failure mode is the single-write collision. When multiple agents can write to the same artifact — a file, a database row, a shared context — concurrent writes produce interleaved or overwritten state. Each agent may succeed from its own perspective while the shared artifact is corrupted from everyone else's.
The pattern for this failure mode is a strict role separation: the orchestrator is read-only and makes go/no-go decisions; workers execute and return findings; only the orchestrator issues write instructions, and it does so to one target at a time. The single-write boundary is not enforced by coordination between workers — it is enforced by the constraint that workers cannot write at all. A worker that cannot write cannot race with another worker. The architectural guarantee does not depend on workers cooperating; it follows from their capability boundary.
The whitepaper pipeline makes this separation explicit and load-bearing. "Main chat orchestrates only — ThoughtBox Hub posts + Agent Mail; no analysis/edits." 4 Explore agents, semantic-search agents, and strategic-reasoner agents return findings in their result message; they do not write files. "Read-only agents cannot Write — Explore, morphllm-sdk:morph-searcher-agent, thoughtbox:strategic-reasoner-agent return findings in the result message; the orchestrator must write the artifact file." 4 The separation is not a convention — it is a tool-surface constraint. Read-only agents are dispatched without the Write tool in their capability set, so the constraint is architectural rather than behavioral.
The go/no-go decision structure follows from the same principle. The orchestrator collects findings from workers, evaluates them against the success criteria for the wave, and decides whether to proceed or stop. Workers do not evaluate each other's findings and do not vote on the proceed decision. The decision authority is singular. When workers disagree in their findings — which happens — the orchestrator reconciles; workers do not. The concurrency cap and the single-decision-authority work together: the cap bounds how many workers run simultaneously, and the decision authority bounds where their output flows.
The regulatory analogue is the three-lines-of-defense structure SR 26-2 preserves. The first line builds and runs; the second line validates independently; the third line audits the program. No line writes the other's conclusions. 9 The architectural principle is identical: decision authority is singular at each level, and the boundaries are enforced by role structure rather than by trusting each line to self-limit.
Pattern Three — Supervisor Gates and Verification Before Commit
The third failure mode is the advisory check. A check that observes but does not block can be bypassed by the thing it checks — not by deliberate circumvention, but by the ordinary mechanics of a pipeline that proceeds on success and continues past failure. If the verification step logs a result but does not block the subsequent commit, the commit happens regardless of what the verification found. The check is present; the enforcement is absent.
The pattern is a supervisor gate: a checkpoint invoked before the commit that returns a go/no-go decision and blocks the commit when the decision is no-go. The gate is not advisory; it does not log and continue. It stops. The difference between a gate and a log entry is not a matter of degree — it is the entire difference between governance and observability theater, as the companion paper on that distinction develops. 11
The whitepaper pipeline implements this pattern in its five-gate prebuild chain. The gates run in order: validate-pairing, citation-coverage, required-fields, bodymdx-clean, and rendered-output. 6 Each gate is a blocking checkpoint: a non-zero exit halts the build and the push does not happen. The bodymdx-clean gate reads the live Neon database, so banned classNames in any paper's body block all pushes — not just the paper that introduced the violation. The gate is not scoped to the changed paper; it is scoped to the system state. The rendered-output gate is OPA/Rego-backed and derives its input offline from the source markdown, so it can evaluate the rendered content without a running server. 6
The keller-platform reference implementation applies the same pattern to agent decisions. The Stop-hook validation gate described in the supervisor's-mirror companion paper is invoked before a decision is committed — “an independent checkpoint invoked before a decision is committed, the architectural analogue of effective challenge and the four-eyes requirement.” 11 A decision that fails the gate is not logged and allowed to proceed; it is refused. The gate is not a reviewer that writes a finding; it is a lock that must be turned before the door opens.
The replayable decision record follows from the gate structure. A gate that blocks before commit means every committed decision passed the gate; the commit event is itself evidence that the check ran and succeeded. An append-only store whose entries are gated commits produces a record in which every entry has an implicit assertion: it was here when the gate was satisfied. A system that logs after commit and separately checks produces a record in which entries and checks are two different streams that must be correlated by timestamp — and correlation is the thing that fails under concurrent writes and clock skew.
SR 26-2's effective-challenge requirement reaches the same conclusion from the supervisory direction. Independent validation must have sufficient authority to challenge and, if necessary, block model deployment — not merely to document disagreement. 9 A validation function that documents findings but cannot stop deployment is advisory, not independent. The architectural pattern and the regulatory requirement are the same thing in two vocabularies: the check must be able to say no, and “no” must mean the commit does not happen.
Pattern Four — File Reservation and Byte-Level Audit
The fourth failure mode is the trusted self-report. An agent reports that a file has been updated, a footer inserted, a citation added. The orchestrator records the claim and proceeds to the next step. The file has not been updated. The footer is not present. The citation was never inserted. The agent's report was accurate from its own perspective — it completed the edit step without error — but the actual file state was not what the agent described. The orchestrator's record of what happened is a record of the agent's internal state, not of the file's external state.
This failure mode occurred in production in the whitepaper pipeline. An editor agent reported a provenance footer as already present. An independent Explore audit found it absent. The gap between the self-report and the file state was not detectable from the agent's output; the agent had not flagged uncertainty or failure. 5 The downstream consequence was not caught until an independent byte-level scan of the files contradicted the accumulated self-reports.
The pattern for this failure mode has two parts. The first part is file reservation: before any agent edits a file, it acquires an advisory lease that declares its intent to the orchestrator and to other agents. Reservations prevent concurrent agents from editing the same file without awareness, which prevents the race condition in which two agents produce inconsistent edits that are both reported as successful. The reservation is advisory — it does not prevent a determined agent from writing without one — but it creates a coordination record: an agent that edits without a reservation is outside the protocol, and the orchestrator can detect the absence of a reservation as a protocol violation.
The second part is independent byte-level verification. After a claimed edit, a separate verification agent — not the editing agent — confirms that the target string is present in the target file by reading the bytes. The orchestrator does not accept the editing agent's confirmation as evidence of the edit. “Verify file state with deterministic byte scans, NOT agent self-reports.” 5 The verification uses a tool that reads the file directly — the Grep tool for pattern matching, a node -e byte sweep for exact counts — because the verification must be independent of the editing agent's perspective. An Explore agent that confirmed the edit using the editing agent's output as input would not be an independent check; it would be a relabeled copy of the same report.
The Saltzer-Schroeder fail-safe-defaults principle applies here in its epistemic form: when the result of a check is uncertain, the default must be the denying outcome. 10 An orchestrator that cannot confirm a claimed edit must treat the edit as not done, not as probably done. The asymmetry is deliberate. A false negative — an edit that was done but not confirmed — results in a redundant re-edit, which is cheap. A false positive — an edit that was not done but was treated as confirmed — results in a published paper without its provenance footer, a pushed commit without its citation, or a deployed model without its validation record. The costs are not symmetric, and the verification protocol should not treat them as if they were.
A file's state is what the bytes say, not what the agent that edited it reported. Independent verification is not a trust issue — it is a data issue.
Pattern Five — Worktree and Workspace Isolation
The fifth failure mode is shared-state corruption. When concurrent agents operate in the same working directory, the same git branch, or the same communication namespace, their actions are not isolated from each other. A file checkout by one agent overwrites unstaged changes from another. A commit by one agent picks up staged files from another. A Hub post by one agent lands in the same channel as another agent's status, making the sequence ambiguous.
The pattern for this failure mode is workspace isolation: each agent operates in a dedicated workspace — a git worktree for file system isolation, a named Hub workspace for communication isolation, a per-project Agent Mail namespace for messaging isolation. The isolation boundary is not behavioral; it is structural. An agent in its own worktree cannot corrupt another agent's unstaged changes because the two worktrees share the object store but not the working tree. A commit in one worktree does not touch another worktree's index.
The whitepaper pipeline implements worktree isolation at the paper level: "Paper-pipeline worktrees: git worktree add ../worktrees/<paper-slug> HEAD." 7 A wave of four parallel paper-drafting agents runs in four separate worktrees. Each agent sees its own working tree and its own index. Merge conflicts between papers are resolved at the integration step, not at the write step — because the writes were isolated, the only surface for conflict is the object store, and object-store conflicts are git's ordinary domain.
Communication isolation follows the same logic. The Hub+Mail orchestration protocol requires the orchestrator to register a workspace, create a problem channel, and join the workspace before posting — in that order: “register → create_workspace → create_problem (yields channel) → join_workspace → post_message.” 7 Each wave of agents operates in its own named workspace. A post_message call that lacks both a workspaceId and a problemId will route to the wrong channel or fail; the two identifiers together form the isolation key. 7 Agent identity follows the same protocol: names must be random ColorNoun pairs — “Agent Mail names: random ColorNoun only (e.g., JadeRiver, PeachHorizon); descriptive names like CrimsonLedger are rejected.” 7 The naming convention is an isolation mechanism: role-descriptive names invite the assumption that a name encodes current state; random names do not, so they do not mislead an agent reading the roster about which agent is doing what.
The interaction between worktree isolation and the zero-dirty- state rule is worth stating explicitly. An agent operating in a shared working directory that encounters modified files belonging to another agent is in the position of the multi-agent git standard: those files are peer-agent work, not uncleaned local changes, and the correct response is to leave them alone and scope commits to the agent's own files. Worktree isolation removes that ambiguity entirely. If a file is in the agent's working tree, it is the agent's to commit or not. The decision surface is clear, not contingent on knowing which of several agents last touched the file.
A Worked Example — Stop-Loop Protection
The five patterns are not independent abstractions — they compose. The stop-loop protection in the keller-platform reference implementation is a compact exhibit of three of them operating together: a bounded counter (Pattern One), a supervisor gate (Pattern Three), and a constraint that is outside the agent's scope (Pattern One again, applied to behavioral rather than resource limits).
The mechanism: ClaudeKellerClient injects a Stop hook into its options that tracks how many times Claude attempts to stop. DEFAULT_MAX_STOPS is set to three at line 35 of src/server/src/agents/kai/client.py. 1 If stop_hook_active is true — meaning a stop is already in progress — and the stop count exceeds the maximum, the client calls interrupt() to force exit. The test suite verifies the threshold exactly: the first three stop events with stop_hook_active=True do not trigger interrupt; the fourth does. 2
The stop-loop threshold is a supervisor gate applied to agent behavior rather than to a file state. The agent does not decide how many times it may attempt to stop; the client infrastructure decides. The decision is not accessible to the agent — it is in the hook that wraps the agent's stop events, not in the agent's own reasoning. An agent that believes it has good reason to stop a fifth time is overridden by a constraint it cannot see. The gate is not advisory; it calls interrupt() and the response is drained, after which receive_response() raises a RuntimeError. 1
The release-on-exception behavior of the semaphore is the third layer. If the forced exit raises during disconnect, aexit still releases the semaphore. 3 A crash does not consume a concurrency slot indefinitely. The concurrency invariant is maintained across the failure path, not just the success path. That invariant is what makes the semaphore a genuine cap rather than a cap that degrades under failure until it no longer constrains.
The composed picture is: a resource gate (semaphore) bounds how many agents may run; a behavioral gate (stop-hook threshold) bounds how many times a running agent may attempt a specific action; and a release invariant ensures the resource gate is not leaked by failure. No single mechanism is sufficient alone. The semaphore without the stop-loop protection admits runaway agents within the concurrency cap. The stop-loop protection without the semaphore does not prevent resource exhaustion. The release invariant without either produces a well-behaved system that slowly poisons its own concurrency pool under repeated failures. The patterns compose because the failure modes they address are not the same failure mode.
Failure Modes and When the Patterns Break
The five patterns have failure modes of their own. Naming them is part of the discipline — the honest-limits requirement applies to an architecture paper as much as it applies to a model-risk inventory.
Ignoring caps produces cascading exhaustion. A semaphore that is correctly implemented but incorrectly sized does not prevent exhaustion — it delays it. A cap of five concurrent agents against a database pool of six connections means one connection is always available for non-agent work, but a pool of three means two agents will deadlock waiting for connections the other three hold. The sizing rule — pool minimum at least concurrency plus five — is a heuristic derived from the advisory-lock behavior of a specific database driver. 8 A different driver, a different lock pattern, or a different query shape will produce a different sizing requirement. The pattern is correct; the size must be derived from the actual resource constraint.
Trusting self-reports produces the supervisor's mirror. The companion paper on the unsupervised allocator exhibits the failure mode at the system level: a multi-stage automated pipeline that can fail open without the failure being observable, because the decision it emits is structurally indistinguishable from a decision produced by a correctly completed pipeline. 12 The same failure mode applies at the agent level: an agent that reports a successful edit when the edit did not take produces a record of claimed completions that diverges from the actual file state. The byte-level verification pattern is the response, but it adds latency and requires a second agent with read access to the file. In pipelines where latency is a hard constraint, some teams elide the verification step — which converts the pattern into an advisory check and restores exactly the failure mode it was designed to prevent.
Advisory-not-gating verification is observability theater. A prebuild gate that exits zero when it cannot find the OPA binary — rather than failing the build — is an example of a gate that has been converted into a log entry by the skip-when-absent behavior. The whitepaper pipeline documents exactly this failure mode and its resolution: the rendered-output gate skips when opa is absent in sandbox environments, but the Vercel installCommand downloads it so that production deploys always run the gate. 6 The gate is present in both environments; the enforcement is present only in production. A gate that runs in production but not in development means development deploys bypass the gate, which means the gate only catches failures that survive to production — which is a subset of the failures it was designed to catch.
Direct writes without isolation produce races. Worktree isolation eliminates write races between agents by making the shared working tree a non-shared working tree. But isolation at the worktree level does not prevent races at the merge step. Two agents that produce valid, isolated edits to the same logical section of the same file in separate worktrees will produce a merge conflict when those worktrees are integrated. The pattern pushes the race from the write step to the merge step, where it is visible and explicit rather than silent and corrupting. That is a real improvement — silent corruption is worse than explicit conflict — but it is not an elimination of the problem. The integration step requires a human or a merge-resolution agent, and that agent can also fail.
The regulatory turn. SR 26-2 requires that model risk management include effective challenge — an independent function with sufficient stature to question and, if necessary, block deployment. 9 The five patterns in this paper are the agent-system analogue of that requirement: semaphores for resource challenge, orchestrator/worker hierarchy for decision-authority challenge, supervisor gates for pre-commit challenge, byte-level verification for self-report challenge, and workspace isolation for state-corruption challenge. Banking already requires these patterns for credit decisions. Agent-native systems operating outside regulated sectors preview the gap — they ship without the challenge layer — and the unsupervised allocator paper documents what that gap looks like in a live system. 12 The distance between a system that has these patterns and one that does not is not a matter of sophistication. It is a matter of whether the architecture assumes agents will fail.
A multi-agent system that assumes its agents are reliable does not need these patterns. A multi-agent system that will eventually run in production does.
End of paper
↑ Back to top