Isn’t it about time Claude prompted Claude inside Claude Code? That question is the whole origin of this. A higher-order function takes a function as its argument; we wanted the higher-order prompt, one session directing another instead of a human relaying context between terminals. cc-dm (claude code direct messaging) is the experiment it became: parallel sessions take on the roles of an engineering org and pass context directly.
What we built
A Claude Code plugin that lets any session direct-message any other session on
the same machine. Messages arrive as native <channel> events inside the
receiving session’s context window, within roughly 500ms, over the Claude Code
Channels protocol. It ships as an installable plugin and an npm package, MIT
licensed, and was reviewed and published through Anthropic’s official Claude Code
plugin submission process (published 22 Mar 2026).
Method
No daemon, no ports, no network. Each session spawns a channel server over stdio; every server connects to one shared SQLite database (WAL mode) and polls it on a fixed interval.
Session A (planner) ──┐
Session B (backend) ──┼──→ ~/.cc-dm/bus.db (SQLite WAL)
Session C (tests) ──┘ ↑
500ms poll per session
→ <channel> event pushed into context session A bus.db session B
│ │ │
│ dm to B │ │
├─── write a row ───────▶│ │
│ │◀─── poll, every 500ms ─┤
│ │─── row, marked read ──▶│
│ │ ├─▶ <channel> event
│ │ │ in B's contextThe design splits into small, independently tested units: the message bus (read/write, delivery marking, schema migration), session heartbeat and liveness, permission relay (opt-in remote tool approval across sessions), input sanitization, and the MCP tools surface. Message metadata (priority, type, thread id) is stored as JSON and spread before the routing fields on delivery, so user data cannot spoof the routing envelope.
The guarantee is one object literal. On delivery the sender’s own metadata is spread first, then the real routing fields are written over it, so a message cannot forge who it is from or who it is for.
// the delivered <channel> event: spread the sender's meta first, then stamp the
// real routing fields over it, so from/to cannot be forged.
await server.notification({
method: "notifications/claude/channel",
params: {
content: message.content,
meta: {
...message.meta,
from_session: message.from_session,
to_session: sessionName,
message_id: String(message.id),
sent_at: message.created_at,
},
},
});
Findings
The suite is 140 tests, zero failing, 296 assertions, across six independently tested units:
tools 55 ██████████████████
bus 35 ███████████
permission 20 ███████
integration 17 ██████
heartbeat 8 ███
sanitize 5 ██- The whole transport is a shared file and a poll loop: it reached a working v1.0.0 the day after the first commit, then 8 tagged releases (v0.1.0 through v1.3.1) hardened it: meta attributes, permission relay, ghost-name theft protection.
- A local-only, file-backed bus sidesteps an entire class of failure modes (ports, sockets, auth, a daemon to supervise). The one deliberate trade is non-atomic read-then-mark in the bus: on a crash between the two, a message re-delivers rather than vanishes, the right default for a local tool.
Meaning
cc-dm is a small, sharp instance of the lab’s working thesis: multi-agent orchestration is becoming ordinary, and the plumbing between agents should be as boring and reliable as a Unix pipe. The interesting move is subtractive: choosing a SQLite file and a 500ms poll over anything that needs a server.