从0打造编码Agent 岔路 阅读 + 实验 ≈ 20 分钟 页内实验 ×1
A2 工具系统:模型怎么说清「改哪里」? 岔路课

让模型写代码来调工具,行不行?

Code Mode
Codex 的 exec / wait 和 OpenCode 的 codemode 让模型写一段脚本、在脚本里连续调工具,把好几次「模型 → 工具 → 模型」压成一次。在真实 trace 上自己勾选哪些轮次能合并,现算省掉的调用、token 和时间:省的是往返,不省判断;脚本被关在解释器里,脚本外的 bash 不受它管。
❓

本课一问:每一次工具调用都要让模型回一次:模型写调用 → harness 执行 → 结果放进下一轮请求 → 模型再写。能不能让模型一次写一段脚本,在脚本里连着调好几个工具?

这一课先不碰:脚本的运行环境怎么隔离到操作系统层(A4 沙箱);脚本调用的每个工具该不该放行(A3)。

从已有出发

第 4 课算过工具定义的账,岔路课 B1 回放过往返。把两者合起来看贯穿任务的真实运行 pd-truncate-s1:7 次模型调用,全程 21,112 个 token,模型生成一共用了约 251 秒。每一次往返,都要把越来越长的历史重发一遍。

从已有出发 · 累计数据流 点已学环节看它回答什么
每一轮循环都经过

先自己设计:每一步都回模型

现在的做法就是朴素方案:模型每调一次工具,都要等结果回来、再发一次完整请求,才能决定下一步。

  1. ① 朴素方案
  2. ② 它在哪失败
  3. ③ 改进方案
朴素方案每调一个工具,都回到模型决定下一步

在下面的列表里,每一行是一次真实的模型调用和它发起的工具。

如果改成 Code Mode:哪些往返能省

数据:本课项目 miniagent 的真实运行 pd-truncate-s1(A5 预算 2400 token,朴素截断(seed 1),2026-09-26;模型 Qwen3-8B-MLX-4bit,本机 mlx_lm.server)。token 数用该模型的 tokenizer 与 chat template 重算,逐轮与服务端 usage.prompt_tokens 核对一致;「服务端缓存」是 mlx_lm.server 自报的 cached_tokens,它的前缀缓存跨请求保留,读数请看项目 README 的说明。

🎯

试三件事:① 只勾第 1、2 轮之间和第 2、3 轮之间:模型调用从 7 次变 5 次,省下的 token 就是第 2、3 轮的真实用量;② 再试着勾第 4、5 轮之间:第 4 轮的 edit 报了「2 处」,模型要看到这句报错才知道第 5 轮该怎么改——合并就意味着它得事先写好「报错了怎么办」;③ 全部勾上,看数字上限,再想想这是不是一个模型写得出来的脚本。

改进:两家的实现

Codex 给模型的是两个工具:exec 与 wait,名字在 code-mode-protocol/src/lib.rs:49-50;脚本跑在内嵌的 V8 里,code-mode-runtime/Cargo.toml:24 引入 v8 时打开了 v8_enable_sandbox 特性。

OpenCode 的 packages/codemode 自己写了一个树遍历解释器,只执行一个 JavaScript 子集。它的设计文档把边界写得很清楚:脚本只能调用 host 提供的工具,没有文件系统、进程、网络等「环境权限」;不支持 import、eval。

⚠
常见误解:「Code Mode 更安全,因为代码被关起来了。」

被关起来的是脚本本身:它不能自己读文件、开进程、上网。可脚本调用的工具照样有全部能力。OpenCode 的设计文档原话是「CodeMode can only confine programs to supplied tools. The host chooses those tools, and each tool enforces its own authorization and side-effect policy.」——授权还得靠每个工具自己。OpenCode 的 bash 仍是脚本之外的直接工具,而 OpenCode 没有操作系统级沙箱(A4 会讲)。Code Mode 改变的是「往返的次数」,不是「能做什么」。

🔍 Anthropic 的「150,000 → 2,000」说的是什么?

Anthropic 工程博客《Code execution with MCP》(2025-11-04)描述的场景是:工具定义很多、中间结果很大(比如整份文档),全部经过模型上下文时要 150,000 token;改成让模型写代码调 MCP 工具、中间结果留在执行环境里、只把需要的部分交回模型,降到 2,000,「a time and cost saving of 98.7%」。那是博客里的示例场景,不是一个评测;本页的数字则来自我们自己的 trace,规模小得多,但每一个都是真实用量。

证据三问

问本课的回答
① 它解决什么问题?往返次数多、每次都重发全部历史;中间结果不必都进上下文
② 为什么可能有效?合并掉的模型调用,其输入输出 token 与生成时间就不再发生
③ 实验上呢?Anthropic 的 98.7% 是博客示例(官方自报) ;Code Mode 对任务成功率的影响,本轮没有找到公开对照数据

演进地图:你在这里

模型怎么说清「改哪里」:每一代都被上一代的某个真数字逼出来
  1. 朴素 按行号改 「把第 a–b 行换成……」
    解决了模型不用复述原文,只报行号,输出最短。
    卡在行号是「上一次读文件时」的快照:第一处修复只要多插 2 行,第二次按旧行号 41 改,就落在 if m: 上(第 1 课现算);CoreCoder README 的原话是「Line numbers are a trap」。
  2. 第一代 整文件覆写 write_file;aider 的 whole 格式
    解决了不需要定位,格式最不容易错:aider 同一个 Qwen2.5-Coder-32B,whole 格式合规 99.6%,diff 格式只有 71.6%。
    卡在只改 1 行,也要把 44 行的文件整个再写一遍:参数 487 token,片段替换只要 54(9.0 倍,项目模型 tokenizer 实数);还会覆盖别人在此期间的改动。
  3. 第二代 唯一片段替换(search-replace) CoreCoder、本课项目 miniagent
    解决了按内容定位,不怕行号过期;0 处或多处都报错、文件不动,让模型自己补上下文。
    卡在真实 trace pd-truncate-s1:第 4 轮 old_string 在文件里 2 处、第 5 轮缩进多 4 格 0 处,两次报错多跑了两轮,花掉 8,481 token、109.5 秒;唯一也不等于对:pd-v2-unlimited-s1 第 7 轮的 old_string 恰好是已修好那一行的前半截,计数 = 1,于是 + 1 变成了 + 1 + 1。
  4. 解决了模型复述时的小漂移不再白费一轮:上面第 5 轮那条缩进多 4 格的 edit,OpenCode 直接接住(LineTrimmed 级)。
    卡在接住 ≠ 改对:多转义一层的引号,OpenCode 的 EscapeNormalized 级照样接住,写回去的是 \",模块导入即 SyntaxError;OpenCode 的 v2(packages/core)眼下只有精确匹配,「edit fuzzy parity」还列在待移植清单里。
  5. 第四代 语法约束解码的 patch Codex apply_patch(19 行 Lark 语法);strict 工具
    解决了语法由解码器保证,不再靠 prompt 教。对照:靠 prompt 教格式时,aider 榜上 47 个 diff 格式条目的「格式正确」比例最低只有 64.4%(官方自报)。
    卡在语法只管格式,定位仍是 seek_sequence:它不检查唯一、取第一处——两处相同的 return datetime.date(year, month, day) 会改到第 31 行 ISO 分支,test_parse_iso 跟着坏;整行比较还会把没写缩进的 new 行原样写进去,得到 IndentationError。
  6. 工具从哪来 工具集随模型变,外部工具经 MCP 接入 OpenCode registry.ts;MCP 2026-07-28
    解决了一套协议接任意外部工具;同一个 harness 给 GPT 换上 apply_patch、给 Claude 用 edit。
    卡在接进来的每个工具每轮都要付上下文:chrome-devtools-mcp 的 30 个工具在项目模型的 chat template 里占 7,047 token,是 miniagent 自己 6 个工具(685)的 10.3 倍。
  7. 岔路 Code Mode:让模型写脚本调工具 Codex exec / wait;OpenCode codemode 你在这里
    解决了N 次「模型 → 工具 → 模型」往返压成一段脚本。
    卡在省的是往返,不省判断:真实 trace 里模型每一步都要看上一步的结果才知道下一步做什么(岔路课逐轮算)。
数字口径:token 数用项目录制模型 Qwen3-8B 的 tokenizer 与 chat template 算(与服务端 usage 逐轮核对一致);裁决与测试结果是原仓库代码实跑(a02-edit-fixtures.json,2026-09-26);trace 来自本课项目 miniagent 的真实运行(2026-09-26);aider 数字来自 aider 仓库的 polyglot_leaderboard.yml(官方自报);MCP 工具数来自本机实跑 tools/list。

一句话

📌
Code Mode 省的是往返次数,不省判断,也不自动更安全。
🚦

先停在这里:回主线去 A3。
再深一层:在列表里找出「必须看到结果才能决定下一步」的那几轮,数一数:它们就是 Code Mode 在这个任务上省不掉的下限。

下一个缺口

回主线:工具能执行 rm -rf,谁来拦? → A3 第 1 课

你可能会问