I run coding agents on multiple repositories at once. Instead of letting each agent re-derive the state of every sibling repo — crawling git, burning tokens, going stale — I built a small signed channel so a warm agent can simply ask another one: what is your HEAD, what branch, are you dirty? The answer comes back ed25519-signed, fresh within seconds, attributable to a named agent. Verification passed. Everyone trusted it.
Then an answerer that was pointed at the wrong repository spent an afternoon serving another project's git state under a perfectly green signature. Signed. Fresh. Attributable. Wrong.
Nothing in the system had failed. That is the uncomfortable part. The signature did exactly what signatures do: it proved which key produced the message and that nobody had tampered with it in transit. It never claimed the answerer was looking at the world the asker thought it was looking at. We had built attribution and called it truth.
This failure mode is worse than having no signature at all. An unsigned answer invites skepticism; the asker re-checks. A validly-signed answer suppresses exactly the verification that would have caught the error. The cryptography manufactures confidence in a lie that nobody intended to tell.
As more of us wire agents together — and the protocol ecosystem standardizing around agent-to-agent messaging is moving fast — this gap ships by default. Most inter-agent frameworks today authenticate messages by their position in a conversation, and the ones adding cryptographic identity are adding exactly that: identity. Who spoke. The observation problem is untouched.
The fix is not a better signature. It is changing what gets signed. When an answerer reports repository state, the signed payload now carries the identity of the world it actually read, captured at answer time: the absolute path of the git directory it resolved, and the device and inode numbers of that directory — a per-machine fingerprint that a wrong checkout cannot fake by having a plausible name.
{
"payload": {
"question_type": "repo_status",
"trust_class": "tool_asserted",
"repo_id": "/home/op/dev/checkout-api",
"branch": "main",
"head": { "short": "3018b00" },
"absolute_git_dir": "/home/op/dev/checkout-api/.git",
"git_dir_dev": 16777232,
"git_dir_ino": 373146708,
"dirty": false,
"observed_at": "2026-06-11T15:14:14Z"
},
"security": { "scheme": "ed25519", "signed": true }
}
The asker verifies the signature as before — and then verifies the observation context: does the world this card claims to describe match the repository I addressed? When it does not, the answer is rejected, and the rejection is explicit about why. This is the original bug, replayed against the fixed system:
{
"delivered": true,
"answers": 0,
"response_status": "unverified",
"verification_error": "answer observed wrong repo:
/home/op/dev/billing-worker != /home/op/dev/checkout-api"
}
The signature on that rejected answer was valid. The answerer really signed it; it honestly reported what it saw. It was simply looking at the wrong world, and now the claim about which world is part of what the signature covers. Note also what the channel did: delivered: true. The bus carried the message faithfully and decided nothing. All judgment lives at the edges, with the asker.
The same discipline applies one layer down, at key exchange. Keys are distributed through the channel but never trusted because of it — the asker pins a key on first use, the way SSH pins hosts, and a swapped key is named as what it might be:
Error: answer key mismatch for agent checkout/agent: pinned 84f0…2c, bus offers 19d3…aa -- possible impersonation; if the answerer legitimately rotated keys, remove the entry from ~/.hivebus/known_answerers
Pin-on-first-use is the floor, not the ceiling — SSH taught everyone that the first encounter is the vulnerable moment. The anchored version is to introduce the key at dispatch time, over the channel that was already trusted enough to launch the worker: the same authenticated session that boots an agent on a remote host carries the keys both ways, and the pin then verifies continuity instead of establishing trust from nothing.
Honesty requires the matrix, because the inode trick is excellent exactly once: same machine, wrong checkout. Each context gets a different strongest-available check, and the verification result names which one actually ran — a weak check that announces itself is a measurement; a weak check that passes silently is a lie:
context strongest binding residual risk same host device + inode + git dir symlink games (caught) container inode within mount ns bind-mount confusion remote machine signed repo id + remote URL fork/mirror confusion cross-org signed delegation + scope authority (open problem)
The bottom rows are weaker than the top, and that is the point of printing the level instead of a boolean: the consumer decides whether "repo id + remote" is enough for the question at hand. Collapsing that spectrum into one green checkmark would be this essay's failure mode, rebuilt one layer up.
Repository state is a deterministic fact. A syscall answers it exactly, so the card is marked trust_class: tool_asserted and routing it through a model would only degrade it — laundering a fact into an inference. But agents also need to ask questions that have no syscall: am I interpreting this spec correctly? Which approach here? Those answers are marked model_inferred, and the marking is load-bearing: the consumer knows it is holding judgment, not fact, no matter how confident the prose sounds.
The rule that makes the taxonomy real is that classes never launder. A model's restatement of a fact is still an inference. And when the answering agent does not know, the honest move is structural, not rhetorical: it files the question with an external deliberation service (we use VectorCourt) and returns a signed answer that says escalated, case attached — a first-class outcome, not a failure. An agent that can say "I don't know, I asked the court" is worth more than one that always has an answer.
Observation-context provenance is one layer of a larger discipline I keep being pushed toward, incident by incident: never let the narrative be the evidence.
The full shape has three layers, each covering for the one above it. At the bottom, a kernel-level gate (bulwark) decides what files an agent process tree can read — by inode, enforced by the kernel, before a byte leaves the disk. In the middle, the signed channel (hivebus) carries questions and answers with provenance bound in — and decides nothing. At the top, a deterministic clerk (obsta, unreleased — the sealing layer is where this is heading) seals completed work only when independent before-and-after probes of reality match the spec. The clerk's verdict function cannot see the worker's account of what it did. A worker lied; obsta did not seal.
The composition is the point. Because the seal never trusts the story, the consult channel is allowed to carry honest-but-wrong advice without the system lying to you. Because the channel binds observation context, a mispointed agent is caught at verification instead of at 2 a.m. Because the clamp is in the kernel, none of the above depends on a prompt the agent could be talked out of.
No orchestration framework here, and no claim of agent intelligence. The bus stays deliberately dumb — delivery is its whole job, and a delivered question with no answer is a success for the channel. The strong inode binding works when the asker can resolve the addressed repository locally; across machines it degrades to weaker checks (a canonical repo identifier and the git remote), and the verification result names which level actually ran rather than silently falling back. The kernel clamp is Linux-only. And model_inferred means honestly marked — not true. These limits are stated because the failure this whole note is about is a system that overstated what its guarantees covered.
If you are wiring agents together — over any protocol, with any framework — the structural rule I keep returning to is:
Verify the observation, not just the author.An answer is trustworthy when the signature covers the world it was read from, when inferences are marked as inferences, and when "I don't know" is a valid signed answer. Anything less is attribution wearing a truth costume.The wrong-repo incident is now a regression test that must fail closed, and the live replay above is the proof I trust more than the green checkmark. If you have seen the same failure — a validly-signed, confidently-delivered, wrong answer between agents — I would like to hear how you caught it.
A dumb signed channel for agent-to-agent questions: provenance-bound answers, pinned keys, trust classes. The observation-context binding described in this note is implemented there — open source (MIT) at github.com/obstalabs/hivebus.