Bundle
@wy/dsh-cross-session-messaging
Cross-session relay for DeepSeek Harness: peer discovery and text delivery between independent dsh processes.
- Source
- Weiyang742
- stars
- 1 stars
- License
- MIT
- Updated
- Updated 5 days ago
Readme
# DSH Cross-Session Relay
> English | [中文](./README.zh.md)
[](https://github.com/Weiyang742/dsh-cross-session-messaging/blob/main/LICENSE)
[](https://github.com/Weiyang742/dsh-cross-session-messaging/actions/workflows/ci.yml)
Lets multiple independent DeepSeek Harness (dsh) sessions discover each other and
exchange text messages peer-to-peer.
## The problem
dsh ships two built-in "cross-session" capabilities, neither of which is
peer-to-peer messaging between processes:
- **subagents** are parent/child: the parent agent spawns a subagent that lives
in its context and stays under its control — not an independent peer.
- **`@session` mentions** in the Web UI are one-way, read-only recall: you can
reference another session's history, but cannot send it a message.
In practice, though, two sessions often need to send each other messages. Take
a "database assistant" and a "frontend assistant" split into two independent
sessions, each in the environment that suits it:
- the database assistant runs on an intranet machine with database access and
context;
- the frontend assistant runs on a dev machine and owns the UI.
When the frontend assistant needs the `CREATE TABLE` SQL for a users table, it
has no database context or credentials of its own, so it has to *message* the
database assistant; the database assistant looks it up and *replies*. This
peer-to-peer ask/reply across different specialties, permissions, or machines is
exactly what subagents' parent/child structure and `@session`'s read-only recall
cannot cover. This plugin adds that peer-to-peer, two-way channel.
## What this plugin does
A peer-to-peer, centerless, cross-process (optionally cross-machine) message
channel:
- **Model-driven**: the agent itself calls `cross_session_list_agents` to discover
peers and `cross_session_send_message` to send; no human relays anything.
- **Relay injection, never authorization**: messages arrive as `form: 'relay'`
text and never approve a tool call on the user's behalf — the receiver's own
approval still applies.
- **Reliable delivery**: sender-side spool + exponential backoff + dead-letter;
receiver persists before ack; at-least-once with content dedup.
- **Local and cross-machine**: Unix sockets locally, TCP+TLS (fail-closed, static
peers) across machines.
- **Centerless**: the on-disk registry is the source of truth; there is no
central server.
## How it differs from dsh's built-ins
| | subagent | `@session` mention | this plugin |
|---|---|---|---|
| Relationship | parent/child | one-way, read-only | peer-to-peer |
| Can send messages | parent → child control | no | yes, both ways |
| Cross-process / cross-machine | same process | same process | yes |
| Injection form | subagent context | history snapshot | relay text, no authorization |
## Installation
Prerequisites: Node.js >= 24.11 and a working dsh (DeepSeek Harness) environment.
When installing from source, build the artifacts first (`lib/` is not distributed):
```sh
npm install
npm run build
```
Then add the plugin to a profile:
```sh
dsh plugin --profile demo add ./cross-session-messaging
dsh --profile demo --dump-config # verify it is wired in
```
The plugin is inserted into the profile at the bundle layer via `cordis.patch.yml`;
any fields not listed fall back to schema defaults.
## Usage
Open two dsh sessions (for example, two "New Session" in the Web UI) and, in one
of them, ask in natural language:
> List the other sessions currently running, then send one of them a message:
> hello.
The model calls `cross_session_list_agents` to discover the peer and
`cross_session_send_message` to send; the other session receives the message as
`form: 'relay'`. The human only writes a natural-language request and names no
tool.
See [Quick start](docs/quickstart.md) for the full end-to-end walkthrough.
## Configuration
Configuration lives in the plugin's `cordis.patch.yml` under `config:` (already
used to wire it in during installation); any field not listed falls back to its
schema default. Local use normally needs no customization beyond `name` and the
inbound policy:
```yaml
- insert:
- id: cross-session-messaging
name: '@wy/dsh-cross-session-messaging'
config:
dirs:
baseDir: ~/.dsh/cross-session # data root; registry/socket/spool derive from it
inbound:
rules: [] # inbound rules, see below
defaultDecision: auto # fallback when no rule matches
name: '' # session name, defaults to the shortId
# Cross-machine (optional, see "Cross-machine"):
# remote:
# listen: { host, port, identity, secret }
# tls: { key, cert }
# peers:
# - { name, host, port, secret, tls, tlsCa/tlsFingerprint }
```
- `dirs.baseDir`: root of all data directories (`registry` / `socket` / `spool` / `credential` / `deadletter` / `audit`), default `~/.dsh/cross-session`; each of the six can be overridden.
- `inbound.rules`: `accept | hold | refuse` rules matched by `receiver` (local session id/name) and `sender` (sender sessionId); omitting either means "all local sessions" / "default rule".
- `inbound.defaultDecision`: fallback when no rule matches; `auto` (default) consults the receiver's `DSH_PERMISSION_MODE` (`danger-full-access` → hold, otherwise accept), or set `accept | hold | refuse` explicitly.
- `name`: the session's readable name, used for addressing by `cross_session_send_message`.
### Cross-machine
Cross-machine delivery needs `remote` (omit it for local-only use):
- `remote.listen`: the TCP listener when this machine receives (`host` / `port` / `identity` / `secret`); `identity` is how this machine appears to peers and is stamped onto inbound `from`.
- `remote.tls`: listener certificate (`key` / `cert`); omit to auto-generate a self-signed cert under `spool/tls`, with its fingerprint written to `spool/tls/fingerprint`.
- `remote.peers`: the static peer list when this machine sends (`name` / `host` / `port` / `secret` / `tls` / `tlsCa` or `tlsFingerprint`). `tls` defaults to `true`; cross-machine TLS requires `tlsCa` or `tlsFingerprint`, otherwise the connection is refused.
### Capacity and retry (tuning)
`limits.*` (queue caps, message size, rate limit, dedup window), `delivery.*`
(retry/backoff), `metricsIntervalMs`, and `auditMaxBytes` have conservative
defaults and rarely need changing; adjust by field name when you do.
## Tools
- `cross_session_list_agents`: list reachable peer sessions (sessionId / name / shortId / cwd / pid).
- `cross_session_send_message`: send text to a session by sessionId, shortId, or name.
- `cross_session_inbox_review`: review held messages awaiting accept/refuse.
- `cross_session_inbox_resolve`: `accept` or `refuse` a held message.
## Permission semantics
- Peer messages are injected with `source.form = 'relay'`, enter the model as text only, and never approve any tool call on the user's behalf; the receiving tools still go through their own approval (fail-closed by default).
- The `from` identity is overwritten by server-side authentication (local = sessionId, cross-machine = `listen.identity`), never trusted from the sender; tool names this session's permission system rejected are remembered, so `send_message` will not forward text asking another session to run one of them.
## Hooks / child-process replies
The plugin injects the current socket / sessionId / handshake token into shell child processes via dsh's `ctx.shellEnv` registry (`DSH_CROSS_SESSION_SOCKET` / `DSH_CROSS_SESSION_ID` / `DSH_CROSS_SESSION_TOKEN`). A long-running child process or hook can connect to that socket and send back text, or call the exported `sendToCurrentSession(text)`.
## Testing
```sh
npm test # unit + integration (real sockets)
npm run check # lint + typecheck + build + test
```
Test layering, the cross-machine stress matrix, and CI policy are in [docs/testing.md](docs/testing.md).
## Limitations
- Cross-machine discovery is static `peers` config (requires restart), with no central registry and no public NAT traversal; real public two-host transport/service layers are verified (stable at low frequency, occasional ping failures at high frequency), but the host end-to-end where the model proactively calls the tool is not yet verified.
- TLS certificates are auto-generated (openssl self-signed) but not rotated/revoked; cross-machine TLS requires `tlsCa` / `tlsFingerprint` (fail-closed).
- Delivery is at-least-once with content dedup (across restarts), not strict exactly-once: duplicates are possible outside the window or for different content.
- Both accepted and held are persisted (recovered on restart); accepted messages are marked after injection and re-delivered if not injected, with a minimal crash window.
- Cross-machine identity is single-peer: `listen.identity` is a single value (two-machine assumption); future multi-peer needs an `allowed` list.
- Forward protection is heuristic (best-effort); the real security boundary is relay-does-not-authorize + the default inbound policy.
- Peer messages are injected with `form: 'relay'` and carry no user authorization; receiving tools still use their own approval (fail-closed by default).
Install
dsh plugin --profile web add github:Weiyang742/dsh-cross-session-messaging
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install wy-dsh-cross-session-messaging from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.