从0打造编码Agent 阅读 + 实验 ≈ 35 分钟 页内实验 ×4
A0 工作流还是 Agent:什么时候需要 agent,什么时候一段代码就够? 第 3 课

把「下一步做什么」交给模型,要付什么代价?

交出决定权的代价
既然 agent 更灵活,那所有任务都用 agent?T2 给出三笔账:在 workflow 已经修好的两个任务上,agent 花了 5.3 倍和 4.5 倍的 token,其中一个反而没修好;同样的配置、同样的 seed 重跑,4 个任务里有 2 个在中途分叉——只因为测试输出里差了几个 token;轮数与 token 事先说不准。于是有了先判别再选轨道的三条标准,以及官方那条「先直接调 API」的建议——它是经验之谈,不是实验结论。
❓

本课一问:第 2 课说,agent 的路在运行时长出来,所以它修好了 workflow 修不好的 parse_date。那干脆所有任务都用 agent?把「下一步做什么」交给模型,要付什么代价?

这一课先不碰:循环怎么写、什么时候停(A1);token 换成钱、缓存能省多少(A8);「好不好」怎么系统地评测(A9)。

从已有出发

第 2 课的结论:workflow 的边在设计时画好,agent 的边在运行时长出来。T2 的比分也是这样:workflow 2 / 4,agent 3 / 4。

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

贯穿任务 parse_date 仍在表里,但这一课要看的是另外三个任务:它们让「一律用 agent」的账算得更清楚。

先自己设计:一律用 agent

最省心的策略:既然 agent 能走出 workflow 走不出的路,那所有任务都交给 agent,反正它自己会看着办。

  1. ① 朴素方案
  2. ② 它在哪失败
  3. ③ 改进方案
朴素方案所有任务一律用 agent
同一个任务,两条真实轨道并排 代码决定模型决定

数据:本课项目 T2 双轨录制(agent-t2-workflow.json,2026-09-26;Qwen/Qwen3-8B-MLX-4bit,本机 mlx_lm.server;两条轨道同为 seed 1、同一沙箱;workflow 单次调用 max_tokens 8192,agent 每轮 4096、上限 30 轮)。每条轨道每个任务只跑 1 次:只能当例子,不能当比率。token 与轮数可比;耗时受本机同时运行的其他任务影响,只能粗看。本地模型没有账单,成本以 token 计;按价格换算留到 A8。

🎯

试三件事:① 在 word_freq 上展开 agent 轨第 3、4、6 轮:都是思考写满上限、这一轮什么也没做;第 5 轮说要调工具却没给出调用;② 切到 leap_year:两条轨道都修好了,看底部那一行的倍数;③ 切到 slugify:agent 修好了,可最后 7 轮全是被截断的思考,一共 28,672 token 没产出任何动作。

改进:先判别,再选轨道

项目在录制之前写下了三条判别标准,录制之后不改(04-实践项目/MILESTONES.md · A0 · M0):

  1. 修复需要的信息是否全在「报错 + 被测源文件」里?是 → workflow 够。
  2. 下一步是否取决于运行时才出现的输出(修完一处才暴露下一处、要装的依赖名只在报错里)?是 → 需要 agent。
  3. 改动范围能否预先确定(文件确定、整文件覆写可行)?能 → workflow 够。

拿它逐个预测,再和 T2 对照:

任务录制前的预测workflow(token)agent(轮 · token)够用的最简单轨道预测
parse_date需要 agent✗ · 3,081✓ · 8 · 21,687需要 agent对
slugifyworkflow 够✗ · 3,727✓ · 13 · 54,301需要 agent错
word_freqworkflow 够✓ · 4,290✗ · 6 · 22,852workflow 够对
leap_yearworkflow 够✓ · 2,020✓ · 5 · 9,091workflow 够对

预测对 3 / 4。错的那个 slugify 很有意思:按三条标准它本该 workflow 够(规格写在 docstring 里、只改一个文件),workflow 也确实答对了修法——输掉的是程序自己的写回规则(第 1 课)。所以这次预测错,错在标准没覆盖「workflow 的实现本身有漏洞」,不是任务需要 agent。修好写回规则,再录一次,才能知道标准本身准不准。

两家官方给的判断方向和这三条是一致的。Anthropic:agent 适合说不准要几步、没法写死路径的开放式问题,而且每一步都要能从环境拿到真实反馈(工具结果、代码执行);代价是更高的成本和可能累积的错误,所以要在沙箱里充分测试 。OpenAI:优先考虑需要复杂判断、规则多到难以维护、严重依赖非结构化数据的流程,否则一个确定性的方案可能就够了 。编码任务正好有一样别的领域少有的东西:测试,每一轮都能拿到客观的真实反馈——这是编码 agent 能成立的前提,也是第 2 条标准里「运行时的输出」的来源。

还有一条常被引用的建议:先直接调模型 API,很多模式几行代码就能写;用框架就要懂它底下在做什么。这是 Anthropic 那篇文章的原意(原文用的是 suggest),依据是他们与几十个团队合作的经验。它正是这门课「从 0 写 harness」的出发点,但要注意它的证据等级——见下面的误解。

极小真数字:agent 的 token 花在哪

parse_date 的 agent 轨一共 21,687 token,是 workflow 轨(3,081)的 ×7.0。拆开看:

parse_date · agent 轨 8 次请求的 token(服务端 usage 逐轮相加)
= +
👆 公式里带下划线的每一项都可以点——点哪项,讲哪项。
对照 workflow 轨:3,081 = 782 + 2,299(一次请求)。agent 这 13,572 个 prompt token 里,服务端报告有 11,169 个命中了前缀缓存——算得更快,但请求里照样是整份;缓存能省多少钱,是 A8 的事。

孪生运行:同样的配置、同样的 seed

A1 录过一批运行,其中有 4 次与 T2 的 agent 轨配置逐键相同、seed 都是 1。按道理应该一模一样。实际上:

同配置、同 seed 的两次 agent 运行 · 按轮对齐 代码决定模型决定

数据:本课项目 T2 双轨录制(agent-t2-workflow.json,2026-09-26;Qwen/Qwen3-8B-MLX-4bit,本机 mlx_lm.server;两条轨道同为 seed 1、同一沙箱;workflow 单次调用 max_tokens 8192,agent 每轮 4096、上限 30 轮)。每条轨道每个任务只跑 1 次:只能当例子,不能当比率。token 与轮数可比;耗时受本机同时运行的其他任务影响,只能粗看。本地模型没有账单,成本以 token 计;按价格换算留到 A8。

🎯

试三件事:① parse_date:前两轮逐条相同(灰),第 3 轮的请求差了 3 个 token——上一轮的测试输出里,traceback 的路径含着工作区目录名,两次运行的目录名不同;之后两条路越走越远,一次通过、一次重复修改同一处而没过;② 切到 slugify:第 8 轮分叉,测试输出除了目录名,还差了 0.000s 与 0.001s,最后一个 13 轮、一个 10 轮;③ 切到 word_freq 与 leap_year:逐条消息完全相同——环境输出一个字节都没差,就能复现。

采样带 seed,可 seed 只固定了「同一份输入 → 同一个随机序列」;环境的输出(路径、耗时、时间戳)只要差一个 token,输入就变了,之后的每一次采样都跟着变。再看同一配置只换 seed 的三次:

同一配置只换 seed(1 / 2 / 3):三条路、三个轮数 请求模型回复工具调用工具结果harness 注入

数据:本课项目 miniagent 的真实运行(src/data/project/agent-traces.json,2026-09-26 导出;模型 Qwen/Qwen3-8B-MLX-4bit,本机 mlx_lm.server)。token 数取自服务端 usage;思考截取约 600 字、正文与工具输出约 900 字(保留头尾)。耗时受本机同时运行的其他任务影响,只能粗看;token 与轮数才可比。

三次运行 8 / 6 / 7 轮、25,714 / 16,971 / 16,905 token、✗ / ✓ / ✓。workflow 轨的形状则永远是 5 步、1 次模型调用——它的输出内容也是采样出来的,但下一步是什么,事先就知道。

⚠

常见误解一:「Anthropic 官方明确反对用框架。」 原文的意思是建议开发者先直接调 API,用框架要懂它底下的代码;依据是与几十个团队合作的经验,没有方法、样本和数据。深度调研对这一条的投票是 2-1,不是满票 。它可以当作这门课的出发点,不能当作实测结论。另外,这篇文章的页首现在多了一段说明:工具生态自 2024 年 12 月以来已经大变,并指向 Anthropic 2026 年的 Managed Agents 文章——建议本身没被撤回,但文中点名的框架清单不该再当成现状。

常见误解二:「seed 一样,运行就能复现。」 上面的孪生运行:同配置、同 seed,2 / 4 个任务在中途分叉。seed 只管模型这一侧;agent 的输入里有环境的输出,环境一变,整条路就变。要复现一次 agent 运行,靠的是录下来的 trace,不是重跑。

🔍 为什么孪生运行的工作区目录名不同?这算不算项目的 bug?

本课项目给每次运行建一个独立的工作区,目录按 run id 命名(t2-parse_date-agent 与 pd-v2-unlimited-s1)。测试失败时,Python 的 traceback 会打印测试文件的完整路径,于是两次运行的工具输出差了几个 token。导出给课程用的数据把本机路径统一脱敏成 <workspace>,在导出版里两次输出看起来一模一样;分叉点和差异行是生成脚本回到原始 trace 比出来的(只保留 workspaces/ 之后的部分)。这不是 bug,是真实环境本来的样子:路径、耗时、时间戳、随机端口,都会进入模型的上下文。想让两次运行可比,要么固定这些东西(同一个目录名、冻结时间),要么接受「每次都不一样」、用多次运行的统计来说话(A9 的 pass@k)。

证据三问

问本课的回答
① 它解决什么问题?在「交出决定权」之前先判别:路径可预知的任务不必付 agent 的代价
② 为什么可能有效?agent 的每一轮都是一次整份重发的请求、一次可能出错的采样;轮数越多,token 越多、出错机会越多、路径越难预知
③ 实验上呢?workflow 已能解决的任务上 token <b>×5.3</b>(word_freq)与<b>×4.5</b>(leap_year);同配置同 seed 2 / 4 个任务分叉;按三条标准的预测对 3 / 4

演进地图:你在这里

什么时候需要 agent:每一代都被上一代的某个真数字逼出来
  1. 朴素 单次调用:报错 + 源文件发给模型,要回整份新文件,覆写后重跑 本课项目 workflow.py · 5 步全由代码决定
    解决了只调一次模型,每一步都写在代码里:可预期、好测、便宜。
    卡在T2 workflow 轨 2 / 4 通过。parse_date 只改了报错指到的那一行,重跑才露出第二处(datetime.date(2024, 2, 5) != datetime.date(2024, 3, 5));slugify 的回复只有函数片段,兜底规则把 130 字符的片段当整份文件写回(原文件 365 字符),丢了 import re。
  2. 第一代 加步骤、加分支:五种 workflow 模式 prompt chaining · routing · parallelization · orchestrator-workers · evaluator-optimizer
    解决了每一类失败补一条预先写好的路径:测试没过就带着新报错再来一次(评估者是测试的退出码)、回复格式不对就拒绝写回。
    卡在分支要在设计时写出来,可真实路径是在运行时长出来的:T2 agent 轨 4 个任务走出 4 条不同的工具序列;同一配置重跑 4 次 parse_date(seed 1 / 2 / 3 / 1),走出 4 条。
  3. 第二代 把「下一步做什么」交给模型:while + 工具 = agent 本课项目 loop.py(A1 细讲) 你在这里
    解决了边在运行时长出来:T2 agent 轨 3 / 4 通过,包括 workflow 没修好的 parse_date 与 slugify。
    卡在代价:workflow 已经能解决的任务上 token word_freq ×5.3、leap_year ×4.5,其中 word_freq 的 agent 反而没修好;同配置、同 seed 重跑,2 / 4 个任务在中途分叉。
  4. 第三代 先判别再选轨道:能用一段代码就不用 agent 三条判别标准 · 先直接调 API 你在这里
    解决了录制前写下三条标准并逐个预测,录制后对照:预测对 3 / 4。
    卡在预测错的 slugify 不是任务本身需要 agent,而是 workflow 自己的写回规则出了错(回复没有 FILE: 标记时的兜底)。每条轨道每个任务只跑 1 次:只能当例子,不能当比率。
  5. 岔路 混合:把固定编排写成 agent 的一个工具 learn-claude-code s16 Workflow Runtime
    解决了编排形状固定的部分(并行审查、逐条验证、断点续跑)写进脚本;模型只决定「要不要调这个 workflow、传什么参数」。
    卡在同一个仓库里两种说法并存:README :11 与 :41 说 agency 来自训练、不是编排;:239 说编排形状固定就写进代码。界线画在哪,没有实验回答。
  6. 岔路 通用 agent 切换到编码姿态 Hermes coding_context.py
    解决了同一个循环,识别出代码工作区后注入编码说明(约 577 token,Qwen3 tokenizer 计)与工作区快照;focus 模式才会收窄工具集与技能索引。
    卡在判定靠文件标记:本课任务仓单独拷出来(README.md、dateparse.py、test_dateparse.py,没有 .git)在默认 auto 下被判成 general;放在项目目录里原地跑,却因为往上 3 层有个 requirements.txt 被判成 coding。姿态在会话开始解析一次,中途不变。
数字口径:本课项目 T2 双轨录制(Qwen3-8B-MLX-4bit,本机 mlx_lm.server,seed 1,2026-09-26;4 个任务各跑 1 次,只说明现象会发生,不估计比率);孪生运行 = A1 里与 T2 agent 轨配置相同、seed 相同的那次运行;Hermes 一行是 pin 56dc01d9 的原实现在临时目录里实跑的判定。

一句话

📌
把决定权交给模型,换来适应性,付出 token、时间与可预期性。
🚦

先停在这里:记住三条判别标准,和「seed 管不住环境」。
再深一层:岔路 通用 agent 和编码 agent 差在哪?——同一个循环,为什么在代码仓库里要换一套说法;或者直接去实验课,先预测再看 4 个任务的双轨结果。

下一个缺口

模型决定下一步的那个循环,到底长什么样? → A1 循环:模型只会吐文字,它怎么改你的文件?什么时候停?

这一章一直把 agent 轨当成一个黑盒:「模型每一轮决定调哪个工具,或者停」。A1 打开它:模型写出的那段调用由谁执行、结果怎么送回去、为什么每轮都要整份重发、「不调工具就停」够不够——slugify 最后那几轮被截断的思考,A1 第 3 课会给它一个名字。

你可能会问