Files
deepseek-harness/packages
Tianyi Cui 7097e4fb50 docs(bash-local): clarify the stdin error handler swallows ANY write error, not just EPIPE
Review noted the handler's comment said "EPIPE" while the code swallowed every
stdin 'error'. Swallowing any stdin-write error IS correct here — the write is
best-effort and the command's authoritative outcome is its exit code + captured
output (reported by the `close` handler regardless of whether the write landed).
A rare non-EPIPE pipe fault means the command ran with incomplete stdin, which
it surfaces itself via its own exit/output; rejecting `done` would instead
discard that real output and turn it into an opaque infrastructure error. Widen
the comment to state this rather than implying only EPIPE is caught. No behavior
change.
2026-07-01 15:23:09 +08:00
..
2026-07-01 12:56:13 +08:00

Packages

Harness packages, all under the @deepseek-ai/dsh-* scope. Each package is a Cordis plugin (microkernel-style): it exports either a default Service subclass or a functional plugin that gets registered via ctx.plugin(), declares its ctx key/events where applicable through declaration merging, and exposes extension points through ctx.effect(), ctx.on(), and ctx.waterfall().

Hierarchy

Packages are grouped by modular role at packages/<group>/<pkg>/. The group directory is a pure container (no package.json of its own); the package name stays @deepseek-ai/dsh-<pkg> regardless of group. Each group has a README.md describing its role and whether it is product or support infrastructure.

Group Role Release expectation
core/ Product API spine: session, system-prompt, tools, agent, and the concrete loop Product — stable surface
llm/ LLM capability family: the abstract service + provider adapters Product — stable surface
bash/ Bash capability family: the executor seam, a local impl, and the model-facing tool Product — stable surface
compact/ Compaction capability family: the abstract seam (backend + tool deferred) Product — stable surface
subagent/ Subagent capability family: the provider-registry seam and the model-facing delegation tool Product — stable surface
todo/ Todo/planning family: the model-facing todo_write tool (whole-list task tracking on the session log) Product — stable surface
session-persistence/ Persistence capability family: the seam + JSONL/SQLite backends Product — stable surface
ui/ Editor/client integration surfaces (the ACP bridge) Product — stable surface
support/ Dev/test/example infrastructure (invariants, stdio UI, replay adapter) Support — lower compatibility expectations
util/ Low-level zero-dependency utilities shared across groups (the Branded<B> primitive) Support — small, stable, harness-dep-free

The split is the point: a package's group says whether it is part of the product API or support/test/example infrastructure, so release and removal decisions do not have to treat every package as an equal public contract. New packages join an existing group; adding a new top-level group is a deliberate act (extend the group READMEs and the hierarchy docs).

Dependency graph

dsh-brand         (no harness deps — type-only Branded<B> primitive)
dsh-llm          ← dsh-brand                       (vocabulary; brands CallId)
dsh-bash          ← dsh-brand                       (abstract executor seam; brands BashTaskId/OwnerToken)
dsh-session       ← dsh-llm, dsh-brand
dsh-system-prompt ← dsh-llm
dsh-agent         ← dsh-llm, dsh-session, dsh-brand
dsh-compact       ← dsh-session, dsh-llm                (abstract compaction seam; backend + tool deferred)
dsh-tools         ← dsh-llm, dsh-system-prompt, dsh-agent
dsh-bash-local    ← dsh-bash                       (BashExecutor impl)
dsh-tool-bash     ← dsh-bash, dsh-tools            (bash tool schemas)
dsh-llm-deepseek  ← dsh-llm                        (DeepSeek adapter)
dsh-llm-pi-ai     ← dsh-llm                        (pi-ai-backed adapter)
dsh-agent-loop    ← dsh-llm, dsh-session, dsh-session-persistence, dsh-system-prompt, dsh-tools, dsh-agent
dsh-invariants    ← dsh-llm, dsh-session, dsh-agent (dev-mode contract checks)
dsh-acp           ← dsh-agent, dsh-llm, dsh-session, dsh-session-persistence, dsh-tools  (ACP JSON-RPC bridge)
dsh-ui-stdio      ← dsh-agent, dsh-llm, dsh-session (stdio readline UI plugin)
dsh-llm-replay    ← dsh-llm, dsh-session            (record/replay adapter for keyless snapshot tests)
dsh-subagent      ← dsh-agent, dsh-llm, dsh-tools    (abstract subagent provider-registry seam)
dsh-subagent-inprocess ← dsh-subagent, dsh-agent, dsh-session, dsh-llm  (shared in-process run driver)
dsh-subagent-mock ← dsh-subagent, dsh-agent, dsh-llm  (scripted provider for tests)
dsh-subagent-spawn ← dsh-subagent, dsh-subagent-inprocess  (in-process fresh child backend)
dsh-subagent-fork ← dsh-subagent, dsh-subagent-inprocess, dsh-agent, dsh-session  (in-process child seeded from parent log)
dsh-subagent-acp  ← dsh-subagent, dsh-agent, dsh-llm, @agentclientprotocol/sdk  (out-of-process child over ACP)
dsh-tool-subagent ← dsh-subagent, dsh-tools, dsh-agent, dsh-llm (model-facing delegation tool)
dsh-tool-todo     ← dsh-tools, dsh-agent, dsh-session  (model-facing todo_write tool; whole list on the session log)
dsh-agent-core    ← timer, dsh-llm, dsh-session, dsh-system-prompt, dsh-tools, dsh-agent, dsh-invariants, dsh-tool-bash, dsh-agent-loop  (the providerless spine, as one bundle plugin)
dsh-stdio-agent   ← dsh-agent-core, dsh-ui-stdio, dsh-session-persistence-jsonl, dsh-agent, dsh-session  (stdio chat APP + bin)
dsh-acp-agent     ← dsh-agent-core, dsh-acp, dsh-session-persistence-jsonl     (ACP server APP + bin)

The rule: extension plugins depend on interfaces, never on the concrete loop. dsh-agent-loop is swappable — UI/hook/tool plugins keep working against the dsh-agent vocabulary if the loop is replaced. The sanctioned exception is a composition/bundle package like dsh-agent-core, whose whole job is to assemble the concrete spine: it depends on dsh-agent-loop (and the other concrete spine plugins) on purpose. The rule constrains plugins that EXTEND the system, not the bundle that COMPOSES it — swapping the loop means shipping a different bundle, not rewiring every extension. A swappable capability splits into interface / implementation / consumer packages (the bash trio is the template — see capability seams).

What goes where

Package Group Role ctx key
llm/ llm Abstract LLM service + content-block vocabulary + chunk assembler ctx.llm
session/ core Event-sourced session log + in-memory store ctx.sessions
system-prompt/ core Prompt-section + tool-schema assembly registry ctx.systemPrompt
tools/ core Tool registry + tools/execute waterfall ctx.tools
agent/ core Agent interface, registry, agent/* event vocabulary ctx.agents
agent-loop/ core THE concrete loop plugin: ReactLoopAgent + the loop driver ctx.agentLoop
agent-core/ core Bundle plugin: the providerless/executor-less/UI-less spine as code (forwards agent-loop's agents) (loads the spine)
bash/ bash Abstract bash executor seam (interface + vocabulary) ctx.bash
bash-local/ bash Local-subprocess BashExecutor implementation (registers ctx.bash)
tool-bash/ bash Model-facing bash/bash_output/bash_kill tool schemas (registers on ctx.tools)
compact/ compact Abstract compaction seam + compact/* events + CompactionResult ctx.compact
llm-deepseek/ llm DeepSeek API adapter (hand-rolled fetch/SSE) (registers on ctx.llm)
llm-pi-ai/ llm DeepSeek adapter via @earendil-works/pi-ai (design twin) (registers on ctx.llm)
session-persistence/ session-persistence Persistence seam + write coordinator ctx.sessionPersistence
session-persistence-jsonl/ session-persistence JSONL-sidecar persistence backend (registers ctx.sessionPersistence)
session-persistence-sqlite/ session-persistence SQLite persistence backend (registers ctx.sessionPersistence)
invariants/ support Dev-mode event-contract invariants + session-log freeze (listens on session/*, agent/*)
acp/ ui Agent Client Protocol bridge: serves the agent to an ACP editor over JSON-RPC stdio (drives ctx.agents/ctx.sessions)
stdio-agent/ ui Terminal stdio chat APP: agent-core spine + console logger + readline UI + a pre-created main agent, with a bin (composition + bin)
acp-agent/ ui ACP server APP: agent-core spine + JSONL persistence + the acp bridge (no stdout logger), with a bin (composition + bin)
ui-stdio/ support Minimal stdio (readline) UI plugin: renders agent/* events, feeds stdin lines to the agent (drives ctx.agents)
llm-replay/ support Record/replay adapter: short-circuits llm/stream with chunks from a recorded session JSONL (keyless snapshot tests) (listens on llm/stream)
subagent/ subagent Abstract subagent seam: named-provider registry for delegating to child agents ctx.subagents
subagent-inprocess/ subagent Shared in-process subagent run driver used by spawn/fork; pure library, registers nothing (none)
subagent-spawn/ subagent In-process backend: a fresh child agent (registers on ctx.subagents)
subagent-fork/ subagent In-process backend: a child agent seeded with the parent's completed-turn prefix (registers on ctx.subagents)
subagent-acp/ subagent Out-of-process backend: a child agent in a spawned subprocess, driven over the Agent Client Protocol (registers on ctx.subagents)
subagent-mock/ support Scripted SubagentProvider for testing the seam through the real load path (registers on ctx.subagents)
tool-subagent/ subagent Model-facing subagent delegation tool over ctx.subagents (registers on ctx.tools)
tool-todo/ todo Model-facing todo_write tool; writes the whole task list to the session log (todo/write) (registers on ctx.tools)
brand/ util Type-only Branded<B> nominal-typing primitive (no runtime code, no harness deps) (none — type-only)

Each package has its own README.md with purpose, service API, events, extension points, and deliberate non-goals (TODOs).

Conventions (applied across all harness packages)

  • Registrations are effects: every contribution (adapter, tool, section, agent, event listener) goes through ctx.effect() / ctx.on(), so disposal and HMR clean up automatically. Every register() returns the disposer.
  • Declaration merging for events and ctx: services declare their events in declare module 'cordis' { interface Events { ... } } and their ctx key in interface Context.
  • Waterfall semantics: ctx.waterfall listeners receive (...args, next) and MUST call next() to delegate; returning without it short-circuits (the veto mechanism).
  • Extensible unions: ContentBlockMap, MessageSourceMap, FinishReasonMap, TurnTriggerMap, TurnEndReasonMap, and SessionEventMap use the merge-extensible-map pattern so plugins can add variants via declaration merging.
  • ESM everywhere; imports use package names across package boundaries and explicit .ts relative specifiers within a package.
  • Tests: vitest, colocated under packages/<group>/<pkg>/tests/*.spec.ts. Every registry needs an HMR-safety test. Err on the side of more tests.