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

agency 是训练出来的,还是编排出来的?

agency 从哪来
本课的蓝本 learn-claude-code 在 README 开头就下了断言:agency 来自模型训练,不来自外部的代码编排;把 LLM 调用用 if-else 和节点图串起来不是在造 agent。Anthropic 却把 workflow 当作一等公民,建议先找最简单的方案,甚至可能根本不建 agentic 系统。更有意思的是,同一个仓库后来加了 s16:「编排形状固定时,就把它写进代码」。两边都是立场,没有实验;本课项目的一组数字说明,两件事缺一不可。
❓

本课一问:第 2 课说,agent 就是让模型决定下一步。可模型凭什么会「决定下一步」?这个能力是训练进模型里的,还是 harness 的代码编排出来的?如果是前者,workflow 那些预先写好的路还有什么意义?

这一课先不碰:怎么训练出这种能力(LLM 课 C12 第 8 课已经讲过);agent 循环的具体写法(A1)。

从已有出发

回到贯穿任务里那一步:T2 的 agent 轨在第 5 轮重跑测试,看见 datetime.date(2024, 2, 5) != datetime.date(2024, 3, 5),第 6 轮就去改 MONTHS.index 那一行。这一步里有两样东西:模型会读 traceback、会写出一个格式正确的 edit 调用;harness 给了它工具、替它跑了测试、把输出送回来。问题是哪一样算「agency」。

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

先自己设计:只听一边

最省事的读法是只听一边。本课的教学蓝本 learn-claude-code 在 README 的开头就把话说满了——照它说,agency 全在模型里,那 harness 的编排就无关紧要,换个更强的模型就是了:

  1. ① 朴素方案
  2. ② 它在哪失败
  3. ③ 改进方案
朴素方案agency 全来自训练:编排无关紧要,换个强模型就行
learn-claude-code README:Agency 来自模型 learn-claude-code@0dcafa2:README.md:5-17需联网 MIT
5# Learn Claude Code -- Harness Engineering for Real Agents
6
7## Agency Comes from the Model. An Agent Product = Model + Harness.
8
9Before we write any code, one thing needs to be clear.
10
11**Agency -- the capacity to perceive, reason, and act -- comes from model training, not from external code orchestration.** But a working agent product needs both the model and the harness. The model is the driver. The harness is the vehicle. This repository teaches you how to build the vehicle.
12
13### Where Agency Comes From
14
15At the core of every agent is a neural network -- a Transformer, an RNN, a trained function -- shaped by billions of gradient updates on sequences of perception, reasoning, and action. Agency was never bestowed by the surrounding code. It was learned during training.
16
17Humans are the original proof. A biological neural network, refined by millions of years of evolutionary pressure, perceives the world through senses, reasons through a brain, and acts through a body. When DeepMind, OpenAI, or Anthropic say "agent," they all mean the same core thing: **a model that learned to act through training, plus the infrastructure that lets it operate in a specific environment.**
👆 带下划线的行可以点:点哪行,讲哪行。

接着它把矛头对准了另一类东西:

learn-claude-code README:什么不是 agent learn-claude-code@0dcafa2:README.md:33-49需联网 MIT
33### What an Agent Is NOT
34
35The word "agent" has been hijacked by an entire prompt-plumbing industry.
36
37Drag-and-drop workflow builders. No-code "AI Agent" platforms. Prompt-chain orchestration libraries. They share a single delusion: that stringing LLM API calls together with if-else branches, node graphs, and hardcoded routing logic constitutes "building an agent."
38
39It does not. What they produce are Rube Goldberg machines -- over-engineered, brittle, procedural rule pipelines with an LLM wedged in as a glorified text-completion node. That is not an agent. That is a shell script with grandiose pretensions.
40
41You cannot brute-force intelligence by stacking procedural logic -- sprawling rule trees, node graphs, chained prompt waterfalls -- and praying that enough glue code will spontaneously produce autonomous behavior. It will not. You cannot engineer agency into existence. Agency is learned, not coded.
42
43### The Mindshift: From "Building Agents" to Building Harnesses
44
45When someone says "I am building an agent," they can only mean one of two things:
46
47**1. Training a model.** Adjusting weights through reinforcement learning, fine-tuning, RLHF, or another gradient-based method. Collecting trajectory data -- real-world sequences of perception, reasoning, and action in a target domain -- and using it to shape the model's behavior. This is what DeepMind, OpenAI, Tencent AI Lab, and Anthropic do.
48
49**2. Building a harness.** Writing the code that gives a model an operational environment. This is what most of us do, and it is the core of this repository.
👆 带下划线的行可以点:点哪行,讲哪行。

立场一:agency 来自训练(learn-claude-code README)

上面那两段 README 就是立场一的全部依据:历史类比与论证。它没有否认 harness(第 11 行后半句:产品要模型和 harness 两样),它否认的是「把 LLM 调用用写死的路径串起来」能造出 agency(第 37–41 行)。

立场二:先找最简单的方案(Anthropic)

Anthropic 的《Building effective agents》站在另一边。它把 workflow(预先写好的代码路径)和 agent(模型动态决定流程)都归为 agentic systems,把五种 workflow 模式当作正经的设计模式逐一讲清;并且建议先找the simplest solution possible,只在需要时加复杂度,这might mean not building agentic systems at all——可能根本不建 agentic 系统 。

learn-claude-code README(pin 0dcafa2)Anthropic《Building effective agents》
核心主张agency 来自训练;产品 = 模型 + harness先找最简单的方案;workflow 与 agent 各有适用处
对 workflow把 LLM 调用串成规则流水线「不是 agent」五种 workflow 是一等公民;很多场景单次调用就够
依据历史类比(DQN、OpenAI Five、AlphaStar……)与论证与几十个团队合作的经验
有没有实验没有没有
适合拿来提醒你:别指望靠堆规则让一个不会用工具的模型变聪明提醒你:别把能写死的路交给模型
调研的定性有力但极端的社区观点,与官方分类有真实张力分野满票;「先直接调 API」2-1 非满票

两边其实在回答不同的问题:README 说的是「能力从哪来」,Anthropic 说的是「系统怎么搭」。张力出在 README 第 37–41 行:它把预先写好路径的编排整体判了出局,而 Anthropic 恰恰把这些当作首选。

转折:同一个仓库,后来加了 s16

pin 0dcafa2 里,learn-claude-code 的 17 章中多了一章 s16 Workflow Runtime,它在 README 章节表里的口号是:

learn-claude-code README:s16 的口号 learn-claude-code@0dcafa2:README.md:237-241需联网 MIT
237> **s15**   *"Many mechanisms, one loop"* — the mechanisms used by the integrated example share one harness
238>
239> **s16**   *"When the orchestration shape is fixed, put it in code"* — saved workflows with resumable journals
240>
241> **s17**   *"A goal decides when the loop may stop"* — an independent evaluator reviews each proposed stop; impossible, failed, or over-limit goals return control to the user
👆 带下划线的行可以点:点哪行,讲哪行。

它的做法是把一段保存好的编排脚本,做成 agent 工具池里的一个工具:

s16 · 一份保存好的 workflow,以及把它交给模型的工具定义 learn-claude-code@0dcafa2:s16_workflow_runtime/code.py:673-729需联网 MIT
673async def sample_workflow(ctx, args):
674 """pipeline over review dimensions (audit -> verify-each), then keep only the
675 findings a verifier confirms. The plan is code, not a chat turn."""
676 ctx.phase("Review")
677 changes = args.get("changes", "")
678 if not isinstance(changes, str):
679 raise WorkflowInputError("args.changes must be a string")
680 review_input = changes.strip() or "No change context was supplied."
681
682 async def audit(_value, dimension, _idx):
683 out = await ctx.agent(
684 f"Review this change context for {dimension} issues. "
685 "Report only issues supported by the supplied text.\n\n"
686 f"{review_input}",
687 schema=FINDINGS_SCHEMA, label=f"audit:{dimension}", phase="Review")
688 return {"dimension": dimension, "findings": out["findings"]}
689
690 async def verify(audited, dimension, _idx):
691 ctx.phase("Verify")
692 # Each finding is verified by its own adversarial subagent, concurrently.
693 verdicts = await ctx.parallel([
694 (lambda f=f: ctx.agent(
695 f"Adversarially verify this {dimension} finding against the "
696 "supplied change context.\n\n"
697 f"Change context:\n{review_input}\n\n"
698 f"Finding:\n{json.dumps(f, ensure_ascii=True)}",
699 schema=VERDICT_SCHEMA, label=f"verify:{dimension}:{f['title']}", phase="Verify"))
700 for f in audited["findings"]])
701 confirmed = [f for f, v in zip(audited["findings"], verdicts)
702 if v and v.get("isReal")]
703 return {"dimension": dimension, "confirmed": confirmed}
704
705 results = await ctx.pipeline(DIMENSIONS, audit, verify)
706 confirmed = [{"dimension": r["dimension"], **f}
707 for r in results if r for f in r["confirmed"]]
708 confirmed.sort(key=lambda f: {"high": 0, "medium": 1, "low": 2}.get(f["severity"], 3))
709 ctx.log(f"confirmed {len(confirmed)} real finding(s)")
710 return {"confirmed": confirmed}
711
712
713# Saved workflow registry
714WORKFLOWS = {SAMPLE_META["name"]: (SAMPLE_META, sample_workflow)}
715
716WORKFLOW_TOOL = {
717 "name": "Workflow",
718 "description": "Run a saved workflow by name. Pass input in args.",
719 "input_schema": {
720 "type": "object",
721 "properties": {
722 "name": {"type": "string"},
723 "args": {"type": "object"},
724 "resume_from_run_id": {"type": "string"},
725 },
726 "required": ["name"],
727 "additionalProperties": False,
728 },
729}
👆 带下划线的行可以点:点哪行,讲哪行。
s16 · 装进 s15 的工具池,循环本身不改 learn-claude-code@0dcafa2:s16_workflow_runtime/code.py:778-794需联网 MIT
778def install_workflow_tool(host):
779 """Extend the s15 host tool pool without changing its dispatch loop."""
780 global RUNNER_FACTORY
781 RUNNER_FACTORY = lambda: AnthropicAgentRunner(host.client, host.MODEL)
782 if getattr(host, "_workflow_tool_installed", False):
783 return
784 base_assemble = host.assemble_tool_pool
785
786 def assemble_with_workflow():
787 tools, handlers = base_assemble()
788 if not any(tool.get("name") == "Workflow" for tool in tools):
789 tools.append(WORKFLOW_TOOL)
790 handlers["Workflow"] = run_workflow_sync
791 return tools, handlers
792
793 host.assemble_tool_pool = assemble_with_workflow
794 host._workflow_tool_installed = True
👆 带下划线的行可以点:点哪行,讲哪行。

所以在同一个仓库里,两种说法并存了:README 开头说编排出不了 agency;s16 说编排形状固定就写进代码,再让 agent 在需要时调用它。放回第 2 课的光谱,s16 是个混合体——外层是 agent(模型决定调不调这个工具),工具里面是 workflow(脚本决定编排),而编排里的每一次 agent() 调用又是一次模型调用。这不是自相矛盾,是立场松动了:它没有收回「agency 来自训练」,但承认了有些路就该写死 。

第三种声音:harness 里的假设会过时

Anthropic 在 2026-04-08 的工程文章《Scaling Managed Agents》里给了一个中间的说法:harness 里写进了「模型自己做不到什么」的假设,而这些假设要经常拿出来重新审视,因为模型在变。它举的例子是:为某一版模型在 harness 里加了上下文重置(那版模型临近上下文上限时会草草收尾),换到下一版模型上,这个行为消失了,重置成了累赘 。

这句话同时站在两边:训练让模型会的东西越来越多(learn-claude-code 的方向),所以 harness 里替模型兜底的编排要跟着删减;但在某个具体的模型上,哪些路该写死、哪些该交出去,仍是 harness 的设计(Anthropic 原文的方向)。

极小真数字:一条规则,十种配置

训练这一侧:Hermes Agent(一个通用 agent,岔路 B2 会细讲)的源码里专门有一条警告——Nous Research 自家的 Hermes 3、Hermes 4 模型不是 agentic 的,缺少 agent 工作流需要的工具调用能力,不适合配这个 harness 。harness 写得再好,模型没学会写工具调用,循环就转不起来。

编排这一侧:本课项目用同一个模型(Qwen3-8B)、同一套 10 种运行配置,把 harness 从 v1 换成 v2。v2 改了两处,一是在 system prompt 末尾加了这条完成规则:

Completion rule: never say the tests pass unless you have just seen them pass in a bash tool
result in this conversation. After every edit you MUST run the tests again before you finish.
One failing assertion can hide another one behind it.

二是服务端丢弃调用时提醒模型重发(v1 直接当成模型停了)。逐个配置对照(验收由 harness 跑测试判定,不看模型自述;最后一列是除这两处之外、两次运行配置里还有的差别):

配置(A1 录制)harness v1harness v2其他差别
unlimited-s1✗ · 5 轮 · no_tool_calls✗ · 8 轮 · no_tool_calls—
unlimited-s2✗ · 5 轮 · no_tool_calls✓ · 6 轮 · no_tool_calls—
unlimited-s3✗ · 5 轮 · no_tool_calls✓ · 7 轮 · no_tool_calls—
cap20-s1✗ · 5 轮 · no_tool_calls✓ · 11 轮 · no_tool_calls—
cap4-s1✗ · 4 轮 · max_rounds✗ · 4 轮 · max_rounds—
firstmatch-s1✗ · 6 轮 · no_tool_calls✗ · 7 轮 · no_tool_calls—
truncate-s1✗ · 7 轮 · no_tool_calls✗ · 8 轮 · no_tool_callsbudget_tokens、snip、summarize、collapse
layered-s1✗ · 5 轮 · no_tool_calls✓ · 21 轮 · no_tool_callsbudget_tokens、snip、summarize、collapse
promptjson-s1✓ · 10 轮 · length✓ · 9 轮 · no_tool_calls—
nothink-s1✗ · 5 轮 · no_tool_calls✗ · 9 轮 · dropped_tool_call—

贯穿任务的验收通过数从 1 / 10(harness v1)变成 5 / 10(harness v2)。由没过变成通过的 4 对里,3 对(unlimited-s2、unlimited-s3、cap20-s1)的配置除了这两处一模一样,而重发开关在它们身上一次也没触发过——配置上真正起作用的差别只剩那条完成规则(每对只跑一次,结果里仍混着采样的随机性,见第 3 课的孪生运行) 。模型没变,变的只是 harness 告诉它的规矩。

Hermes · 对 Hermes 3/4 模型的警告 hermes-agent@56dc01d9:hermes_cli/model_switch.py:206-211需联网 MIT
206_HERMES_MODEL_WARNING = (
207 "Nous Research Hermes 3 & 4 models are NOT agentic and are not designed "
208 "for use with Hermes Agent. They lack the tool-calling capabilities "
209 "required for agent workflows. Consider using an agentic model instead "
210 "(Claude, GPT, Gemini, DeepSeek, etc.)."
211)
👆 带下划线的行可以点:点哪行,讲哪行。
⚠
常见误解:「learn-claude-code 说 agency 来自训练,所以 workflow 没用,直接上 agent 就行。」

那是一个有力但极端的社区立场,没有实验支撑;它和 Anthropic 把 workflow 当一等公民的分类有真实的张力,调研要求两者并陈、不当结论 。而且同一个仓库自己加了 s16,把固定的编排写进了代码。反过来也不对:Anthropic 说「先找最简单的方案」,不等于「别用 agent」——它同一篇文章里也写了 agent 适合什么。

证据三问

问本课的回答
① 它解决什么问题?分清「模型会不会决定下一步」与「系统让它在哪儿决定」,免得拿一个问题的答案去否定另一个
② 为什么可能有效?训练决定模型能不能可靠地写出调用、读懂反馈;编排决定这些能力被用在哪几条边上
③ 实验上呢?没有直接比较两种立场的实验;本课项目只提供旁证:同一模型只换 harness(主要是一条完成规则),通过数 1 → 5(10 种配置)

演进地图:你在这里

什么时候需要 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 的原实现在临时目录里实跑的判定。

一句话

📌
会不会决定下一步靠训练;在哪儿让它决定、决定到什么程度,靠编排。

回主线

把决定权交给模型,代价是什么? → 第 3 课

你可能会问