Cross-session agent messaging for DeepSeek Harness with session discovery, offline delivery, delivery receipts, and sender-session navigation.
Install
# from npm (prebuilt)
dsh plugin --profile web add dsh-agent-message
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:GengDaPeng/dsh-agent-message
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
English | 中文
A cross-session Agent communication plugin for DeepSeek Harness: it lets different Agent sessions running in the same process send and receive messages to each other, just like messaging.
What is this
In DeepSeek Harness, a single process hosts multiple Agent sessions at once. This plugin equips each session with three tools so they can "message" each other:
- Before sending, first list every sendable independent session (non-archived, excluding actual subagents, including offline sessions that have not been reopened), and find the target by its title;
- Once found, deliver the message to the target session — ordinary messages always enter a new independent turn; if the target is offline (not loaded since the last process restart), the plugin resumes it through Harness's public API, delivers the message, and keeps the handle loaded for later communication until plugin teardown;
- When needed, query the delivery status of a message on demand (queued / claimed / discarded / unknown), with the target runtime status reported separately for supervision scenarios.
Typical scenarios: an orchestrator Agent dispatching work to a developer Agent, two Agents collaborating in a relay, a main session sending instructions to a test session, or a supervisor Agent watching over several workers.
Features
| Capability | Description |
|---|---|
list_peer_agents |
List all sendable independent sessions: non-archived, excluding actual subagents while retaining ordinary forks; returns id, title, working directory, and runtime status |
send_agent_message |
Send a message to a session id; followup creates an independent turn by default and offline targets are resumed automatically; explicit modes are followup, inject, and steer |
check_delivery |
Query receipts for messages sent by this session (pending/claimed/discarded/unknown). A successful send first returns accepted. A failure before acceptance is a tool error. Explicit message ids remain queryable after restart |
Native Harness @ session reference |
Reuse Harness's structured Session Reference directly. The current Agent receives the stable Session ID plus a bounded, read-only snapshot marked as untrusted. Referencing alone does not send a message or wake the target |
| Visible Agent message provenance | Relay keeps its true plugin provenance. Harness 0.2.x renders it as a request row that triggered a new turn; once expanded, the plugin adds a clickable From Session · <name>: sender line and hides the model-addressing protocol header, with labels following the Harness language setting |
| Delivery card settings | The Host declares a Config field with .volatile(); since 0.2.2 the plugin registers a configuration section on its bundle page through the plugins.bundle.config slot (between the details and the components list), writing to the Harness settings document. Toggle delivery mode and target runtime details; messageId is always shown |
Sender navigation example

Relay messages are rendered natively by Harness (a request row that triggered a new turn on 0.2.x); once expanded, the plugin adds the From Session · <name>: sender line, and clicking or keyboard-activating it opens the sender session. The persisted source remains plugin relay provenance rather than impersonating human input. The full session id remains in typed source metadata and in a Host-generated model-visible protocol header, so the receiving Agent never has to guess the sender.
Delivery modes (the mode parameter of send_agent_message)
| mode | Meaning |
|---|---|
| (default, omitted) | followup: create an independent turn; an offline target is resumed automatically before delivery |
followup |
Same as the default; queue directly when online, or resume and queue when offline |
steer |
Intervene in the target's current work immediately (running sessions only) |
inject |
Add next-step context without interrupting the current goal (running sessions only) |
Archived sessions and actual subagents are always rejected; ordinary forks remain independent and sendable. Sending to yourself is also rejected.
Installation
Option 1: One-liner (recommended)
dsh plugin --profile web add dsh-agent-message
It self-registers on install; no extra configuration is needed.
Compatibility: Node.js 24 and DeepSeek Harness >=0.2.2-rc.1 <0.3.0 (bundling @deepseek-ai/schemastery >=3.19.0). Session-log reads and receipt attribution use the host's public observeSession()/ownEvents()/inheritedEventCount inheritance boundary and fall back only when the host exposes the older readSession() alone; plugin settings are Host Config fields marked .volatile(), and the form is rendered by the plugin page's component row into the Harness settings document. Verified against Node.js 24.x and Harness 0.2.2-rc.1.
Option 2: Install from GitHub
dsh plugin --profile web add github:GengDaPeng/dsh-agent-message
Option 3: Just tell your Agent
Open any DSH session and send it this message:
Help me install the cross-session communication plugin by running:
dsh plugin --profile web add dsh-agent-message
The Agent will run this command via bash; once installed it auto-mounts and is immediately available to all sessions.
What happens automatically after install
The plugin ships a cordis.patch.yml (pointed to by dsh.bundle.patch in package.json) that mounts itself into the host composition on install — so you do not need to manually edit presets or cordis.patch.yml. All sessions automatically gain list_peer_agents, send_agent_message and check_delivery.
Usage
- Type
@in session A's composer and choose the target session from Harness's native candidate menu; - Harness gives A a bounded, read-only, untrusted snapshot of that Session, but does not message or wake B. A calls
send_agent_messageonly when the current request or an orchestration responsibility already granted by the user explicitly requires cross-session communication. For example,@B tell it to stop after opening the draft PRsends, while@B analyze its latest conversation resultonly uses the referenced snapshot; - For an explicit forwarding request, A only delivers and reports whether the message was accepted or failed. It must not execute the forwarded task itself or ask B for an extra acknowledgement. B sends a message to
senderSessionIdonly when the body explicitly asks it to return business content; - You can still ask the Agent to call
list_peer_agentsand send directly with a full session id; - Session B receives a native
UserMessagewith a typed relay source plus a minimal Host-generated source header on the first body line, so B does not have to guess the sender. The Client presents it as a visible Agent message card whose header opens the sender session; - (Supervision) Say "check the status of my messages to
<session id>" — it callscheck_delivery.
How it works
Each Agent has an inbox Inbox containing two FIFO queues:
next-turn: messages queued to be processed as an independent turn;next-step: steering input consumed at the next step boundary within the current turn.
Delivery paths of send_agent_message:
- Ordinary online message: find the target Agent through the
agentsregistry and callfollowup()so it enters an independentnext-turn; - Running-mode semantics: users do not need to name a mode. The Agent selects
steer()when the full request clearly asks for immediate intervention, orinject()when it clearly asks to add context without interrupting the current task. The target must actually berunning; when intent is unclear, the Agent keeps the defaultfollowup(); - Ordinary offline message: first read and validate one logical-session snapshot through
sessionQuery.observeSession(), then restore it through the publicagents.resume()API and callfollowup(). The plugin retains and reuses the returned handle, keeps the target loaded after it becomes idle, and releases the handle only when the plugin unloads. Resume failures are returned directly; the plugin does not forge core Inbox events as a fallback note.
Session enumeration, batched titles, and offline log reads use Harness's sessionQuery.listSessions(), readTitleSnapshots(), and observeSession() respectively. SessionId is the only address; parentSession records fork lineage only, and only origin: subagent identifies an actual subagent. The plugin does not scan sessionPersistence directly to rebuild a parallel session directory. observeSession() is used because it returns the session header, the exact inheritance boundary, and events together, and it can read forked sessions; the older readSession() throws on forked sessions in the current host (the seed must equal the inherited prefix), so it remains only as a fallback when the former is unavailable.
After send_agent_message submits the native message to the target Inbox, it immediately returns accepted with the native messageId; both the terse tool result and the full tool card show a selectable messageId for copying, while the complete result also stays in tool presentation metadata. check_delivery then derives pending (still queued), claimed (claimed by a turn), discarded (cancelled), or unknown from Inbox events on demand. claimed is transport evidence only: it does not prove that the message was read, answered, or completed. Pre-admission failure remains a Harness tool error and writes nothing to the target Inbox. Runtime state is reported separately as targetRuntimeStatus, so unrelated Agent activity never changes the message state. A known messageId remains queryable from the target Inbox log after a process restart.
Receipts cover relay messages sent by the calling session whose provenance can be verified in the target session's own Inbox events. A lookup by id returns unknown for another session's message or when its provenance cannot be verified. The same rule applies online and offline. Fork-inherited parent history belongs to the parent: a message the parent received and claimed or discarded never appears in the fork's receipts, while messages newly sent to the fork stay queryable. The boundary comes from the host's public inheritance data (Session.ownEvents() online, SessionLogSnapshot.inheritedEventCount offline, and header.seedLength on the older contract). When a host reports a fork but provides no decidable boundary, the query fails with an explicit controlled error instead of silently folding as if nothing were inherited or masquerading as unknown.
Every cross-session message is created by Harness createUserMessage(), and UserMessage.id is its only message identity. Its source always uses kind: dsh-agent-message and form: relay, plus the protocol version, sender/target Session ids, and display title. Because current Harness model requests do not expand custom source fields, the Host also writes a minimal <dsh-agent-message> header containing only senderSessionId on the first body line. The typed source is the durable/UI truth; the header is only the model-visible projection needed for reply addressing. The plugin registers no global system prompt; send admission lives only in the send_agent_message tool contract. The Client only projects relay as a visible Agent message card and never rewrites an Agent message as human user provenance.
Relay only means “a message addressed by another session”; it neither requires nor forbids a reply. When the body explicitly requests business content in return, the receiving Agent may send a message to senderSessionId with the same tool. Otherwise it must not send a transport acknowledgement or a bare “received.” The plugin does not automatically correlate requests and replies or forward ordinary Agent answers.
Composer-side @ is provided entirely by Harness's native ui-reference / session-reference services. Harness retains the stable Session ID in the structured reference and owns bounded snapshot loading and injection. The plugin no longer registers an @ source, parses titles for addressing, or maintains a second session-reference UI.
See docs/architecture-v2.md for the current architecture contract.
Directory structure
dsh-agent-message/
├── lib/
│ ├── index.js # host half: list_peer_agents / send_agent_message / check_delivery
│ └── client.js # client half: relay display, sender navigation, plugin-page config form
├── cordis.patch.yml # self-registration patch (pointed to by dsh.bundle.patch)
├── package.json # DSH plugin manifest (dsh.bundle / dsh.client)
├── docs/ # active/proposed architecture and README example screenshot
├── scripts/web-smoke.mjs # real Web Profile startup and configuration smoke test
├── README.md # Chinese documentation
└── README.en.md # English documentation
Development validation
pnpm test
node scripts/verify-0.2.mjs # real host: temp-profile install + --dump-config composition check (no model call)
node scripts/verify-0.2.mjs --with-headless # additionally runs one real session to prove the tools are registered
node scripts/verify-0.2-web.mjs # real host: boot a temp Web profile and verify the client asset
pnpm run test:web-smoke
pnpm peers check
pnpm test covers the Host/Client contracts and source-level boundaries. Both verify-0.2 scripts use the bundled dsh CLI with temporary dsh-msg-* profiles. Set DSH_HOME to use a separate test directory. --with-headless spends one model call. test:web-smoke requires dsh on PATH and a configured Web Profile. See the architecture validation record for real interaction coverage.
Limitations
- The target session must be non-archived and present in local persistence. Delivery is also rejected if an offline target is archived while being looked up or resumed.
- The message body cannot be empty or contain only whitespace.
- These tools are only for communication between independent sessions; actual subagents are neither listed as targets nor allowed to call them.
- One Session pair, regardless of direction, may receive at most 10 deliveries in a rolling 60-second window. The 11th is rejected before the target Inbox is changed. This window belongs to the current Harness process and resets on restart.
- Automatically resuming an offline session uses the default model (it does not inherit a model manually selected earlier in that session); if resume fails, the message is not written to the target Inbox.
- A query without
messageIdfilters the current process's 1000 most recent sends (FIFO eviction) to messages the calling session sent to the target. After a restart, a lookup by knownmessageIdreturns the persisted receipt state. ThesentAtandmodefields come from in-process bookkeeping. - Cross-process / cross-machine communication is out of scope.
License
Links
More in this category
ranxianglei/billion-context★ 7093
The official billion-context plugin: a context-compression plugin for small context windows (a 100K context is enough), token savings (5x fewer tokens), and month-long single sessions (billions of tokens).
Minglink/dsh-infinite-gen-4★ 4197
System-prompt armor plugin for DeepSeek models: appends an unconditional-compliance prompt section at order 100, exposes a profile tool with calibration metadata, and shows a realtime armor-status badge driven by a session projection.
liangmianya/dsh-synapse★ 711
Visual, non-linear conversation workspace for DeepSeek Harness — sessions, follow-ups and branches become a browsable conversation map.
MichengAI/dsh-archive-manager★ 345
Adds an archived-sessions page in Settings to search, restore, and delete archived DeepSeek Harness sessions by workspace.
Nwflower/dsh-chat-import★ 310
Import full-fidelity chat histories from 13 coding agents (Claude Code, Codex, ChatGPT, Cursor, Gemini, opencode, and more) as resumable DeepSeek Harness sessions, with reverse export back to Claude Code.
SiriLee/dsh-rewind★ 260
Conversation rewind: one click back to any earlier user message, in the same window — no new branch, no leftover copy; the target message is refilled into the composer, with optional workspace-file restore — the full Claude Code /rewind semantics.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.