DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

plumbkit /

plumbkit/dsh-plumb-identity

Verified

Per-agent plumb session identity for DeepSeek Harness: every conversation, workspace, and subagent gets its own stable plumb session id on a shared plumb MCP connection

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@6b580a6c

dsh-plumb-identity

Per-agent plumb session identity for DeepSeek Harness.

A cordis plugin that gives every DSH conversation, workspace, and in-process subagent its own stable plumb session identity — enforced at the transport layer, not by instruction. Developed and tested against @deepseek-ai/dsh@0.1.1-rc.2 and plumb 0.17.x.

Why

DSH multiplexes every conversation over one plumb serve connection per process, and DSH's MCP client (@deepseek-ai/dsh-mcp-client) sends no identity: no per-conversation session id, no per-call _meta. plumb's isolation layer — per-agent workspace shards, read tracking, mail addressing — arms on a declared identity, so unidentified conversations can force-re-pin each other's workspaces. The usual mitigation is an instruction in ~/.dsh/AGENTS.md asking each conversation to declare a session_id; that covers main conversations, but DSH subagents run in-process with their own system prompt and no parent context, so they are structurally anonymous.

plumb accepts an agent identity on two channels, both used here:

  • session_start { session_id, workspace, purpose } — declares the agent, pins its own workspace shard, and records the linkage (stable ids inherit plumb session names across resumes).
  • a per-call tools/call params _meta["dev.plumbkit/logical-agent"] — always honoured by plumb; the fallback that keeps every call attributed even when no declaration has happened yet.

How it works

  1. Correlate. A listener on DSH's tools/execute waterfall sees every tool call with exec.agent — the caller, conversation or subagent, each with its own session UUID and workspace. For mcp__<serverName>__* calls it mints a stable id dsh-<workspace-slug>-<session-short> and runs the rest of the pipeline inside an AsyncLocalStorage scope.
  2. Stamp. A narrow patch on the MCP SDK Client.prototype.request (the exact class instance dsh-mcp-client uses, imported by absolute path from DSH's shared profile tree at module load) adds _meta["dev.plumbkit/logical-agent"] to tools/call requests made inside such a scope. The scope is armed only around plumb tool executions, so a stamped request is by construction a request to the plumb server. The same patch captures the plumb Client instance the moment its connection names itself — the stdio transport's server params say plumb serve — during the SDK's initial tools sync, long before any agent exists.
  3. Declare. With the captured instance, the plugin issues a proactive session_start { session_id, workspace, purpose } for each agent's first plumb call — outside the identity scope, so the id travels in the arguments where plumb's linkage records it and the shard pins its own workspace. Per-Client bookkeeping because dsh-mcp-client builds a fresh client on every reconnect generation, and a new connection must re-declare.

Everything fails open: SDK missing, patch refused, declaration rejected — the call proceeds and the AGENTS.md instruction surface remains the fallback. A tool call must never break because its observer could not describe itself.

Install

Requires a running plumb daemon (plumb serve reachable as an MCP server named plumb — see plumb setup dsh).

As a profile bundle (recommended)

dsh plugin --profile web add dsh-plumb-identity
dsh plugin --profile headless add dsh-plumb-identity

The package ships a dsh.bundle patch, so installing the bundle also mounts the plugin with default config. Restart dsh (running processes do not re-read patch layers).

Pick one mount mechanism. Installing the bundle and keeping a manual insert row with the same id crashes DSH at boot — duplicate loader entry id: dsh-plumb-identity. Patch layers merge config overrides by row id, but two inserts never merge. If you switch between the two, remove the other first (dsh plugin --profile <name> remove dsh-plumb-identity, or delete the row).

Manual patch row (alternative to the bundle)

Only if the bundle is not installed — append to ~/.dsh/cordis.patch.yml:

- insert:
    - id: dsh-plumb-identity
      name: dsh-plumb-identity
      config:
        serverName: plumb

Remove the row, or disable it with an override row at the TOP LEVEL of any patch layer loaded after the one that mounts it (never inside an insert list — that would insert a nameless row):

- id: dsh-plumb-identity
  disabled: true

Local development

Point the row at a checkout instead of the package name — and remove or disable the bundle mount first, per the warning above:

- insert:
    - id: dsh-plumb-identity
      name: 'file:///absolute/path/to/dsh-plumb-identity/dsh-plumb-identity.mjs'
      config: { serverName: plumb, logEvents: true }

Config (all optional)

field default meaning
serverName plumb which MCP server's tools to guard (mcp__<serverName>__*)
idPrefix dsh first segment of minted ids
purpose dsh session_start purpose tag for conversations
subagentPurpose dsh-subagent purpose tag when the session header shows parentSession/delegationDepth
detail brief orientation packet size for the proactive session_start
excludeEnv ["PAUTA_RUN_ID"] skip entirely when any of these env vars is set
connectMarker plumb substring matched against the stdio command to recognise the plumb connection
logEvents false per-call stamp/capture debug lines
sdkPath DSH profile tree absolute path to the MCP SDK's client/index.js (non-standard installs)

excludeEnv matters: pauta's dsh driver appends its own plumb linkage sentence (pauta-dsh-<card>-<run>), and a second identity for the same run would be noise.

Verify

# identities and their workspaces, per agent
sqlite3 "file:$HOME/Library/Application Support/plumb/session_state.db?mode=ro" \
  "SELECT logical_agent_id, workspace, source FROM pinned_workspace WHERE logical_agent_id != '';"

# declared sessions (external_id = minted id, purpose = dsh / dsh-subagent)
ls -t "$HOME/Library/Application Support/plumb/sessions/" | head

With two conversations in two workspaces plus a subagent you should see ≥3 distinct logical_agent_ids, each pinned to its own workspace, and no pin-contest notices in the daemon log. plumb sessions and the workspace_sessions tool show the same picture live.

Tests

npm test

Hermetic — no DSH, no plumb, no network (the MCP SDK is a stub fixture). spike/probe.mjs and spike/observe.mjs are the field diagnostics: mount either through a --patch overlay into a dsh --profile headless run to re-map DSH internals after an upgrade.

End-to-end against a fake model

npm run test:e2e

Verifies the full chain — real DSH agent loop, this plugin mounted from the working tree, real plumb — with only the MODEL scripted (test/fake-model.mjs, a local OpenAI-compatible server), so it spends zero LLM credits and touches no network beyond localhost. Each scenario runs in a throwaway DSH_HOME whose only configured provider is the local fake (a misrouted model request fails on "unknown provider" rather than billing a real key) against an isolated plumb serve whose HOME/XDG roots live under the scenario's temp dir — neither your DSH profile nor your plumb state is touched.

Three scenarios, each asserting on plumb's own session records:

  • tool-call — one plumb tool call; a session with purpose: dsh and external_id: dsh-* must be declared.
  • text-only — no plumb tool call; nothing may be declared (the control).
  • subagent — the agent delegates a subagent that makes the plumb call; a purpose: dsh-subagent identity must be declared.

Requirements: a dsh install (resolved from $DSH_BIN, else the $DSH_HOME / ~/.dsh profile tree, exactly what dsh itself resolves) and a plumb binary ($PLUMB_BIN, else ../plumb/plumb beside this checkout, else plumb on PATH). Run one scenario with node test/e2e-mock-provider.mjs --mode subagent; keep the temp dirs for debugging with --keep or E2E_KEEP=1.

Interplay notes

  • A model that follows ~/.dsh/AGENTS.md and calls session_start with its own id changes nothing: the per-call _meta stamp wins plumb's identity resolution, so the conversation stays under its minted id. Keep the AGENTS.md rules — they are the fallback when the plugin is absent and the path through which the model receives plumb's orientation packet.
  • A plumb connection keeps ONE session record; external_id/purpose in plumb sessions show the most recent declarer on that connection. Per-agent isolation is plumb's shard state (pinned_workspace/read_tracking keyed by logical_agent_id), not the session record.
  • Single-identity connections pin at connection level by design ("one declared id arms nothing"); sharding and per-agent pins engage from the second distinct identity, which is exactly the multi-workspace web case.
  • Compatibility is pinned to @deepseek-ai/dsh@0.1.1-rc.2 field locations (agent.id, agent.session.header.{cwd,parentSession,delegationDepth}, the tools/execute waterfall, SDK client/index.js). If a DSH upgrade moves one of these, the plugin fails open loudly in the DSH log; re-run the spike probes to re-map, and please open an issue.

License

MIT

—/ 5

No ratings yet

Verified DSH bundle

Commit 6b580a6c30bd

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout