SubprocessHandle loses dispose(graces) and SubprocessDisposeGraces: the stdin-EOF→SIGTERM→SIGKILL sequence is teardown POLICY encoding one consumer's cooperation shape, not process vocabulary — the seam keeps kill/terminate/waitForExit, and waitForExit(signal?) is the quiescence probe a consumer ladder needs. dsh-subagent-acp owns disposeAcpChild() over those public verbs (tier tests move into its suite; a never-exits stub pins the fail-loud path); dsh-subprocess-local sheds the ladder, its deadline import, and the dsh-timeout dependency. Every future backend now owes four verbs and no teardown policy. New bilingual ladder-ownership Agent Note records the decision; catalogs regenerated.
@deepseek-ai/dsh-subprocess
English | 中文
The subprocess seam (ctx.subprocess). The abstract SubprocessService exposes one method — spawn(spec): SubprocessHandle — plus the vocabulary shared by every consumer: the fully-explicit SubprocessSpawnSpec, SubprocessHandle with its non-consuming offset-based output readers, SubprocessOutcome, CollectedOutput, and the managed DSH_* environment namespace (DSH_ENV_PREFIX, DshEnvironment). The local implementation lives in dsh-subprocess-local.
Contract
spawn(spec)returns immediately with a live handle;doneresolves at process close with exit facts (SubprocessOutcomecarries no output and no cause classification) and rejects only for spawn-level failures.- The spec is fully explicit — argv, cwd, per-stream stdio dispositions, grace — because deployment-varying defaults belong to the calling seam's config, not to a hidden subprocess-service default (the
dsh-bashrequest/spec split is the owning template).argvis never shell-interpreted; a consumer that wants a shell passes['bash', '-c', command]itself. - Stdio is Node-shaped per stream:
'pipe'hands the caller the raw stream for its own protocol framing (LSP JSON-RPC, ACP ndjson),'inherit'passes the parent descriptor through for diagnostics, and collect mode ({ maxBytes, spill? }) buffers a bounded tail with an optional full-stream spill file. Collect readers take whole-stream byte offsets and never consume, so independent readers cannot steal one another's deltas; a read whose offset slid out of the in-memory tail islossyand points at the spill file when one exists. Collected output stays readable after settlement. - Termination is tree-scoped on every platform (POSIX detached groups with direct-child fallback; Windows
taskkill /T):kill(signal)sends one signal Node-style and is a no-op after settlement,terminate()(and the spec's abort signal) escalates SIGTERM→grace→SIGKILL, andwaitForExit(signal?)observes whole-tree liveness so a consumer-owned teardown ladder holds each tier on real quiescence — the manager reacts but never classifies why (callers own deadlines, teardown ladders, and cause classification). scrubbedParentEnv()/SENSITIVE_ENV_PATTERNare the one shared scrub definition: ambient credential-shaped andDSH_*names are dropped, explicitenvmerges after the scrub (a deliberately forwarded key survives), anddshEnvcarries current harness facts on its own validated channel;splitEnvChannels()partitions a consumer config's single mixed env map onto those two channels (lsp-local servers and the ACP backend expose one map, and a configuredDSH_*fact must ride the managed channel the ordinary one rejects). Spawners that cannot route through the service (node-pty backends, SDK-managed transports) import the scrub.- Disposal of the service terminates all still-running managed processes and awaits their exit.
See the subprocess data-structure catalog and the seam Agent Note.
Model Experience
Indirectly, through consumer seams (today the bash executor family behind dsh-tool-bash), which own all model-facing rendering of process output and lifecycle.
KV Cache effect
No direct invalidation; the named consumers own any request-prefix changes.
Known Limitations and Deferred Work
- node-pty and SDK-managed spawns share only the scrub — the PTY backend's terminal fork and the MCP SDK's own stdio transport cannot route their spawns through this seam (the library owns the fork/spawn call); they import
scrubbedParentEnvso the environment policy stays single-sourced. - Teardown ladders are consumer-owned — the seam ships signalling verbs and the tree-liveness wait, not a canned quiesce sequence; each out-of-process consumer encodes its child's cooperation shape itself (the ACP backend's stdin-EOF-first ladder is the in-repo template).