🐎 第1章 登高望远:Claude Code 技术架构全景
📄 一句话概括

同一个模型,一句「帮我修好这个测试」,在命令行助手那儿得到的是一段建议, 在 Agent 那儿得到的是一个改好的文件。 差别不在模型多大,在中间那个 while:能动手、记得住、知道什么时候停。这一节把这三件事拆开, 最后算一遍循环的账。

📝 本章和前面几章的关系

这是全书第一章。 后面九章讲的每一样东西——记忆、技能、子智能体、钩子、协议、无人值守、SDK、插件、 工程化——都是在给这一节的循环添零件。 所以这一节不求细,只求把「循环」这个骨架立起来,后面往里挂东西才不会乱。

一、助手有两种,名字一样

命令行里的 AI 工具有两种用法,界面上看不出区别,行为上差得很远。 第一种是问一句答一句:你描述问题,它给一段文字,会话结束。 下一句追问,它已经不记得上一句了,除非你把上下文再贴一遍。 第二种是你说一句,它干一串:读文件、改文件、跑测试、看报错、再改一次, 直到它认为事情办完了才回来跟你说结果。

这两种都叫「AI 编程助手」。 但第一种的天花板是「它说得对不对」,第二种的天花板是「它做得对不对」——判断标准完全不是一回事。 演示里同一份坏代码交给了这两种跑法。第一种给了一句完全正确的建议,文件一个字没动; 第二种四步之后测试变绿。 建议本身没错,错在没有谁去执行它。模型能看见问题,但没有手。

二、循环的三件必需品

把第二种跑法拆开,就三样东西,缺一样都转不起来。 第一样是工具。读文件、写文件、执行命令、搜代码,这些是模型伸出去的手。 没有工具,它只能输出文字,也就是回到第一种。 第二样是状态。每一轮它都要知道「我刚才干了什么、结果是什么」,否则第二步会重复第一步。 演示里那个上下文变量就是状态的最小形态。

第三样是终止条件。这是最容易被忽略、也最容易出事的一样。 演示第三段把终止条件拿掉之后,测试第一轮就绿了,后面四轮它还在测——因为它不看结果, 只按自己的节奏走。 真机上这表现为对话停不下来、token 一直烧。 所以循环必须带两个刹车:状态满足就停,回合数到上限也停。

状态那个刹车要读「外部世界的真实反馈」,不能读「模型说完成了」。 模型说完成了是自我评价,测试通过是事实。这两者在工程上不等价。

三、Harness 这个词

书名里的 Harness 是马具的意思:缰绳、鞍、辕、嚼子那一套。 马具不让马跑得更快。 它做的是另一件事:把马的力气导向一个方向。没有马具,马也能跑,但你不知道它往哪跑、拉不动车、 也停不下来。 这套东西套在模型身上,就是循环外面那一圈:什么情况下给它什么上下文、允许它动哪些东西、 动完谁来检查、什么时候强制收工。

所以「Harness 工程」不是调模型。模型是别人训好的,你改不了它的权重。 你能改的是它周围那一圈架子——而这圈架子决定了它到底能干什么活。 这个视角很重要,因为它解释了后面九章为什么长成那样:每一章都是给架子加一个部件, 不是给模型加一分智商。

四、循环不是免费的

循环的每一步都要重新读一遍前面的全部内容,因为模型没有记忆,它每次都是从零开始看这一整段历史。 演示里四步走完,四轮各自的输入是 15、33、37、37 token,合计 122;而一问一答那一份只有 19 token。 做了四倍的事,付了六倍的钱。 这个比例会随着步数放大。第十步的时候,前十步的全部内容都要再读一遍。

所以循环的工程问题从来不是「能不能转」,而是「转几圈能完事」。 圈数少,靠的是上下文干净、工具好用、任务拆得细——这三件事正是后面各章的主题。

五、这本书的路怎么走

第 1 章立骨架:四层架构、组件怎么协作、什么需求该用什么。 第 2 章到第 6 章各讲一个部件:记忆(CLAUDE.md)、技能(Skills)、子智能体、钩子(Hooks)、 协议(MCP)。 第 7 章到第 9 章讲怎么把这些部件用到人不在场的地方:无人值守的命令行模式、写程序调用它的 SDK、 把一整套打包分发的插件。

第 10 章收口:成本、调试、安全、大仓库、团队落地。 这个顺序是有讲究的:先知道有什么,再知道怎么用,最后知道怎么管。跳过第一步直接抄配置, 出了事就不知道该查哪儿。

六、动手 Demo:同一份坏代码,两种跑法

造一个两文件的小仓库:一个加法函数写错了符号,一个测试断言 2 加 3 等于 5。 先真的跑一遍测试,把报错贴出来。 然后拿同一条报错走两条路:一问一答,和带终止条件的循环。最后把终止条件摘掉,看它会不会自己停。

def policy(ctx):
    if u"1 passed" in ctx:            # 外部世界说好了,才停
        return (u"停", None)
    if u"已替换" not in ctx:
        if u"return a - b" in ctx:
            return (u"patch", (u"return a - b", u"return a + b"))
        return (u"read", u"calc.py")
    return (u"test", None)

while True:
    act, arg = policy(ctx)        # 真机上是模型在规划
    if act == u"停":
        break
    ctx += u"\n[%s] %s" % (act, 执行(act, arg))

实跑输出:

--------------------------------------------------------------------
一问一答和「循环」差在哪:同一个坏测试,两种跑法
--------------------------------------------------------------------

  仓库里一个加法函数、一个测试,测试挂了:
  Traceback (most recent call last):
    File "…\test_calc.py", line 4, in <module>
      raise AssertionError(u"add(2, 3) 得到 %r,期望 5" % (add(2, 3),))
  同一条报错,交给两种不同的跑法。

  ① 一问一答:给了建议,但没有人去执行
  --------------------------------------------------------------------
  模型回了一句「把 calc.py 第 2 行的减号改成加号。」,这一轮就结束了。
  文件一个字没动,再跑一次测试:
  AssertionError: add(2, 3) 得到 -1,期望 5
  模型没有工具,也没有下一轮。建议停在对话框里。

  ② Agentic Loop:模型自己读、改、验,转到不再需要动作为止
  --------------------------------------------------------------------
| 回合 | 动作 | 对象 | 结果 | 上下文 token |
|---:|---|---|---|---:|
| 1 | read | calc.py | 32 字符,第 2 行是 return a - b | 15 |
| 2 | patch | calc.py | 已替换 1 处,减号变加号 | 33 |
| 3 | test | test_calc.py | 1 passed | 37 |
| 4 | 停 | — | 测试已绿,不再动作 | 37 |

  四步之后测试变绿。循环本身不稀奇,三行 while 就能写:
  while True:  act = model(ctx);  if act == 停: break;  ctx += 执行(act)
  ! 真正决定成败的是那个 break 的条件。
  这里它读的是上下文里出现了 1 passed,也就是「外部世界真的变了」;不是「模型说完了」。

  ③ 把终止条件拿掉:循环不会自己停
  --------------------------------------------------------------------
| 回合 | 动作 | 测试返回 | 说明 |
|---:|---|---|---|
| 1 | test | 1 passed | 第一轮就这样 |
| 2 | test | 1 passed | 和第一轮一模一样 |
| 3 | test | 1 passed | 和第一轮一模一样 |
| 4 | test | 1 passed | 和第一轮一模一样 |
| 5 | test | 1 passed | 和第一轮一模一样 |

  ! 第一轮就已经绿了,后面四轮模型还是在测——因为它不看结果,只按自己的节奏走。
  真机上这表现为对话停不下来、token 一直烧,所以循环必须带两个刹车:状态满足就停,
  回合数到上限也停。

  ④ 循环的代价:上下文只增不减
  --------------------------------------------------------------------
  第二张表的最后一列就是代价。
  每做一步,前面所有结果都得重新带进下一轮,因为模型不会「记住」,它每次都从零把整段历史读一遍。
  一问一答那份上下文 19 token,只付一次;循环四步各付一遍,合计 122 token,是前者的 6.4 倍。

①第一段是环境。报错信息一字不改地贴出来,因为后面所有判断都基于它:断言失败,实际值 -1,期望 5。 ②一问一答这一格,模型给的答案完全正确。但文件没动,所以再跑一次测试,报错一模一样。 这就是「能看见问题,但没有手」。模型的输出停在对话框里,没有通道回到文件系统。 ③循环这张表要看三列:动作、结果、上下文 token。 第一步 read,第二步 patch,第三步 test,第四步停。 每一步的决定都基于上一步的结果,不是提前排好的脚本。 最后一列 15、33、37、37 是这一轮的输入量。它一直在涨,因为历史越攒越长。 ④那个 break 条件是整段代码里最要紧的一行。它读的是 1 passed,也就是测试进程真的返回了这个字符串。 如果换成读「模型说完成了」,那就是让运动员自己当裁判。 真机上这两种写法的差别,会在模型判断失误时暴露出来。 ⑤第三张表是反例:把终止条件拿掉,循环跑到第五轮还在测。 注意第一轮就已经绿了。剩下的四轮不是因为它觉得没绿,是因为它根本不看。 真机上这就是「对话停不下来」。所以两个刹车缺一不可:状态满足停一次,回合数到顶强制停一次。 ⑥最后这一格把账算清楚:一问一答 19 token,循环四步 122 token,六点四倍。 这不是缺陷,是循环的固有成本。要降它,只能让圈数变少——而这正是后面每一章在做的事。

30 秒实验

  1. 把 policy 里那个「停」的判断改成读「模型说自己完成了」,也就是永远返回停。演示会变成几步? 这说明什么?
  2. 把 patch 那一步拆成两次(先备份再改),重跑第 ③ 步。上下文合计涨多少?步数和成本是什么关系?
  3. 给循环加一个上限:最多五轮。 然后人为让测试永远不过(比如把断言改成等于 6),看它是在第几轮被强制停下的。

常见误区

  1. 把「Agent」当成一个更强的模型。它不是模型,是模型外面那圈循环加工具。 同一个模型放进去就是 Agent,拿出来就是聊天框。
  2. 终止条件写成「模型说完成了」。模型对自己的判断没有校准,它会在事情没办成时说办成了。 要读外部反馈:测试通过、命令退出码为零、文件真的变了。
  3. 只设回合上限,不设状态条件。上限是兜底,不是正常出口; 靠上限停下的循环,说明前面每一步都没收敛。
  4. 以为循环越多次越好。步数多说明任务没拆清楚,上下文越滚越脏,后面几步的判断质量反而下降。
  5. 把 Harness 理解成提示词工程。提示词是循环里的一句话,Harness 是整个循环的结构。 两者的量级差着一个数量级。
自测三问(点开对答案) 1. 「能看见问题」和「能解决问题」之间差的是什么? —— 差一条把决定变回动作的通道,也就是工具。 再加上状态(知道上一步干了什么)和终止条件(知道什么时候收工),才构成一个能自己干活的循环。 2. 终止条件为什么必须读外部反馈,不能读模型的自我评价? —— 因为模型对自己是否完成任务的判断没有校准,它会在失败时说成功。 外部反馈是事实:测试进程返回了什么、文件内容变没变、命令退出码是多少。 3. 循环的上下文成本为什么随步数增长,而不是线性摊平? —— 每一步都要把前面全部历史重新读一遍,模型没有跨轮记忆。 所以第 n 步的输入包含前 n-1 步的全部内容,累计成本随步数加速上升。

骨架立起来了,下一节把它拆成四层:记忆、扩展、集成、编程,各管一段。

相关笔记