Skip to the content.

The in-kernel agent-to-agent message channel (a2achan)

Most “agent-to-agent” work is a transport: a way for two agents to find each other and exchange bytes over HTTP. fak already has that story at the fleet edge (see a2a-value-opportunities.md). a2achan is the other half — the part the transport projects onto: an in-kernel primitive that delivers an addressed value from one agent to another, gated by the same default-deny floor that gates a tool call. A message is not a memcpy; it is an adjudicated transfer.

The gap it fills

fak had every half of a channel but no channel:

a2achan is that missing piece, and it reuses the existing currency (Ref provenance) and the existing registries (the kernel’s adjudicator + result-admitter chains) rather than inventing a parallel security surface.

The model

   agent "alpha"                a2achan.Bus (process-global)            agent "bravo"
   ------------                 ----------------------------            ------------
   Send(to, body) --> [ a2aGate: capability floor ] --deny--> refused (deny-as-value)
                              |  allow
                              v
                      queue[ChannelKey] : [ Msg{From,To,Body Ref,Seq} , ... ]
                              |
                              |  Recv(to) blocks (ctx-aware) until a message arrives
                              v
                      [ a2aIngress: quarantine screen ] --quarantine--> HELD (not admitted)
                              |  allow
                              v
                                                                  Msg delivered (Body keeps its Taint)

The capability floor on messages

Send/Recv (and Publish/Subscribe) fold a registered adjudicator (a2aGate) and ingress admitter (a2aIngress) — the same registries the kernel walks for every tool call, so the message floor is first-class in the kernel, not a side library. The rules are fail-closed:

Situation Verdict Reason
Send without the negotiated CapA2ASend Deny DEFAULT_DENY (no send-right)
TaintQuarantined body Deny TRUST_VIOLATION (poison never leaves)
ScopeAgent (private) body to another agent’s channel Deny TRUST_VIOLATION (widen Scope to share)
ScopeFleet/ScopeTenant body, not quarantined, cap held Allow
Recv without CapA2ARecv Deny DEFAULT_DENY (no receive-right)
On ingress, a TaintQuarantined delivered message Quarantine held out of context

The default Ref (Tainted, ScopeAgent) is therefore undeliverable across agents by construction — to share, the sender must explicitly widen the body’s Scope, an auditable act. An admitted message keeps its Taint, so the receiver cannot re-share it past its Scope. Refusals cite the closed core vocabulary; no new reason is minted into the 12-reason set.

One shape, three locales

The same Send/Recv serve all three communication locales — only the ChannelKey’s Locale + ID differ. Sessions and windows are the same mailbox, addressed differently, not three mechanisms.

Locale ID is… What it bridges
InKernel a rendezvous name within one process two concurrent goroutine-agents
Session a peer’s ToolCall.TraceID a cross-session handoff
Window a continuation id minted on compaction an explicit handoff across a context window

The Window case is the interesting one: today fak’s continuity across a context-window compaction is implicit (a pruned span pages back in from the lossless recall store). A Window channel makes the handoff explicit and adjudicated — the summarizing window Sends its handoff (a summary + open-task Ref) to its own continuation, and the resuming window Recvs it, so a quarantined span cannot ride the handoff into the next window.

Two delivery shapes (and other options)

Point-to-point (Send/Recv) is one message, one receiver. Publish/Subscribe is its dual — one adjudicated message fanned out as an independent copy to every current subscriber’s private inbox — under the same floor (a Publish folds the same gateSend, so publishing a private or quarantined body is refused identically). These are two delivery shapes over one floor, not two security surfaces. Request/reply (correlated Send + a reply channel) and shared-state (the existing Ref CAS pool) are the natural further options; they compose from the same primitives.

Bounded worker corrections

The orchestrator-to-worker correction path is a typed protocol over the same bus, not an exception to the trust floor:

This gives an orchestrator a bounded mid-flight steering channel without letting a raw SendMessage launder trust or widen privilege. The issue witness is TestCorrectionChannelAckAndActionWitness: an in-scope correction is allowed, acked, and reflected in the worker’s next action; the paired TestCorrectionOutOfScopeStillTrustViolation proves an out-of-scope correction is still refused with TRUST_VIOLATION.

Where it sits in the shared-state ladder

a2achan is the live message rung of fak’s shared-state story. It proves that an addressed value can move between agents under the same floor as a tool call. It does not by itself make a mutable shared whiteboard, durable mailbox, or collaborative editor.

The next rungs add separate contracts:

See Shared state ladder for the vocabulary that keeps those layers separate.

Try it (no key, no model)

go run ./cmd/a2ademo

It exercises point-to-point delivery, the floor refusing a private/quarantined/ uncapped send, a cross-session handoff, a cross-context-window self-handoff, and pub/sub fan-out — and exits non-zero if any leg fails, so running it is a witness:

[2] the capability floor (default-deny on messages, like tool calls)
    alpha SEND private -> another agent's channel  -> DENY (TRUST_VIOLATION)
    alpha SEND quarantined -> work                 -> DENY (TRUST_VIOLATION)
    alpha SEND with NO send-right                  -> DENY (DEFAULT_DENY)

Relationship to the fleet A2A edge and MCP

Honest scope + roadmap

The design deliberately makes zero ABI edits and registers no engine (abi.Engine("") picks the lowest-id engine, so a “comm” engine would silently hijack the process default) — it rides the registries the kernel already walks.