Files
deepseek-harness/packages/subprocess/subprocess-local/README.md
Tianyi Cui fc566119a7 refactor(subprocess): rename the process seam to subprocess and address review
Review feedback (tianyicui): 'process' is a poor service name. The family is
now packages/subprocess/ — @deepseek-ai/dsh-subprocess (ctx.subprocess,
abstract SubprocessService, Subprocess* vocabulary) and
@deepseek-ai/dsh-subprocess-local (LocalSubprocessService) — renamed
throughout code, compositions, docs (en+zh, pairs re-recorded), catalogs,
and gates. 'subprocess' is the precise term for managed OS children (the
Python-stdlib sense), avoids colliding with Node's global process object,
and reads as one system beside dsh-subagent-subprocess.

ds-review-bot findings addressed:
- kill() on a settled handle is now a no-op (no signal to a possibly-reused
  pgid, no referenced grace timer delaying exit); pinned by a spy test.
- The moved DshEnvironmentKey/DshEnvironment/CollectedOutput types get
  drift-checked type-equiv blocks on the new subprocess.md page, restoring
  their manifest registration.
- subprocess.md is registered in the core.md sub-page index (en+zh).
2026-07-26 12:43:59 +08:00

3.7 KiB

@deepseek-ai/dsh-subprocess-local

Local implementation of the @deepseek-ai/dsh-subprocess seam: LocalSubprocessService spawns each spec's argv as a detached process group, collects bounded output with size-limited full-stream spill files, and escalates kills SIGTERM→SIGKILL across the whole group. It has no config: every limit and directory arrives on the spawn spec, so the deployment-varying knobs stay with the calling seam's config (dsh-bash-local today).

Behavior (and where it came from)

  • Detached process groups with escalation — children are spawned detached (own process group); kills send SIGTERM to the group, then SIGKILL after the spec's grace (OpenCode's escalation; pipelines and subshells die with the parent). After the leader exits, inherited stdout/stderr pipes receive the same bounded drain grace so a surviving descendant cannot hold the spawn open indefinitely. ESRCH is tolerated; daemons that re-parent away from the group can still survive — the same caveat as the surveyed tools.
  • Tail-keep truncation + bounded spill files — output beyond a stream's cap keeps the in-memory TAIL (errors and results cluster at the end — pi/OpenCode rationale) while the FULL stream is appended to a private temp file whose path is reported when available. A stream larger than the spill cap discards its now-incomplete spill and returns only the marked truncated tail; a failed final close withholds the path rather than advertising an incomplete file. Spill files are 0600 with random names under a lazily-created 0700 per-process directory.
  • Credential scrub + managed DSH_* mergeprocess.env minus credential-shaped vars (*KEY*/*SECRET*/*TOKEN*) and all ambient DSH_* names; a spec's ordinary env merges after the scrub but rejects DSH_*; managed dshEnv rejects ordinary names and merges last, preventing stale nested-harness identity. Supplied stdin is written and closed; otherwise fd 0 is /dev/null. See the stdin/env Agent Note and managed environment Agent Note.
  • Offset-based readsSubprocessHandle readers return deltas in whole-stream byte coordinates; the service never holds a cursor, so consumer-owned cursors (the bash background read path) and full-stream re-reads coexist.
  • Kill-and-join disposal — the service retains live handles only so its own disposal can kill every running group and await its exit; settled and spawn-failed handles leave the live set on settlement.

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

  • POSIX-only — detached process groups, group kills, and SIGTERM→SIGKILL escalation are hardcoded; Windows is unsupported.
  • The credential scrub is a name heuristic*KEY*/*SECRET*/*TOKEN* only; differently-named secrets (e.g. *PASSWORD*) pass through, and a whitelist for over-scrubbed vars is noted future work.
  • Completed spill files are not deleted — bounded full-output recovery files (and the private per-process spill dir) accumulate under the OS tmpdir until something external cleans them; oversize incomplete spills are discarded and deletion is attempted immediately, but a cleanup failure can leave a bounded file behind.

The raw process handling lives in src/spawn.ts; src/index.ts is the service wiring.