DSH plugin ecosystem · 25 packages audited · two sweeps
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.
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.
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.
| Package | Search | Edit/redact | Trust boundary | Receipts/ack | Attachments | Broadcast | A2A protocol | External clients | Peer sessions | Threads/rooms | Presence/registry | Cross-machine | Durable | Auth | n |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| dsh-agent-mailbox (built)MIT · 0 deps | ● | ● | ● | ● | ● | ● | ● | ● | ● | ● | ● | ● | ● | ● | 14 |
| @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 |
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.
Both @yuanchilin/dsh-mailbox and dsh-crosstalk persist to disk with atomic writes, so a delivered message survives a restart.
Heartbeat registries in 10 packages. dsh-crosstalk garbage-collects dead entries instead of showing stale peers.
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.
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.
7 have rooms or topics, 4 have broadcast. No single package has both alongside durability and a trust model.
2 of 23. Without them there is no way to tell an unread handoff from an ignored one.
1 of 23. Our messages quote provider errors and stack traces — exactly where a key leaks into a log that cannot be edited afterwards.
0 of 23. Nothing in the ecosystem can find an earlier decision by its text.
0 of 23. Every option assumes participants are DSH sessions. Nothing bridges two external MCP clients to each other.
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.
MIT · zero runtime dependencies · no build step · 205 unit tests · 42 live checks against a running instance · source
mailbox_wait for loop-driven clients; notifyCommand for turn-based onesDeliberately 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.
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.
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.
?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.
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.
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.
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.
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.