1. 字一个一个冒出来
你在终端里敲下一句话,按下回车。屏幕上字一个一个冒出来,中间还自己跑了几个命令、读了几份文件,改了一两个文件,最后给你一段总结。整个过程可能就一两秒,也可能几十秒。
那这一秒里,到底发生了什么?
-
是模型一口气把所有东西都想好再吐出来,还是边想边做?
-
那些"自己跑的命令"是谁、在什么时候决定跑的?
-
它们和最后那段文字总结又是什么关系?
-
如果模型中途改主意------本来要读 A 文件,读到一半又想去改 B------这是怎么发生的?
这些问题,恰恰是理解 Claude Code 这类 Agent 的钥匙。那答案是什么呢?
2. 答案是一条流水线
它的答案其实一点都不神秘:整个流程摊开看,是一条普通的流水线,加上一个 while 循环。这套让模型能动手的脚手架有个名字,叫 Harness。
-
你的输入先被加工成一条结构化消息,然后送进一个叫
query的异步生成器; -
query自己几乎不干活,它通过yield*把控制权整个交给queryLoop------一个while循环。 -
循环里,模型和工具来回接力:模型说"我要读这个文件",工具就跑、把结果塞回去;模型再说、工具再跑,直到模型说"我说完了"。
-
每一步都通过
yield把事件流式喷回 REPL,REPL 再用 Ink 把它一个字一个字地画到你的终端上。
一行话:一次回合 = 一个 while 循环 + 一条流式事件管道。
这里有两个关键词值得记住。一是"循环"------Agent 的所有"自主性"都来自它;二是"流式"------你看到的打字机效果、工具实时进度、可中断的 Ctrl+C,都建立在 yield 这个字上。下面三节,就是把这句话拆开揉碎来介绍。
3. 核心骨架就只有8行
要拆开它,最省力的入口不是直接啃源码---src/query.ts 有 1700+ 行,光 queryLoop 就占上千行,啃下去只会迷路。先把骨架剥出来。剥掉 hook、权限校验、压缩、计费、日志这些外壳,剩下的和一个 8 行的玩具 Agent 没有本质区别。
下面这段,就是 Harness 一次回合的最小骨架:
python
# 极简版:harness 行动回路的最小实现
messages = [{"role": "user", "content": prompt}]
while True:
resp = client.messages.create(model=MODEL, messages=messages, tools=tools)
messages.append({"role": "assistant", "content": resp.content})
if resp.stop_reason != "tool_use": # 模型不再要工具 → 说完了
break
results = run_tools(resp.content) # 执行模型要的工具
messages.append({"role": "user", "content": results}) # 结果塞回去
盯住三个关键点:
while True:没有固定次数的循环。什么时候结束,不靠计数器,靠模型自己说"完了"。这是 Agent "自主性"的源头------而这个"自主",Harness 一行都没写,它全在模型里。stop_reason:模型每一轮都会给一个停下的理由。要工具就是tool_use,说完话就是end_turn。整个 Agent 的行为分支,都挂在这个模型吐出的字段上。messages.append(...):工具结果不是悄悄用的,而是作为新的 user 消息塞回对话历史。对模型来说,它看到的始终是一条连续的对话------工具的"动手"被翻译成了对话里的"该你了"。
把这三点记住,后面做Harness工程实践时,就不会被各种 hook、权限、压缩、子 Agent 带跑------它们都是 Harness 给这个骨架添的"器官"。骨架本身,只有这三行。
还有一个细节:这段极简版是"同步"的------create 调完拿到完整 resp 才往下走。而 Claude Code 的真实实现是流式的,模型边吐 token、循环边收。但骨架没变,只是把"一次性 resp"换成了一串时序到达的事件。流式改变的是体验和响应度,不是结构。
4. Harness 五脏速览
有了骨架,我们把 Claude Code 真正的实现叠上去。下面这张图就是一次完整回合的全景------也是 Harness 五脏在这一秒里的速览。每个节点都是一个"器官",后面都会单独放大;但请先记住这张图,它是后面所有的导览。

逐个节点介绍一下,顺便认认器官:
- ① 会话启动 :回车键触发 REPL 里的
handlePromptSubmit(src/screens/REPL.tsx),这一回合的"启动按钮"。 - ② 造一条消息 :裸文本被加工成结构化的
Message,塞进对话历史。这一步会拼上 system prompt、CLAUDE.md、memory------那是上下文工程这个器官的活,后面详聊,这里只当成"造一条消息"。 - ③ 进入 query :消息交给
query(src/query.ts)。注意它声明里的*------async function*,异步生成器,可以一边跑、一边往外吐事件。 - ④ 交给 queryLoop :
query自己几乎不干活,它通过yield* queryLoop(...)(src/query.ts)把控制权整个 交给queryLoop(src/query.ts)。这才是行动回路真正的心脏。 - ⑤ 调用模型 :循环体里先调模型,拿到一个
stop_reason。 - ⑥ 执行工具 :
tool_use分支------执行工具、把结果塞回消息、回到循环顶继续问模型。这条回路就是 Agent "自己跑命令"的来源,它就是工具系统(Part 2)在行动回路里的接口。 - ⑦ 收尾输出 :
end_turn分支------收尾、把整条路径上累积的事件一路yield出去。中间那道 Stop Hook,是安全护栏插进来的拦截点。 - ⑧ 渲染到终端 :事件穿过
query的yield*回到 REPL 的for await (const event of query({ ... }))(src/screens/REPL.tsx),onQueryEvent把它翻译成 React state,Ink 把新 state 渲染到终端------这是人机交互的一端。
一句话总结这张图:回车 → 加工 → query → queryLoop(模型/工具来回接力)→ 流式 yield → 渲染。生产级的所有魔法,都缩在中间那个循环里;而那个循环,就是行动回路。
5. 回到最初的悬念
现在回头看第 1 节留下的那个悬念------"Harness 到底干了什么"。答案就散在这张图的节点上,化作你能看见的三种现象:
- "字一个一个冒出来" = 流式
yield。模型每生成一个 token、工具每产出一行,都是一帧事件,被 REPL 收到就立刻画到屏幕。你看到的"打字机效果"不是前端动画,是事件真的按时序到达------屏幕显示的速度,就是模型生成的速度。也正因事件是流式的,你按 Ctrl+C 才能随时在某一帧打断它。 - "自己执行命令" =
tool_use分支。模型判断"我需要读这个文件",queryLoop就让对应工具跑、把输出塞回对话历史,再回到循环顶继续问模型。你看到它"自己读了三个文件、改了一个",其实是这条回路被走了多次,每次都让模型基于新结果再决策一次。模型并不会预先规划"读三个文件"------它每一轮根据最新信息临时决定下一步。 - "说完了" =
end_turn。模型主动给出这个stop_reason,循环才会退出。在此之前,queryLoop永远不会"自己决定结束"------Agent 的终点,始终是模型自己选的。
发现了吗?这三种现象里,没有一种是 Harness "想"出来的。 Harness 只负责把模型的决策搬运成动作、把动作的结果搬运回模型。智能始终在模型那一边。
6. 把 Harness 想成一个值班室
把整套 Harness 想成一个值班室。
你是送件人,把一张工单(输入)递进窗口。值班员(queryLoop)拿到工单后只做一件事:打电话问专家(模型):"这个怎么处理?"
专家回:"我需要查一下 X 文件。"值班员不自己查,而是让助理(工具)去查、把结果带回来,再打电话给专家:"查到了,内容是这样,然后呢?"专家又回:"那再看一下 Y。"如此循环。
直到某一次,专家说:"行了,我懂了,答案是这个。"------end_turn。值班员把最终答案整理好,从窗口递回给你。
关键看分工:值班员和助理都是 Harness,专家才是 Agent(模型)。 值班员不思考,只传话和派活(行动回路);助理不判断,只动手(工具系统);真正做决策的只有专家。循环驱动、分工清晰------这就是 Harness 的本质。Claude Code 的 query.ts 那 1700+ 行,大部分都是在给值班员、专家、助理各自添置护栏和助手:给值班员加恢复机制(不让循环崩)、给专家拼好上下文(工作记忆)、给助理加权限审批(护栏)。
还有个容易忽略的细节:值班员和专家之间的电话是双工 的------专家一边说,值班员一边往外递(流式 yield),不是等专家挂电话才一次性汇报。这正是你在终端看到字一个一个冒出来的原因。这个比喻背后,正好对应 TypeScript 里 async function* 和 for await 这对语法:它们天生就是为"边产边消费"的流式管道设计的。
7. 智能不在代码里
回到最开始那个错觉:Claude Code 的智能,不在它的代码里。代码是 Harness, Agent是模型 。一次回合,不过是 Harness 用一个 while 循环把模型的决策变成动作、再流式画到屏幕------query.ts 那 1700+ 行,全是给这个循环配的器官和护栏。
而这套 Harness 的器官,正好就是整部系列的地图:
- 行动回路------让模型动起来,且不让它崩(这是 Harness 的心脏);
- 工具系统------给模型的手;
- 上下文工程------给模型的工作记忆;
- 安全护栏------让模型放手干又不闯祸;
- 多 Agent 编排------从单兵放大到团队;
- 可扩展与人机交互------对接世界,也接住人的插手。
记住这张地图,这个就是整个Harness的五脏六腑。
8. 后记
现在我们回顾一次会话的处理过程,会发现里面藏着一个问题:万一专家一直要工具、永远不说"说完了"怎么办?或者更现实的------对话太长、上下文窗口快爆了、工具失败了、权限拒绝了,这时循环怎么自救?那 8 行的 while True 根本 hold 不住,它会要么无限转下去、要么崩出一个 stack trace。
8 行的 demo 循环,和 1700+ 行的生产级 queryLoop,差就差在"怎么不崩"上。 这正是 Harness 工程的核心难题之一: 可靠性。
下一篇,我们就钻进 queryLoop 这个节点,看它一个循环里的 7 种 continue ------每一行 continue 都是一种自救动作:工具失败时怎么重试、上下文将满时怎么压缩、配额超限时怎么降级、模型越界时怎么拉回来。把那 7 条路看明白,你才会真正相信:Agent 不会在半路卡死。
Let's go!