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.

Packages

Package details

extension

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); dsh on PATH for the oneshot fallback
  • Node.js >= 22 for running the verification scripts (test/verify-pathb.mjs imports a standalone .ts module via native type stripping)

How it works

  • Registers a provider dsh with a custom api: "dsh-exec" and a non-HTTP baseUrl: "dsh-exec://local".
  • Pool mode (default, mode: "pool"): boots one in-process DSH cordis tree per process (the dsh-base composition, 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 is agent.followup(...), and the session/event firehose is translated into Pi's AssistantMessageEventStreamtext_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 — spawn dsh --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/sessions JSONL): 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 SandboxMode vocabulary in any mode.
  • Confined modes (e.g. workspace-write with approval requests bridged to Pi's permission UI) are a deliberate future change: edit the two boot patches in src/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.jsonpackages:

{
  "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.yamlagent-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. Default dsh.
  • 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, set mode: "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 Pi toolcall_* 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 usage chunks are per model call (verified against persisted sessions), so pi-dsh sums the turn's chunks; totalTokens stays 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)