ByteNoteByteNote
DeepSeek Harness 怎么管住模型写的代码
字

字节笔记本

2026年10月6日 · 约 6 分钟读完

DeepSeek Harness 怎么管住模型写的代码

API中转
¥120

agent 干活时经常需要写代码:算一批数据、拼一段文本、连着调几次工具。问题是谁来执行这些代码。直接塞进主进程裸跑,一个死循环或者一次内存暴涨就能拖垮整个会话。DeepSeek 开源的 agent 框架 Harness 给了这个问题一个正式答案:一个独立的代码运行时子系统。

先交代背景。DeepSeek Harness(命令行工具叫 dsh)是 DeepSeek 在 GitHub 上开源的 agent 框架,MIT 协议,架构口号是"一切皆插件",底层构建在 Cordis 插件框架之上,装好 Node.js 之后一条 npx @deepseek-ai/dsh web 就能起一个带 Web 界面的 agent。本文读的是它的 code-runtime 子系统,接口定义在仓库的 packages/code-runtime/code-runtime 包里。

DeepSeek Harness 代码运行时的请求与结果数据流

代码执行是一项可选能力

Harness 把代码执行定位成一项可选能力,而不是 agent 主循环的必需件:不需要代码执行的部署可以完全不挂这个服务。它以 ctx.codeRuntime 的形式暴露,是一个抽象服务,本体只有三样东西:一个 run(request) 方法,加两个只读描述符。

两个描述符都值得单独说。language 声明程序必须用什么语言写,已知取值是 typescript 和 python,目前只有 TypeScript 有已发布的后端;负责生成代码相关界面的组件靠它切换展示,遇到展示不了的语言要显式报错,而不是硬着头皮渲染。isolation 声明执行基底,取值是 worker-thread、process 或 container。文档里特意强调了一句:这只是诊断标签,不构成安全承诺。这个坦白很重要,别看到 container 两个字就以为拿到了安全边界。

请求进,结果出

一次运行的输入被完整装进 CodeRunRequest:程序源码、一组宿主绑定、一个可选的 AbortSignal。程序按异步函数体执行,顶层可以直接 await 和 return,完成值就是结果里的 value。请求里没有任何可选的调参旋钮:时间预算、输出上限这些默认值全部来自实现侧经过校验的配置。文档把这叫"显式优于隐式",直白说就是不许 run() 内部藏一个 ?? 兜底,配置错了就在配置层报错。

输出端的设计更有意思:错误是结果上的一个字段,不是被拒绝的 Promise。程序抛错、解析失败,都照常返回一个带 error 字段的结果,run() 本身只在服务契约被误用时才会 reject。这和框架里 shell 执行器的约定一脉相承:报告一次失败的程序是调用方的职责,不该走异常路径。对上层来说,"模型写的代码跑挂了"就是一条普通数据,而不是一次需要 try/catch 的意外。

还有一个容易被忽略的细节:signal 触发时,运行时会硬停程序,哪怕正卡在循环中间;但已经在途的绑定调用,运行时只负责不再发起新的,等它们落地的责任在调用方。

宿主函数变成程序里的全局变量

绑定解决的是"程序里怎么调用宿主能力"。每个 CodeBindingNamespace 在程序内成为一个全局对象,Code Mode 传入的那个就叫 tools。规矩有几条:

参数和返回值必须是无损 JSON,运行时靠结构化克隆跨边界搬运,绑定结果不受接缝层字节上限约束。命名空间可以声明一个程序可见的错误类,运行时会把真正的构造函数注入程序,被拒绝的调用变成这个类的实例,程序能按类型接住错误,而运行时不需要知道任何具体业务的名字。

安全方面,绑定名被当作不可信输入处理:全局对象用空原型构造,__proto__ 只是一个普通自有属性,永远不会发生原型碰撞;全局名必须匹配各语言通用的标识符规则,并且不得撞任何语言的保留字,像 $tools 这种只在 JS 里合法的拼写会被设计层面直接拒绝;console、__dsh_main__ 这类后端自留的名字在任何后端都注册不进去。

六种失败,各说各的

失败分类是这份文档里最值得借鉴的部分。CodeRunFailure 把失败分成六种:

ts
'exception'      // 程序抛错,或解析、转换失败
'timeout'        // 实现持有的预算到期,消息里说明是哪个
'abort'          // 请求的 signal 触发
'worker-exit'    // 执行基底死亡且未结算,如 OOM
'invalid-output' // 完成值不是无损 JSON
'output-limit'   // 序列化后的日志或载荷超出上限

要点是这六种彼此正交、独立报告:预算耗尽不是异常,中止不是超时,基底崩溃也不是前两者中的任何一种。message 面向模型可读,可以直接喂回去让模型自我修正。日志则按发出顺序存成纯字符串数组,console 的通道和方法元数据不在接缝范围内;序列化后的日志数组和完成值载荷都有上限,超限就显式失败,不会往值里塞一段替代文本蒙混过关。

CodeRunFailure 的六种失败分类与两条硬规矩

隔离与收尾

实现必须保证各次运行彼此隔离,没有跨运行状态;资源释放时要等所有进行中的运行都终止并结算完毕,收尾才算完成。这两条加上前面的失败分类,构成把模型代码当不可信输入对待的完整闭环:进有格式约束,跑有预算和信号,出有上限和分类。

对想研究 agent 基础设施的人来说,这套接口是个不错的参考样本:它没有把"安全执行模型代码"吹成一个大词,而是拆成请求、结果、绑定、失败、隔离五个小契约,每个契约都写得能直接测试。源码和文档都在 GitHub 的 deepseek-ai/deepseek-harness 仓库里,MIT 协议,文末参考链接直达 code-runtime 的接口定义。

相关文章

分享: