按下回车之后,Claude Code 在 100ms 内经历了什么?
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 单次回合的最小骨架:
# 极简版: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": # 模型不再要工具 → 说完了breakresults = 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!
-
07.29
失落城堡2诅咒遗物强势搭配思路与实战组合指南
-
07.29
失落城堡2信念记忆碎片如何获得 失落城堡2信念记忆碎片收集指南
-
07.29
失落城堡2堕落法师斗篷获取途径详解
-
07.29
失落城堡2诅咒遗物如何组合 失落城堡2诅咒遗物搭配指南
-
07.29
失落城堡2符文解锁效果详解 失落城堡2符文能带来什么效果
-
07.29
失落城堡2新手职业有哪些选择 失落城堡2新手职业选择指南
-
-
下载
- |
-
-
下载
- 《行尸走肉第一章》免安装中文汉化硬盘版下载
- 单机|436 MB
- 一款以动作冒险为主题的游戏
-
-
下载
- 《街头霸王X铁拳》免安装中文汉化硬盘版下载
- 单机|111MB
- 一款非常好玩的格斗游戏
-
-
下载
- |
-
-
下载
- 《暗黑破坏神3》免安装繁体中文正式版下载
- 单机|7630 MB
- 一款以角色扮演为主题的游戏
-
-
下载
- 《马克思佩恩3》免安装硬盘版下载
- 单机|27033 MB
- 一款以第三人称射击为主题的游戏