
字节笔记本
2026年10月6日 · 约 5 分钟读完
deepseek-harness 的七条防御性编程模式
deepseek-harness 是 DeepSeek 在 GitHub 上以 MIT 协议开源的 Agent harness 项目。它的仓库文档里收录了一份不太起眼却相当硬核的 defensive-patterns(防御性模式):每一条模式都对应该项目实际发布或差点发布的一类缺陷,事后以规则的形式固化下来,防止同类问题复发。对于经常编写生命周期管理、并发控制、子进程调度或资源清理代码的开发者来说,这份清单值得逐条对照自查。本文整理其七个要点。

一、正交结果独立上报
一个结果可以同时具有多种性质:进程可能已经超时,却仍以退出码 0 结束,因为它捕获了终止信号。因此每个独立事实(timedOut、signal、exitCode)都应单独上报,切勿把一个标志的上报嵌套在另一个标志的分支里,否则调用方可能把提前终止的运行误判为正常成功。
二、公共约定两侧都要遵守
当一个实现收到同一结果的多种表示时,应在通过公共 API 返回前将其规范化。文档给出的例子是:LlmAdapter.stream() 的实现既可以抛出异常,也可以发出 kind 为 error 或 aborted 的 finish 分片,但 LlmRuntime.stream() 只会通过终止型 finish 分片暴露模型请求失败,middleware 缺陷与消费方缺陷仍以异常形式抛出。这样消费方就不必猜测捕获的异常究竟来自提供方、包装层、分片日志记录还是自身组装逻辑。规范化的约定要写在类型定义处,并通过真实消费方覆盖每一种来源形式。
三、异步状态不是同步状态
agent.followup() 没有逐消息的完成状态或结果;后台任务的完成与轮次边界存在竞争;reader.close() 在 EOF 和 dispose(资源释放)两种情况下都会触发。切勿把 agent/status 或 whenIdle() 当作某次 followup() 的结果:多条已排队的后续消息、steering(中途引导)和注入工作可能共用同一个 running 区间,而取消或资源释放可能丢弃尚未启动的项。真正拥有一次运行的自动化调用方必须显式定义其区间,例如从消息的持久 inbox 回执到整个 agent(智能体)下一次进入 idle,并把选取的输出描述为整个区间的输出,而不是把因果关系归于某条消息。这条守则是双向的:如果等待的转换永远不会发生,等待就会挂起,因此要显式处理无需等待的分支。
四、dispose 必须完全停稳,而不只是请求停止
如果清理流程只发出终止或中止信号便返回,而不等待工作真正停止,就会留下孤儿进程。清理逻辑应采用异步流程,发出终止信号后等待子进程真正退出(等待 done);还应在终止进程前关闭监听器注册表和通知注册表,让迟到的完成事件保持静默。
五、在分发器中隔离回调异常
用户提供的监听器如果抛出异常,不得导致它所在的 promise 被 reject,也不得饿死排在它后面的监听器。用 try/catch 包裹分发循环并记录日志即可:一个行为不当的订阅者,绝不能破坏核心生命周期。
六、不把环境变量或可预测路径暴露给不可信输出
启动的命令应使用经过清理的环境变量,移除名称匹配 *KEY*、*SECRET*、*TOKEN* 或 *PASSWORD* 的项,防止 harness 凭证通过命令输出、env 或 spill 文件泄漏。临时文件和 spill 文件应放在权限为 0700 的私有目录中,使用随机文件名,并以独占且仅所有者可访问的方式打开('wx'、0o600);可预测且全局可读的路径会引发符号链接竞态和信息泄露。
七、用 unlink 删除链接形态的路径
可能是符号链接或 Windows junction 的路径,应先用 lstatSync().isSymbolicLink() 判断,再用 unlinkSync 删除:unlink 只删除链接本身并拒绝真实目录,因此绝不会跟随链接进入其目标。Windows 上对 junction 调用 rmSync(link) 会抛出 ERR_FS_EISDIR,递归删除则可能穿过 junction 进入目标。只有确认是真实目录,才使用带 recursive 的 rmSync。

小结
这七条模式的共同思路,是把已经被咬过一口的教训写成可执行的检查项:上报维度是否正交、约定是否规范化、异步区间是否显式、清理是否真正停稳、回调是否隔离、凭证是否泄漏、删除是否安全。该项目在测试层面还有一套对应的配套规则,强调走真实入口路径、验证实际结果、明确资源归属。如果你维护的项目同样要管理长生命周期的 agent 进程与子进程,建议把这份清单纳入代码评审的核对项。



