
字节笔记本
2026年10月6日 · 约 6 分钟读完
DeepSeek Harness 子进程层设计拆解
子进程是 agent 框架里最容易被低估的部分。模型要跑 shell 命令、要连语言服务器、要开伪终端、要派生子代理,背后全是同一件事:生一个进程,收它的输出,最后干净地送走它。DeepSeek 近期开源的智能体框架 DeepSeek Harness 以一切皆插件的架构著称,底层由 Cordis 框架驱动,它把这件事收敛成一条独立的子进程接缝。本文基于其官方文档拆解这份设计。
一条接缝,两层拆分
子进程接缝拆成两层。Service Definition(dsh-subprocess 包)只声明契约,以 ctx.subprocess 服务挂出三个原语:可执行文件解析 resolveExecutable、普通进程 spawn、终端进程 spawnTerminal;Service Provider(dsh-subprocess-local 包)给出本地实现,负责进程树脱离、逐流接线、凭据清洗、基于 node-pty 的终端分配,以及销毁时的终止加等待收尾。契约与实现分离,换一套远端实现不用动消费方。
消费方按需选处置:bash 执行器用收集模式拿有界批量输出;LSP 客户端用原始管道自己做 JSON-RPC 分帧;PTY 后端用终端原语;ACP 子代理后端用管道传 ndjson,同时让 stderr 直通继承。shell 包重导出这套词汇,命令执行相关的消费方保持单一导入入口。

可执行文件查找与环境命名空间
resolveExecutable 只认两种输入:绝对路径直接验证;裸名称走提供方清洗后的 PATH 加显式覆盖来解析;带路径分隔符的相对路径会被直接拒绝,因为解析基准未定义,实现宁可报错也不猜。由此子进程的工作目录、可执行文件与挂载文件系统处在同一个执行世界,不存在两套路径体系。
环境变量方面,DSH_* 前缀的变量归 Harness 所有。实现先把环境里已有的 DSH_ 条目全部丢弃,再合并调用方显式传入的 env,所以一份当前事实只能以调用方有意写下的字符串条目到达子进程;显式的 undefined 则是墓碑,用来删掉普通的环境条目。父环境的共享凭据清洗也集中在这一层,凭据形状的条目默认不会传给孩子,只有调用方以字符串显式写进规格的才会存活。
每条流单独处置,规格全显式
spawn 的输入是一个完全显式的规格对象:argv、工作目录、每条流的处置、宽限期 graceMs、中止信号、环境覆盖,一项不缺,也没有任何隐藏默认值。行为由调用方自己的配置决定,而不是被子进程服务的默认值绑架;argv 永远不会经过 shell 解释。
stdin 有三种处置:ignore 落到 /dev/null;pipe 暴露可持续写入的流,适合协议交互;{ data } 把字节写完就关,是典型的批处理形态。stdout 和 stderr 同样三选:pipe 把原始流交给调用方做协议解码;inherit 直通父进程的诊断流;collect 做有界收集。collect 在内存里设 maxBytes 上限,溢出保留尾部;可选的 spill 文件保存全量流,自身也带上限,超出即整体丢弃。省略 spill 就是诊断尾形态,比如语言服务器的 stderr 只在内存里留个尾巴、不留文件;带上 spill 就是全量可恢复形态,bash 工具走这条路。
interface CollectedOutput {
text: string // 截断时保留的是流的尾部
truncated: boolean // text 是否丢过字节
spillPath?: string // 截断且可用时,存完整流的文件路径
}
句柄、读取器与整树终止
spawn 立即返回一个活句柄。done 承诺在进程关闭时兑现,只带 Node close 事件的词汇:退出码与终止信号,刻意不做超时或取消的归因。道理很直接:期限归调用方所有,谁触发的中止谁自己分类,接缝只负责杀,不负责解释死因。
收集模式的读取器按整流字节偏移读取、绝不消费,多个独立读取器互不偷走对方的增量;偏移滑出内存尾窗时读取报 lossy,缺口只能从 spill 文件找回。进程退出后收集输出依然可读,批处理和流式调用方共享同一条访问路径。
terminate 是唯一的终止动词:SIGTERM 起步,等 graceMs 宽限,再升级 SIGKILL;规格上的中止信号触发同一阶梯。POSIX 上对脱离的进程组发信号,组不存在时退回直接子进程;Windows 走 taskkill /T。terminate 幂等,进程树没了就是空操作,不怕 pid 复用。waitForExit 观察的是整棵进程树而不是直接子进程,还活着的辅助进程在拆除返回前就能被观察到,消费方可以据此搭自己的逐级拆除流程,比如先关 stdin 等对端收尾再逐层降级。服务销毁时会终止所有仍在运行的受管进程并逐一等待,不留孤儿。
终端原语:普通 spawn 补不出来的部分
spawnTerminal 是接缝里唯一的非管道原语。提供方分配真实的控制终端,负责 UTF-8 文本传输、前台进程组检查与发信,以及一次等待全部会话成员静默的终止操作;提示符检测、就绪推断、回滚、沙箱策略和持久会话的所有权仍留在 PTY 消费方手里。这些控制终端语义靠普通 spawn 重建不出来,所以接口把它单列。
值得带走的四个决定
一是规格全显式、无默认值,把配置决定权还给调用方;二是非消费式的偏移读取,多读取器共存而互不干扰;三是退出事实与原因归因分离,谁拥有期限谁分类;四是凭据清洗与 DSH_* 命名空间集中治理,环境变量不再是父进程的无脑继承。对想在自己的 agent 里管好进程的开发者,这份契约本身就是一张现成的检查清单。



