pi-dsh
Pi Coding Agent provider extension that exposes the DeepSeek Harness (dsh) as a selectable provider: in-process session pool with live streaming; DSH owns prompt/memory/skills, pi-dsh routes each turn.
Package details
Install pi-dsh from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-dsh- Package
pi-dsh- Version
0.1.2- Published
- Aug 15, 2026
- Downloads
- 619/mo · 27/wk
- Author
- fatwang2
- License
- MIT
- Types
- extension
- Size
- 56.1 KB
- Dependencies
- 12 dependencies · 2 peers
Pi manifest JSON
{
"extensions": [
"./src/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-dsh
Pi Coding Agent provider extension that exposes the DeepSeek Harness (dsh) as a selectable provider. Selecting the dsh provider routes each turn to DSH's own harness, running an in-process cordis tree with a persistent per-conversation session pool instead of an HTTP LLM endpoint.
Context ownership = DSH. DSH natively loads its own system prompt, AGENTS.md (~/.dsh/AGENTS.md + the project AGENTS.md chain via dsh-agent-instructions), and skills (~/.agents/skills, ~/.dsh/skills, project .dsh/skills + .agents/skills via dsh-skill-filesystem). pi-dsh does not re-forward pi's AGENTS.md/skills — that would double-load.
Requirements
- A Pi Coding Agent host (
@earendil-works/pi-ai/@earendil-works/pi-coding-agent>= 0.82.0) - A real DSH install for pool mode (
~/.dsh/profiles/headless+ credentials);dshon PATH for the oneshot fallback - Node.js >= 22 for running the verification scripts (
test/verify-pathb.mjsimports a standalone.tsmodule via native type stripping)
How it works
- Registers a provider
dshwith a customapi: "dsh-exec"and a non-HTTPbaseUrl: "dsh-exec://local". - Pool mode (default,
mode: "pool"): boots one in-process DSH cordis tree per process (thedsh-basecomposition, with the HMR row disabled), then keeps a pool of live agents — one DSH conversation per Pi conversation, keyed by Pi's session id (LRU cap 8, 15-min idle TTL). Each turn isagent.followup(...), and thesession/eventfirehose is translated into Pi'sAssistantMessageEventStream—text_delta/thinking_delta, with tool activity shown as thinking blocks — so the answer streams live, not as one block. - One-shot fallback (
mode: "oneshot"): the pre-path-B behavior — spawndsh --profile headless "<user text>", collect stdout, emit one text block. Kept config-selectable in case the rc tree misbehaves. - DSH owns prompt/memory/skills; pi-dsh only routes the latest user message. Cross-turn continuity comes from DSH's session persistence (
~/.dsh/sessionsJSONL): a resumed Pi conversation resumes the same DSH session (via deterministic session ids, so continuity even survives a host restart).
Permissions
The embedded DSH tree runs with sandbox danger-full-access + approval never, patched at boot in src/dsh-host.ts (dsh-base would otherwise default to workspace-write + approval ask).
Rationale: pi-dsh is a provider inside the Pi host, so Pi's own permission surface is the gate — the embedded harness must not run its own approval prompts (with no answerer mounted they would fail closed anyway). This mirrors pi-claude-bridge's permissionMode: "bypassPermissions" and pi-cursor-sdk's SDK embedding (no permission dialogs at all).
- DSH's file sandbox does not restrict writes from the embedded tree.
- Network/process policy is outside DSH's
SandboxModevocabulary in any mode. - Confined modes (e.g.
workspace-writewith approval requests bridged to Pi's permission UI) are a deliberate future change: edit the two boot patches insrc/dsh-host.ts.
Install
Published on npm:
pi install npm:pi-dsh
For local development, add the absolute path of this repo to
~/.pi/agent/settings.json → packages:
{
"packages": [
"<absolute-path-to-this-repo>"
]
}
Then restart pi (or your pi host). The dsh provider appears in /model as
dsh/<model> — the model DSH itself is configured to run
(~/.dsh/settings.yaml → agent-default-model, e.g. dsh/deepseek-v4-pro).
When that file is unreadable the catalog falls back to a synthetic
dsh/dsh-harness entry. DSH owns model selection; pi-dsh mirrors it.
Dependency layout
@deepseek-ai/* are pinned (0.1.0-rc.6) pi-dsh dependencies and live in
this repo's node_modules as real installs, isolated from the pi host's
dependency graph (dsh pulls zod@4, which must not be hoisted over a host
that pins zod@3). The pi host packages and dev tooling resolve through
scope-level symlinks into the host install. See
plans/002-design-notes.md for the exact layout and rationale.
Development
npm install # installs pinned @deepseek-ai/* deps + dev tooling
# point the pi host packages at your pi install (single physical pi-ai instance):
ln -s <your-pi-install>/node_modules/@earendil-works node_modules/@earendil-works
npm run typecheck # tsc --noEmit
npm run verify # provider registration check
npm run verify:stream # oneshot-mode regression (fake dsh)
npm run verify:pathb # pool-mode integration (real dsh; SKIPs if absent)
Configure (optional)
~/.pi/agent/dsh.json:
{
"mode": "pool",
"poolMax": 8,
"poolIdleTtlMs": 900000,
"dshBin": "dsh",
"timeoutMs": 1800000
}
mode:"pool"(default) or"oneshot".poolMax: max pooled sessions (LRU-evicted beyond this). Default 8.poolIdleTtlMs: idle time before a pooled session closes, ms. Default 15 min.dshBin: used only by the"oneshot"fallback. Defaultdsh.timeoutMs: used only by the"oneshot"fallback (per-run budget). Default 30 min.
Env overrides: PI_DSH_MODE, PI_DSH_POOL_MAX, PI_DSH_POOL_IDLE_TTL_MS, PI_DSH_BIN, PI_DSH_TIMEOUT_MS.
Pinning policy
The in-process DSH surface is unstable (0.1.0-rc.6, SESSION_FORMAT_VERSION = 0, "no compatibility is implied"). @deepseek-ai/* are pinned to exact versions in package.json (no ^), and every @deepseek-ai/* import is isolated behind src/dsh-host.ts (the single chokepoint). Any dsh upgrade (0.1.0-rc.x → newer) requires re-running the Phase 1–2 spike gates (recorded in the repo's plans/ notes — not shipped in the npm tarball) before bumping the pins — the assistant/chunk / turn/end event vocabulary has no compat guarantee.
Limitations
- Pool mode requires a real DSH install (
~/.dsh/profiles/headless+ credentials in~/.dsh/.credentials.yaml). If the rc tree misbehaves, setmode: "oneshot"to fall back to the stateless subprocess path (no continuity, no streaming). - Abort cancels the live turn but preserves the pooled session; an entry is destroyed only on a real turn error or by the idle-TTL/LRU sweeper.
- Tool calls run inside DSH's own tool loop and appear as thinking blocks AT their chronological position (
[tool] …on call,[tool result] …on completion) — so the stream interleaves by time (think → tool → think → tool) instead of piling tools into a separate area. They are never Pitoolcall_*events: foreign toolCall blocks make Pi's agent loop take over tool execution with empty toolResults and re-drive the turn (an infinite loop). - Usage is reported per turn: DSH's
usagechunks are per model call (verified against persisted sessions), so pi-dsh sums the turn's chunks;totalTokensstays prompt-side so Pi's auto-compaction never misfires.
Verification
npx tsc --noEmit # typecheck
node verify.mjs # provider registered
node verify-stream.mjs # path-A (oneshot) regression vs fake-dsh.sh
node test/verify-pathb.mjs # path-B pool integration (real dsh; SKIP if absent)