Claude Code 01:Agent Loop——一个循环就是一个 Agent

在探讨复杂的自主智能体架构时,我们很容易被多角色对话、规划图、记忆网络等概念裹挟。但如果剥去所有外层的花哨包装,Agent 的物理内核其实就是一个受控的循环语句。

大语言模型本身具备强大的代码推理能力,但它没有双手,碰不到现实世界——它既不能读取磁盘文件,也不能运行单测,更看不到标准错误输出(stderr)。在没有 Agent 循环时,开发者在网页端使用大模型写代码:模型输出一段命令,你复制到终端执行,再把报错信息粘回输入框。在这个过程中,你自己就是那个手动循环。

所谓的自主 Coding Agent,本质就是用一段代码把“模型决策 $\rightarrow$ 工具执行 $\rightarrow$ 结果捕获 $\rightarrow$ 回传模型”的链路闭环起来。

本文将实现一个不到 30 行的最小可运行 Agent Loop,并深入剖析 Claude Code 生产源码中多达 1,729 行的状态机控制流。


30 行最小实现:One Loop & Bash

下面是用 Python 编写的最小闭环实现。它只注册了一个 bash 工具,但已经具备了自主完成真实工程任务的核心能力:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import subprocess
import anthropic

client = anthropic.Anthropic()
MODEL = "claude-3-7-sonnet-20250219"

SYSTEM_PROMPT = "你是一个能够执行 bash 命令的 Coding Agent。通过工具调用完成用户需求。"

TOOLS = [{
"name": "bash",
"description": "在当前工作区执行 shell 命令,并返回 stdout 和 stderr",
"input_schema": {
"type": "object",
"properties": {
"command": {"type": "string", "description": "待执行的命令"}
},
"required": ["command"]
}
}]

def run_bash(command: str) -> str:
try:
res = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=30)
return res.stdout + res.stderr
except subprocess.TimeoutExpired:
return "Error: Command timed out after 30 seconds."

def agent_loop(query: str):
messages = [{"role": "user", "content": query}]

while True:
# 1. 触发 LLM 推理
response = client.messages.create(
model=MODEL,
system=SYSTEM_PROMPT,
messages=messages,
tools=TOOLS,
max_tokens=8000
)
# 必须将 assistant 的回复原样压入消息栈
messages.append({"role": "assistant", "content": response.content})

# 2. 终止判定:模型不再请求调用工具
if response.stop_reason != "tool_use":
for block in response.content:
if hasattr(block, "text"):
print(f"\n[Agent 答复]: {block.text}")
return

# 3. 收集并执行所有的工具调用
tool_results = []
for block in response.content:
if block.type == "tool_use":
print(f"[执行命令] > {block.input['command']}")
output = run_bash(block.input["command"])
print(f"[输出截取] < {output[:150]}...")

tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": output
})

# 4. 将工具结果打包为 user 角色消息回传
messages.append({"role": "user", "content": tool_results})

if __name__ == "__main__":
task = input("请输入编程任务: ").strip()
agent_loop(task)

核心设计要点剖析

上述 30 行代码虽然简短,却包含了 Agent 状态机的三个黄金法则:

1. messages 上下文是追加而非覆盖

每一轮循环中,模型的工具请求响应(包含 tool_use 块)和本地执行的结果(tool_result 块)都必须按序累加追加到消息数组中。模型之所以能感知自己的执行历史、基于上一轮的报错做出纠偏,完全依赖这个累积的消息栈。

2. stop_reason 是唯一的出口

从聊天机器人(Chatbot)跃迁到智能体(Agent),最核心的技术分水岭就在于退出的判断逻辑:

  • 普通 Chatbot:模型吐完一句话(stop_reason == "end_turn")就将控制权抛回给终端用户;
  • 自主 Agent:只要模型返回了 stop_reason == "tool_use",系统就会剥夺用户的介入权,自动执行工具并进入下一轮循环,直到模型自主判断任务彻底完成。

3. tool_use_id 物理强对齐

Anthropic Messages API 规定:回传的每个 tool_result 对象中,必须显式携带对应的 tool_use_id。如果一个响应里模型并行发起了多个工具调用,返回给模型的 tool_result 必须一一对应,否则 API 会抛出 invalid_request_error 导致会话中断。


生产源码穿透:Claude Code 的 1,729 行状态机

上面的 30 行代码覆盖了核心的 Happy Path。但在工业级实践中,进程会遭遇网络抖动、Token 超限、文件死锁或输出截断。

在 Claude Code 的开源源码架构中,Agent 核心循环被拆分为两层:

  1. src/QueryEngine.ts(会话生命周期层):
    持有会话实例的 mutableMessages[]、全局取消信号 abortController、文件读取内存缓存 readFileState 以及 Token 用量追踪器 totalUsage。
  2. src/query.ts(单轮查询循环层):
    一个长达 1,729 行的 while(true) 状态机,负责模型流式调用、工具分发、异常捕获与深层次自动压缩。

queryLoop 的六大状态转移

在 src/query.ts 中,每轮迭代都会计算 state.transition,指导循环的走向:

转移类型(transition.reason) 触发边界 生产级恢复策略
next_turn 正常返回 tool_use 执行工具并回填结果,继续进入下一轮推理
collapse_drain_retry 上下文触发 prompt-too-long 第一级防御:轻量折叠已归档的历史摘要缓存,然后静默重试
reactive_compact_retry 折叠后依然超长 第二级防御:对整个会话历史发起一次紧急 LLM 深度摘要,压缩后重试
max_output_tokens_escalate 生成内容触碰 8k 输出上限截断 自动将 max_tokens 参数从 8,000 升级到 64,000 后重试
max_output_tokens_recovery 升级到 64k 后依然生成超限 在下一轮中注入 [system: 请从刚才截断处继续输出] 提示词引导多轮拼接
stop_hook_blocking 提交前执行代码 Lint/单测失败 拦截退出请求,将错误栈注入为用户消息,强制模型修复坏味道
completed 任务完成且 Hook 校验通过 正常结束并返回结果

恢复链设计亮点:输出截断与超长重试

在生产编码中,最容易让初学者编写的 Agent 挂死的问题是长文件输出被截断:

这种自愈机制让 Claude Code 即使在重构几百行的复杂文件时,也不会因为单次请求上限而留下半截残缺代码。


架构演进视角:从 REPL 到 Agent

回看软件工程的交互演化脉络:

$$\text{Lisp REPL (1960)} \longrightarrow \text{Unix Shell (1971)} \longrightarrow \text{Jupyter Notebook (2014)} \longrightarrow \text{Claude Code (2025)}$$

所有这些系统的核心架构完全一脉相承:Read $\rightarrow$ Eval $\rightarrow$ Print $\rightarrow$ Loop。

  • Shell 的 Eval 执行的是系统命令;
  • Jupyter 的 Eval 执行的是 Python 解释器;
  • 而 Coding Agent 的 Eval,执行的是 “大语言模型推理 + 物理工具调度”。

这印证了一个底层事实:while True 循环不是一个简陋的临时代码,它就是智能体系统的本质骨架。

下一篇我们将探讨如何摆脱单调的 Bash 执行:构建安全可扩展的 Tool Use 与 Dispatch Map 机制。