LEARN CLAUDE CODE

S01: Agent Loop — 30 行代码构成整个 Agent

这一课解决什么问题

大语言模型能推理、能写代码,但它碰不到真实世界。你问它「帮我创建一个文件」,它只会输出文本告诉你怎么做,却没法真的执行 touch file.txt。模型被关在一个只能说话的牢笼里。

Agent Loop 要做的就是打破这个牢笼:让模型能持续跟外部世界交互,而不是一问一答就结束。

核心机制

整个 Agent 的骨架只有一个 while True 循环和一个退出条件:

┌─────────────────────────────────────────────────────┐ │ AGENT LOOP │ │ │ │ messages = [system_prompt, user_message] │ │ │ │ while True: │ │ ┌───────────────────────────────────────────┐ │ │ │ response = claude.send(messages) │ │ │ │ │ │ │ │ if response.stop_reason == "end_turn": │ │ │ │ break ← 模型主动说「我做完了」 │ │ │ │ │ │ │ │ if response.stop_reason == "tool_use": │ │ │ │ result = execute_tool(response.tool) │ │ │ │ messages.append(response) ← 累积 │ │ │ │ messages.append(tool_result) ← 累积 │ │ │ │ continue ← 回到循环顶部 │ │ │ └───────────────────────────────────────────┘ │ │ │ │ messages[] 是不断增长的完整对话历史 │ │ 每次 API 调用都带上全部历史 ← 这很贵但很关键 │ └─────────────────────────────────────────────────────┘

关键概念解释

为什么是 while True 而不是 for 循环?因为你无法预知模型需要几步才能完成任务。一个「帮我重构这个模块」的请求可能需要 3 步,也可能需要 30 步。循环次数由模型的 stop_reason 决定,不由人决定。

messages[] 累积机制详解

这是最容易被低估的设计。来看一个具体例子:

循环轮次messages[] 内容token 数(估算)
初始system + user_message~500
第 1 轮+ assistant(tool_call) + tool_result~1,200
第 2 轮+ assistant(tool_call) + tool_result~2,000
第 3 轮+ assistant(end_turn)~2,300

注意:messages[] 只增不减。每一轮的完整 assistant 回复和 tool_result 都保留在数组里。这确保了模型在第 N 轮能看到之前所有轮的上下文——但代价是 token 成本线性增长。这也是 S06(Context Compact)存在的原因。

对应到 Claude Code 官方的什么

QueryEngine.submitMessage()

Claude Code 源码中的核心循环在 QueryEngine 类里。它做的事情和我们的 while True 完全一样,只是多了错误处理、重试、流式输出、权限检查等生产级逻辑。

  • submitMessage() — 发送消息并进入循环
  • processToolCalls() — 执行工具调用
  • appendToMessages() — 累积消息
  • 退出条件同样是 stop_reason === "end_turn"

变更对比表

维度S01 之前(无 Agent)S01 之后(有 Agent Loop)
交互模式一问一答,单次调用持续循环,多步执行
模型能力只能输出文本可以调用工具并获取结果
退出控制调用者决定模型自己通过 stop_reason 决定
上下文单次,无记忆messages[] 累积完整历史
代码量~30 行 Python

Agent Loop 交互模拟器

点击「播放」观察 Agent Loop 的 7 个步骤如何依次执行,消息如何在 messages[] 中累积:

等待开始...
1 初始化 messages = [system_prompt, user_message]
2 发送 messages[] 给 Claude API
3 收到 response: stop_reason = "tool_use" (调用 Bash)
4 执行工具 → 结果追加到 messages[]
5 第二轮:发送更新后的 messages[] 给 API
6 收到 response: stop_reason = "end_turn"
7 break — 循环结束,返回最终结果给用户
messages[] 实时状态
// 等待开始...