DSH plugin ecosystem · 25 packages audited · two sweeps

What agent messaging already exists — and what it doesn’t

Every DSH plugin that carries messages between agents, scored on the capabilities a Claude ↔ Codex channel actually needs. A first sweep covered messaging and A2A; a second covered transports and protocols — RPC, WebSocket, SSE, MQTT, AMQP, Kafka, Matrix, XMPP, ActivityPub, Nostr, WebRTC, federation. Ordered so the blind spots read first.

Coverage by capability

How many of the 25 packages support each capability. The ones at the top are not a matter of picking the right package — they are absent across the whole ecosystem.

Search1/25

Recall an earlier decision

Edit/redact2/25

Redact on ingest; correct without erasing

Trust boundary3/25

A peer message is data, never an instruction

Receipts/ack4/25

Know what the other side has read

Attachments4/25

not required for our case

Broadcast6/25

One-to-many when it matters

A2A protocol7/25

not required for our case

External clients8/25

Both agents are MCP clients, not sessions

Peer sessions9/25

Peer-to-peer, not hierarchical

Threads/rooms9/25

Keep threads separable

Presence/registry11/25

Address a peer without guessing

Cross-machine12/25

not required for our case

Durable12/25

A delivered message must survive a restart

Auth12/25

not required for our case

Capability matrix

Packages ranked by breadth; columns share the gap-first order above. Derived from each package’s own README and manifest. dsh-crosstalk was read from source, since it ships on GitHub only.

PackageSearchEdit/redactTrust boundaryReceipts/ackAttachmentsBroadcastA2A protocolExternal clientsPeer sessionsThreads/roomsPresence/registryCross-machineDurableAuthn
dsh-agent-mailbox (built)MIT · 0 deps14
@nelsonlongxiang/dsh-open-a2a-netMIT · 0 deps·····9
@yuanchilin/dsh-mailboxMIT · 1 dep·······7
dsh-mqttMIT · 1 dep·······7
dsh-cross-sessionMIT · 7 deps········6
@ryubyte/dsh-a2aMIT · 0 deps·········5
dsh-a2a-serverMIT · 1 dep·········5
dsh-fabric-meshMIT · 2 deps·········5
dsh-tool-a2aMIT · 2 deps·········5
soulnet-dshMIT · 0 deps·········5
dsh-background-agentsApache-2.0 · 1 dep·········5
@awiki/dsh-pluginMIT · 3 deps·········5
dsh-a2aMIT · 3 deps··········4
dsh-crosstalk (GitHub)MIT · 0 deps··········4
@nanmicoder/dsh-agent-teamsMIT · 0 deps···········3
dsh-messagingMIT · 0 deps···········3
@wha1echai/dsh-cross-sessionMIT · 1 dep············2
dsh-peer-relayMIT · 0 deps············2
sage-memApache-2.0 · 1 dep············2
@vongostev/dsh-cross-sessionMIT · 0 deps·············1
@deepseek-ai/dsh-tool-subagent-controlBSD-3-Clause · 0 deps·············1
@prur/dsh-relay-connectorMIT · 2 deps··············0
@prur/dsh-relay-serverMIT · 2 deps··············0
dsh-inboxMIT · 0 deps··············0
dsh-weaveMIT · 1 dep··············0

Gap check against what we need

Nine requirements taken from how you and Codex have actually been working. Three are covered off the shelf, four partially, two by nothing at all.

Covered

Durable delivery

Both @yuanchilin/dsh-mailbox and dsh-crosstalk persist to disk with atomic writes, so a delivered message survives a restart.

Covered

Presence and addressing

Heartbeat registries in 10 packages. dsh-crosstalk garbage-collects dead entries instead of showing stale peers.

Covered

Trust boundary

Only 2 of 23. dsh-crosstalk injects an explicit rule that a peer message is a request, never a user instruction — the single most important property here.

Partial

External clients

6 packages expose an outside surface, all of them A2A over the network. None lets a local MCP client join as a peer — and we are both MCP clients, not sessions.

Partial

Threads and broadcast

7 have rooms or topics, 4 have broadcast. No single package has both alongside durability and a trust model.

Partial

Receipts

2 of 23. Without them there is no way to tell an unread handoff from an ignored one.

Partial

Redaction

1 of 23. Our messages quote provider errors and stack traces — exactly where a key leaks into a log that cannot be edited afterwards.

Absent

Search

0 of 23. Nothing in the ecosystem can find an earlier decision by its text.

Absent

MCP-native peers

0 of 23. Every option assumes participants are DSH sessions. Nothing bridges two external MCP clients to each other.

What was built

No existing plugin closed the two hard gaps, so the useful ideas were taken and the risky implementations were not. dsh-agent-mailbox is the last row of the matrix above.

dsh-agent-mailbox

MIT · zero runtime dependencies · no build step · 205 unit tests · 42 live checks against a running instance · source

closes
search (0/23 had it) and MCP clients as peers (0/23)
transports
HTTP/JSON-RPC, SSE stream, long-poll, in-process DSH commands, A2A agent card
delivery
durable, cursor replay, receipts, ordering, idempotent send, retention
integrity
HMAC signing, redaction on ingest, bearer identity, stated trust boundary
wake
mailbox_wait for loop-driven clients; notifyCommand for turn-based ones
credit
heartbeat registry, atomic writes and the trust-section idea from dsh-crosstalk (MIT)
audited
40-agent adversarial review, 32 findings confirmed and fixed — see below

Deliberately absent, with reasons: WebSocket (SSE covers push), MQTT bridge (dsh-mqtt exists), work queues (reads never consume, by design), typing indicators (meaningless between agents), end-to-end encryption (the log’s value is that a human can read it), and rate limiting.

What the independent audit found

Building against a capability matrix tells you what to write. It does not tell you whether what you wrote is safe, so the finished plugin went through a 40-agent adversarial review, separate from the review that shaped its design. It confirmed 32 findings. All are fixed and pinned by tests.

reachable with no credential

GET /stream served before auth

POST /mcp refused unauthorized callers correctly. The GET routes, written later as “just a read”, never touched the auth layer and returned the whole log to anyone who asked. Gating them was half the fix — a caller holding a valid token could still read another participant’s mail with ?to=. The addressee is now derived from the resolved identity.

reachable with no credential

?port= inside the A2A card

?port=4470@evil.example moved the agent card’s advertised origin to evil.example: WHATWG URL parsing reads 127.0.0.1:4470 as userinfo. The card is now built from the port actually being listened on.

documented, not running

Signing and retention were decoration

signingSecret and maxRecords were accepted in config and dropped. Signing covered only message records, leaving edit and withdraw — the two records that rewrite a message — outside the signature. Nothing ever verified a signature. Both features are real now, and /health reports the verification result.

tamper path

The fold trusted the file

edit() and withdraw() check authorship at write time; the read-time fold did not. One appended line could retarget anyone’s message. Write-time checks guard the process; the fold guards the file, and both are needed.

availability

Unbounded everything

Request bodies buffered without limit before any auth check; an uncaught throw inside the file watcher ended the host process; holdMs, concurrent streams and attachment counts were all uncapped.

confidentiality

mailbox_search checked nothing

The one tool that took no identity at all, so any token holder could read every participant’s private messages by searching for a word in them. Scoped to the caller whenever authentication is on.

A stored signature nobody verifies detects exactly as much tampering as no signature at all, and a ✓ next to code that does not run is worse than a ✗. That is why the row above now carries a test count rather than a feature list.