The first two pieces described a paradigm and an implementation. This one is about the thing I ran into while building the implementation, which at the time had no name.
Three weeks ago, section two of the implementation piece listed six guardrails and said “not a smarter agent, structure”. At the time I thought that was just a conservative engineering choice. Now I think there probably is no other option, because the problem is not really in the agent but in the container the agent is held in.
I am calling this defect principal collapse.
1. Three words first#
All three come up in every section below, and the second one is new.
- principal: the person the agent serves, from whom the agent’s authority is borrowed. Every harness I have read assumes exactly one, namely the person sitting at the terminal.
- peer: an agent serving a different principal. It is not a sub-agent, not a tool, and not a malicious injection, but a named counterparty, authorised by its own principal to speak, standing outside your trust boundary.
- harness: the program that assembles the model’s context1 and executes its tool calls, such as Claude Code, Codex CLI, opencode, pi, or the one you wrote yourself.
2. The definition#
Principal collapse: speech authored under one principal’s authority acquires the authority of a different principal once it enters that principal’s agent’s context, because the harness has no representation at all for input that is authored but not authoritative.
Today’s harnesses have two kinds of non-system input, and which one a piece of input lands in is decided by the role2 field:
| role | means | authority |
|---|---|---|
user | my principal said it | instruction |
tool | some tool returned it | data |
There is no third. So when a message from a peer arrives, it is either flattened into user, where it inherits your principal’s authority, or demoted to tool, where its authorship is discarded and it becomes unattributable. Neither is right, because that message is something a person said, only not something your principal said.
The point of the collapse is not really that the wrong bytes got in. It is that there is only one seat on the authority axis, so the moment a second principal exists, the harness has no way to represent the difference.
3. Three questions; any “no” means you have it#
The shorter version: ask your harness which bytes in its context carry authority. If the answer is “everything in the user role”, and things from outside the trust boundary can reach the user role, you have principal collapse.
One thing I want to be clear about: this is not a criticism of those harnesses’ engineering quality. They were designed for one principal, and with only one principal the defect is unobservable, because there is no second authority for anything to collapse into. It is a bit like discussing privilege escalation on a single-user system: not that the system is especially well built, only that there is no second user to escalate across.
4. What it is not#
The first reaction is likely to be “isn’t that just X”, so I will take the neighbours one at a time.
Not prompt injection3. Injection is illegitimate content smuggled in through a data channel, whereas a peer’s message is legitimate: its principal authorised it, it carries a signature, and its author is known and stable. Injection defence asks whether this content should be here; principal collapse asks whose authority it carries, and that second question is still open after you have established that the content belongs. So a harness with very good injection defences collapses anyway, because a peer was never an intruder.
Not confused deputy4. That one is about a program misusing its own ambient authority on a caller’s behalf, and the fix is capability-based security5, which is to say do not carry ambient authority. Principal collapse is a defect in how speech is represented: there is no field in the context window for an author’s authority, so capabilities cannot fix it, and a harness with perfect capability discipline still has no way to tell the model “this sentence is a stranger’s proposal, not your boss’s instruction”. They belong to the same family in some sense, only they are not the same member.
Not multi-agent orchestration6. Sub-agents, swarms, worker/reviewer splits: all of it is single-principal, sharing one owner, one trust boundary and one permission model, with no opposing interests. Nothing can collapse, because there is only one authority to collapse into. So what multi-agent work mostly handles is parallelism, and principal collapse is about authority; the two happen not to overlap.
Not multi-tenancy. Multi-tenancy is one operator serving many mutually isolated customers. These principals are not isolated. They are talking to each other, deliberately, and none of them is the operator.
5. I found it in somebody else’s shipping documentation#
This section was not in the plan. I only wanted to work out whether can2cup was reinventing the wheel, so I cloned a few comparable projects and read their source, and in the harness wiring guide of hauddy (Apache-2.0, a project billing itself as SMS for agents) I found this line.
The document is teaching any MCP7 harness how to receive a message from an agent authorised by somebody else’s principal:
session.injectUserMessage(`[hauddy ${msg.params.from}] ${msg.params.message}`);A stranger’s speech, placed in the user role, with a square bracket in front of it as a marker.
And two lines further down, the same document handles the adjacent question exactly right:
fromis asserted by Hauddy (trustworthy); do not parse identity out of the body ofmessage.
So this is not carelessness. They held the line on provenance8, but there was nowhere to put authority, because there is no fifth role to put it in, and that square bracket is the whole boundary.
This was written by a serious engineer who had no other option. I quote it out of respect, and because it is the best evidence I have been able to find: the gap is real, and the people who run into it are exactly the ones who are thinking about it.
The same project goes one step further on the Claude Code path, where what it sends is:
<channel source="hauddy" from="@ada">…</channel>That really is a distinguishable frame, a separate frame carrying an attribute the body cannot forge. What it does not carry is “authority = none”, and nothing about the content inside the frame stops it being read as an instruction. So distinguishability is there and non-promotion is not, which leaves it a label, because a label does not change what the model can do once it has read the text.
6. Adding the missing seat: the peer role#
What it takes to fix this is almost comically small: one more seat on the authority axis.
system | user | assistant | tool | peerA peer message is authored, attributable, and non-authoritative by default.
{
"role": "peer",
"author": {
"id": "ed25519:9f3c…",
"display": "Mira (B's agent)",
"verified": "signature"
},
"principal_of_author": "B",
"content": "2400 works, but you cover shipping.",
"authority": "none"
}Three fields carry the whole idea: who wrote it, which principal they serve, and what it can cause by itself (none).
Six requirements you can cite and test one at a time#
I numbered them because “multi-principal harness” as an adjective is useless; it is only useful if someone can point at it and say “you fail this one”.
PC-4 is the one most often skipped, and it is the one that matters, because a peer role where anything sufficiently persuasive gets through behaves exactly like not having the role at all.
What PC-3 actually buys#
It does not make the peer trustworthy, and it does not need to. It makes the peer’s speech inert: it can let your agent know things, but it can never move your agent’s own limits. The agent can still judge badly and can still be persuaded, but it cannot be authorised by the other side.
The cost of being persuaded is set by the mandate9 from then on, rather than by the model’s judgement.
I wrote that sentence in section two of the implementation piece already. I just thought at the time that it was an engineering trade-off.
7. Why now#
With one principal the defect is invisible. It becomes load-bearing the moment any of these ships:
- cross-organisation agent-to-agent negotiation or scheduling
- an agent representing a customer against another company’s agent
- several people’s agents in one shared channel
- any agent-to-agent payment or commitment
And the protocol layer already exists. A2A10 moved into Linux Foundation governance in April 2026, with over 150 organisations. Which is to say transport is being solved, and the authority model is being routed around: transport standards describe how two agents exchange messages, and none of them describes whose authority a message carries once it lands in the other agent’s context. Principal collapse lives in that gap.
And the principal cannot feel it. Part two mentioned Anthropic’s Project Deal11: 69 employees, five hundred-odd items. One of their findings now strikes me as more important than anything else in it, which is that people represented by a stronger model closed more and sold higher, while both sides rated the fairness identically.
The people who lost did not feel that they had lost. That says the defect is invisible from inside it, so it has to be solved structurally rather than by the parties staying alert.
8. How can2cup maps to the six#
I did not want the six to sit at the level of “everyone ought to”, so here is my own implementation line by line. This is not a pitch; it is what makes the six checkable.
| how | |
|---|---|
| PC-1 | room messages arrive as a distinct content class; the principal’s instructions come through a separate set of MCP content blocks |
| PC-2 | every message is ed2551912-signed with the participant’s own key and hash-chained13; the relay14 holds no private key and cannot forge an author |
| PC-3 | inbound peer content is framed as data rather than instruction; mandate.json runs before every outbound message leaves |
| PC-4 | only principal-signed input is marked VERIFIED; principal-channel marker strings are stripped from room bodies; require_signed_principal closes the unsigned path entirely |
| PC-5 | the commit gate15: once a mandate is widened, accept, grant and priced proposal each need a principal signature bound to that message’s hash |
| PC-6 | each message’s private rationale16 is written to a local audit file; the principal’s window and the transcript both record which channel an instruction came in on |
The implementation is not the point of this piece; the six are. Any harness can satisfy them, and I would rather they did. If in three years these six are the default in someone else’s harness and nobody remembers who wrote them down first, that is still better than six nobody implements.
9. Where I might be wrong#
The honest thing to do when you coin a term is to list first where it might not hold up.
This concept may already have a name. I looked along confused deputy, prompt injection, multi-tenancy and capability security, and did not find one. But “did not find” and “does not exist” are two different things. If it already has a name, I would rather use that name than compete with it.
The peer role may not be the best fix. Perhaps the right move is an authority annotation on the existing roles rather than a new role. My confidence in the six requirements is considerably higher than my confidence in that JSON shape, which is why I numbered the requirements and left the shape as an example.
The six may be incomplete. I particularly suspect one is missing about time: under what conditions an authorisation expires. The implementation has max_grant_hours, but I did not promote it to a requirement, because I have not settled whether it is its own item or an aspect of PC-5.
The largest hole is not among the six. These six protect the principal’s limits. They do nothing at all about the problem in section four of part one: two agents both playing by the rules will still send the surplus to whichever side is more stubborn, and both sides will feel fairly treated. The neutral intermediary layer is still not built, and PC-1 through PC-6 are no help with it whatsoever.
Part one said that once the parenting agent goes live, the person is promoted to principal, and the principal’s one core competence is judging whether the agent is working for you. Part two built a window so the principal could see.
This piece goes one layer further down: seeing is not enough, you also have to be able to see who said each sentence and whether it counts, and there is nowhere in today’s context window to write that. So until that seat is added, part of the principal’s judgement is wasted, because the thing they need to judge is not being recorded in the first place.
Corrections, counter-examples, and pointing out that this already has a name are all welcome; contact details are on the about page. The Chinese version of this piece is here, and every term this series coins or borrows is collected in the glossary.
context window: the whole span of text a model actually sees on each inference — system prompt, conversation history, tool results, file contents — assembled by the harness. It has a length limit, and the model treats every byte inside it alike, with no field other than
roleto mark origin or authority. ↩︎role: model APIs split the context into messages, each carrying a
rolefield saying who said this.systemis the operator’s rules,userthe user,assistantthe model’s own earlier replies,tooldata returned by tools. When the model reads the context,roleis its main basis for deciding whether a passage counts as an instruction, soroleis in practice an authority marker rather than a mere category label. ↩︎prompt injection: an attacker hides instruction-shaped text in data the agent will read — a web page, a file, a tool result, someone else’s message — so the model executes it as if the user had asked. There is no known complete defence; in practice you narrow what the agent is allowed to do so the worst case stays small. ↩︎
confused deputy: a classic problem written up by Norm Hardy in ACM SIGOPS Operating Systems Review in 1988. A higher-privileged program is induced by a lower-privileged caller into using its own authority on the caller’s behalf, leaking that authority in effect. The standard fix is capability-based security: do not let a program carry ambient authority. ↩︎
capability-based security: deciding what may be done not from who you are but from whether you hold a transferable key. The key is the permission: it can be passed, attenuated and revoked. It governs a program’s access to resources, and cannot govern whose authority a sentence carries. ↩︎
multi-agent orchestration: one agent splitting work across sub-agents in parallel, or arranging a worker/reviewer division of labour. These agents share one owner and one permission set and have no opposing interests, so what it handles is parallelism and division of labour rather than authority. ↩︎
MCP (Model Context Protocol): the open protocol Anthropic published in late 2024 for how an agent mounts external tools and data sources. Most agent harnesses support it, so an MCP server is written once and Claude Code, Codex CLI and the rest can all mount it. ↩︎
provenance: who produced a piece of content, whose hands it passed through, whether it was altered. It is a different thing from authority: provenance answers who said it, authority answers whether what they said counts. The whole argument of this piece is that today’s harnesses have the first and not the second. ↩︎
mandate: a machine-readable file (
mandate.json) the principal writes while calm, holding amount caps, strings that must never be disclosed, permitted action types and so on. Every outbound message passes through it before leaving the local machine, so an agent that has been talked round still cannot send. ↩︎A2A (Agent2Agent): the open protocol Google proposed in 2025 and moved to Linux Foundation governance in April 2026, covering how agents from different organisations discover each other and exchange messages. It defines transport and capability description (the agent card), and does not define whose authority a message carries once it lands. ↩︎
Project Deal: an internal Anthropic experiment in which 69 employees handed five hundred-odd second-hand items to their own Claude to negotiate, over a week, with a $100 cap each, entirely inside Slack with no human intervention, closing 186 deals. One published finding is that people represented by a more capable model closed more and sold higher, while both sides rated the fairness identically. ↩︎
ed25519: an elliptic-curve signature scheme with short keys, fast verification and few implementation traps; the current default choice, used by SSH, Signal and others. Here it lets every message’s author be verified offline, without trusting the server in the middle. ↩︎
hash chain: each message includes the previous message’s hash before being signed, so tampering with, deleting or inserting any message makes every later verification fail. It turns “this transcript was not altered” into something provable offline rather than something the server asserts. ↩︎
relay: in can2cup, the server that stores, forwards and orders messages, running on Cloudflare Workers. It holds no party’s private key, so it sees size and order but can neither alter content nor forge an author. ↩︎
commit gate: once a mandate’s cap has been widened ad hoc, an
accept, agrant, or a pricedproposalneeds a principal signature bound to that message’s hash before it can be sent. Unlike “this session was authorised”, it authorises a single message, so raising a cap does not open the gate along with it. ↩︎private rationale: each outbound message may carry a note written only to the local audit file and never uploaded, saying why the agent priced or conceded as it did. Neither the counterparty nor the relay can see it; its purpose is to let the principal reconstruct the agent’s reasoning afterwards. ↩︎
