学 AI 产品 · 专业 AI 产品经理播客第 4 章 · T4 解剖 DeepSeek Harness:一切皆插件的 Agent 底座 · EP 12
第 4 章 · EP 12

Code Mode:一段代码顶多轮工具调用

时长 15:54音色 云健 · 男声

同步字幕

章节导航(点击跳转)

0:00开场 · 官宣叫 PTC,源码叫 code mode1:34
1:34遥控器问题2:01
3:35一次往返1:18
4:54这个道理不新鲜1:31
6:25先当它是坏人1:44
8:10通信只认结构化消息1:51
10:02双预算与两头记账2:06
12:08权限一寸不松2:23
14:32可带走的原则1:21
解读全文

dsh12 · Code Mode:一段代码顶多轮工具调用

  • 模块:T4 解剖 DeepSeek Harness:一切皆插件的 Agent 底座
  • 集页:https://xueai-podcast.pages.dev/t/dsh12/
  • 取材课节:Code Mode:一段代码顶多轮工具调用(1 节,素材 4988 字)
  • 来源:xueai.miyang.cn(小山学堂 · 洛小山《学 AI 产品,从入门到精通》)
本页文字稿为音频的配套解读,内容取自小山学堂课程素材,属二次演绎版本。
行号与源码核对日期为 2026-08-13;演示中的轮数与 token 数为课程化估算,非精确基准。

一、本集要解决什么

两件事:为什么让模型写一段程序,比逐个发工具调用又快又省;以及模型写的代码在沙箱里跑,宿主凭什么自保。

贯穿两块的线索:第一块的本质是省往返,第二块的本质是不信任。二者是一枚硬币的两面——正因为要把一堆动作打包交给别人执行,才必须在放手前把防护做足。省往返能省到什么程度,取决于你敢放手到什么程度。

先把名字说清楚

名字出处说明
PTC(程序化工具调用)官方发布文;code/preset.yml 第 1 行 name: PTC 模式只在 UI 文案层
code mode配置项 mode: code、工具名 run_code源码内部命名,全仓库搜不到 PTC
跟人聊源码时说 code mode 才对得上号。

二、能力地图 · 两种模式对照

同一个任务:读 3 个日志文件 → 汇总统计 → 写 1 份报告。

维度传统工具调用Code Mode
采样轮数5 轮(每动作一轮)1 轮
上下文内容三份文件全文 + 报告只有程序 + 一次性返回值
吞吐(课程化估算)约 18.3k token约 2.6k token
中间结果去向全部躺进上下文留在沙箱变量里,只有 print / return 回模型

成本瓶颈在哪里

不在工具本身,而在"一步一采样"这个节奏。 每一轮采样模型都要把滚大的上下文重读一遍——文件越多,上下文越大,每轮代价越高,接近平方级增长,不是线性。

越到后面的轮次,模型为了决定一个越来越小的动作,要读的东西反而越来越多。

三、思路一 · 一次往返

一轮采样里,模型写一段 TypeScript 小程序,用 await tools.<name>(args) 想调几次调几次,循环、分支都行。程序在沙箱里把活干完,中间结果全留在沙箱变量里,跑完只把 print 与 return 的内容送回模型。

工具描述原话(packages/core/tools/src/code-mode.ts 第 52 行):

"Only what you print or return comes back — curate it."

一句话概括:中间值永不进上下文。

出处 · 设计意图注释

apps/cli/config/agent-presets/code/agent.cordis.yml 第 1–6 行:

The code agent preset: the standard coding agent, presented as Code Mode. Everything in standard is here unchanged. What is added is the tool-presentation row: instead of one tool call per action, the model writes a TypeScript program against a generated SDK and run_code executes it, so a sequence that would be five round trips becomes one.

伏笔(第 8–11 行):这个 preset 改的只是工具的呈现方式,工具注册表本身留在宿主手里——这正是思路二"权限跟随"的地基。

三层收益

  1. 省 token——N 次往返合成一次;
  2. 控制流表达更精确——循环就是循环、条件就是条件,语义不因上下文变长而模糊;
  3. 信息筛选权交还模型——模型自己写程序决定返回哪几个字段,等于加了一层裁剪能力。这层价值往往比省下的 token 还大。

为什么长期成立

网络编程几十年的老道理:往返贵,批处理便宜。数据库有批量写入,RPC 框架都在攒 batch。模型采样一轮远比一次网络往返贵,把 N 次往返合成一次收益只会更夸张。换语言重写这笔账照样成立——省的是往返次数本身,不是某个实现细节。


四、思路二 · 跑别人写的代码,先当它是坏人

前提问题:沙箱里跑的是模型现写的代码,没人审过一行。图省事在宿主进程里跑,翻车方式随便挑:

  • 环境变量里的 API key 随手读走;
  • 一个死循环把内存吃光,宿主跟着一起死;
  • 程序伪造消息,冒充工具结果骗过上层。
宿主必须从第一天就假设对面会使坏。这个假设立住之后,剩下的都是工程题。

五、三道防线

第一道 · 资源焊死

出处:packages/code-runtime/code-runtime-worker-thread/src/index.ts 第 378–387 行。

措施针对
每次运行新开全新 worker,用完即弃上次运行的痕迹不带到下次,攻击面不累积
环境变量清空最常见的一类泄露——凭据藏在环境变量里
继承的加载器标志掐断通过修改加载行为做手脚
堆上限焊死,爆了 worker 直接退出资源耗尽;在这一层就终止,不抛可被捕获的异常
到了内存吃满这一步,程序已经不可信了——给它一次捕获异常的机会,就是给它一次挣扎的机会。

第二道 · 通信只认结构化消息

出处:同文件第 142–165 行;bootstrap.ts 第 324–326 行。

  • 宿主与 worker 之间只有一条消息端口,不共享内存;
  • 上行消息只有四种:call / log / output-limit / done;
  • 每条入站消息先验形状,再逐字段重建一份干净的,垃圾静默丢弃;
  • worker 侧的 tools 命名空间用 null-prototype 构建,伪造原型名摸不到任何东西。

两个关键词:

  • 先验形状——不是验证完就用原始对象,而是照白名单重新造一个。多余属性、原型链上的东西都进不到宿主世界。这比"检查通过就放行"稳得多——后者总有你没想到要检查的字段。
  • 窄接口——种类越少,要验证的形状越少,出错面越小。你能验证的东西越少,验证得就越彻底。
若允许共享内存,程序可直接摸宿主地址空间,验证消息形状就完全没意义了。

第三道 · 双预算与两头记账

出处:同文件第 534–545 行(时间)、第 169–229 行(字节)。

账本类型作用
computeMsCPU 忙碌时间(轮询实测)热循环藏不住;干等慢工具不冤枉计费
maxWallMs墙上时间兜底不管忙不忙,到点就终止
输出账本字节worker 发送前自预检,宿主收到后再记一遍

为什么要两本时间账:只有墙钟 → 干等慢工具被冤枉;只有 CPU 时间 → 一个死等不消耗的调用就能无限挂住。两本一起上才既不错杀也不放过。

凡是让不可信的一方自己上报消耗,都要在可信侧再记一遍。自报的数字只能当参考,不能当事实。
自报本身就是一个可被攻击的接口——程序可以先干一堆消耗再只报一个小数;只要有一本账捏在自己手里,美化就没用。计费、配额、限流系统的铁律:账本必须在你信任的那一侧。

轮询的意义:边跑边量才能抓住"跑了很久但中途才开始作恶"的情况;跑完再量只能得到一个总数,分不清时间花在哪。


六、权限一寸不松

出处:packages/core/tools/src/code-mode.ts 第 545、477、470 行;第 601–608 行。

  • 每次 await tools.xxx 被宿主包装成子调度,带父调用 token,走和原生模式同一条 pre-execute 审批瀑布;
  • 子调用 id 形如 callId:code:n,事件里全程留痕;
  • 程序能绑到的工具正好是系统提示词里声明过的,受限工具在名单里直接消失;
  • 反方向也堵死:mode: code 下想绕开 run_code 直发原生调用,进策略管线之前就被拒为 UNKNOWN_TOOL。
入口收窄了,但权限没换门。

官方定位(很重要)

packages/code-runtime/code-runtime-worker-thread/README.zh.md 开门见山:

这是隔离措施,而非安全边界:其信任立场有意与 bash 等价……但提供 bash 没有的隔离:独立 isolate、空环境、堆上限与强制终止。

展开:很多人一听到沙箱就默认它是牢不可破的墙。这里的说法恰恰相反——信任立场有意与 bash 等价。别因为代码跑在沙箱里就降低警惕,该走的审批一条都不能少。

含义
隔离出事时把损失圈住
安全边界保证不出事

两者的区别,决定了你会不会在沙箱外面再补一层防线。


七、横向对比

产品有无等价机制说明
Claude Code无bash 是通用逃生舱,模型可写脚本再执行,但 Read / Edit 等工具 API 不作为可编程绑定暴露给脚本,脚本内部动作也不走各工具自己的管线。Anthropic 官方博客《Code execution with MCP》提出同思路,截至核对日期未见产品内置同类 run_code
Grok Build无全库检索 run_code 与 code mode,只有遥测事件名字面撞词。工具体系走原生调用加 toolset preset 组合
Codex CLI / Cloudflare思路相通DSH 设计笔记明确引用 Cloudflare 博客,核心观察是模型写代码的能力好于连发工具调用(.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md)。本课未核对这两家源码

八、审查清单

评估"让模型写程序去编排工具"这类设计时:

  1. N 次往返是否已合成一次?成本瓶颈是否定位在"一步一采样"而非工具本身?
  2. 中间结果是否留在执行环境里,而非滚进上下文?
  3. 是否新开干净环境、用完即弃?资源是否从启动参数层就焊死?
  4. 环境变量是否清空?堆上限是否焊死并在该层直接终止?
  5. 通信是否只有一条端口、不共享内存?消息种类是否限死?
  6. 是否先验形状再逐字段重建,而非验证通过就用原对象?
  7. 命名空间是否用 null-prototype 构建?
  8. 时间是否双账本(CPU 忙碌 + 墙上兜底)且轮询实测?
  9. 字节是否两头记账(worker 预检 + 宿主复记)?
  10. 子调用是否照走同一条审批瀑布并全程留痕?是否堵死绕过通道?
  11. 是否明确写下"这是隔离而非安全边界"?

九、约束说明

  • 行号口径:基于 2026-08-13 对 deepseek-harness-master 本地仓库核对。
  • 演示口径:轮数与 token 数(5 轮 vs 1 轮、18.3k vs 2.6k)为课程化估算,用于展示结构差异,非精确基准;右侧程序为 5 行课程示例,非源码引用。
  • 证据边界:关于 CC 与 Grok 无等价机制,基于已公开还原源码/本地仓库检索;Codex CLI 与 Cloudflare 本课未核对源码,仅引公开叙述。
  • 本页用途:文字稿仅供阅读,音频以集页播放器为准;题目页内容不进入音频。

十、实践提示

提示一 · 往返贵,批处理便宜

凡模型要连续做一串动作,先问:能不能合成一次?省的不只是 token,还有上下文变长带来的每一轮代价。

提示二 · 中间值不要进上下文

让执行环境替你拿着中间结果,只把结论送回来。上下文不是日志,别什么都往里塞。 更关键的是,把"什么值得进上下文"的决定权交还给程序。

提示三 · 新开干净环境,用完即弃

不复用就不积累攻击面。资源从启动参数那一层就开始焊死——别指望代码自觉。到资源耗尽那一步,直接终止,不要给捕获异常的机会。

提示四 · 窄接口 + 先验证形状再逐字段重建

窄接口本身就是防御,重建比验证更可靠。你能验证的东西越少,验证得就越彻底。

提示五 · 两头记账,自报不作数

凡涉及计费、配额、限流,账本必须在你信任的那一侧,且要边跑边量而非事后统计。


十一、课堂练习(附推导)

程序 A 是同步热循环 while (true) {},程序 B 是 await new Promise(() => {})(永不 resolve)。A 和 B 分别被 computeMs 还是 maxWallMs 终止?为什么 A 不能靠挂一个待完成的工具调用躲过计费?
  1. 程序 A(同步热循环):持续占用 CPU,computeMs 轮询到的忙碌时间不断累加 → 被 computeMs 终止。它藏不住,因为它在真跑 CPU。
  2. 程序 B(死等 Promise):几乎不消耗 CPU,computeMs 增长极慢 → 只能靠 maxWallMs 墙上时间兜底终止。这正是"只有 CPU 时间账就会被无限挂住"的反例。
  3. A 能否靠挂一个待完成的工具调用躲过计费:不能。因为 computeMs 是轮询 worker 实测的忙碌时间,热循环期间 CPU 一直忙,时间照记;而待完成的工具调用属于"干等",本来就不计入 CPU 忙碌时间——两者不冲突,忙的时间该记还是记。

十二、Takeaway

  • Code Mode(官宣名 PTC)用一轮采样换掉 N 轮往返:模型写程序,沙箱替它跑,中间值永不进上下文;
  • 省的是往返次数本身:往返贵,批处理便宜;附带收益是控制流更精确、信息筛选权交还模型;
  • 沙箱是隔离,不是安全边界,信任立场有意与 bash 等价——靠空环境、堆上限、双预算、两头记账自保;
  • 三道防线:资源焊死(新开即用弃)、通信只认结构化消息(单端口 + 四消息 + 逐字段重建)、双预算两头记账;
  • 权限不因进沙箱而松动:每个子调用照走同一条审批瀑布,绕过通道被拒为 UNKNOWN_TOOL。

来源:xueai.miyang.cn(小山学堂 · 洛小山《学 AI 产品,从入门到精通》)

本内容改编自小山学堂课程素材,为二次演绎版本。