AG-12_Agent 主循环:那个 while-loop 和它的护城河
Agent 主循环:那个 while-loop 和它的护城河
所有 AI Agent 的核心都是一个 while-loop。调模型,跑工具,把结果喂回去,再调模型。听起来简单到令人怀疑——但正是这个简单的循环,构成了今天最强编程助手最深的工程护城河。
前言
如果你去问任何一个 AI Agent 的架构师「你们的核心是什么」,他们大概率会指着一段伪代码说:「就这个 while-loop。」
然后你会觉得很失望。一个循环?一个 while(true)?这就是传说中的 AI Agent?
是的。但这就像说「火箭引擎就是个管子往后面喷火」一样——技术上正确,但完全忽略了工程实现中的复杂度。
在上一篇文章中,我们从高空俯瞰了 Claude Code 的 25 个子系统。今天,我们要深入到整个系统的绝对核心——Agent 主循环。这个循环是 Claude Code 乃至所有 Agent 系统的心脏,理解它,就理解了 Agent 工程的本质。
Agent Loop 的本质

在深入源码之前,让我们先从理论层面理解 Agent Loop 的本质。
2023 年,Yao 等人在 ICLR 上发表的 ReAct 论文提出了一个关键洞察:LLM 的推理(Reasoning)和行动(Acting)应该交替进行,而不是分离。这个洞察直接催生了现代 Agent Loop 的基本范式:
while (任务未完成) {
1. 思考(Think)—— LLM 分析当前状态,决定下一步行动
2. 行动(Act)—— 执行一个工具调用
3. 观察(Observe)—— 获取工具执行的结果
4. 将观察结果反馈给 LLM
}
这个循环的精妙之处在于:每一轮迭代都在扩展 Agent 的知识。第一轮,Agent 可能只知道用户的指令;第二轮,它可能已经读取了相关文件;第三轮,它可能已经执行了搜索命令并获得了结果。每一轮的观察结果都成为下一轮推理的输入。
Claude Code 的 Agent Loop 在此基础上进行了大量工程化改造,但核心逻辑完全一致。
Claude Code 的核心循环实现

让我们直接看源码。以下是根据 Claude Code v2.1.88 源码还原的核心循环实现:
// agent-loop/core.ts - Agent 主循环核心实现(简化还原)
// 这是整个 Claude Code 最核心的代码,所有功能都围绕这个循环展开
interface AgentLoopState {
messages: Message[]; // 完整的对话历史
context: ContextWindow; // 当前上下文窗口(经过压缩)
turnCount: number; // 当前轮次
totalTokens: number; // 累计消耗的 Token 数
pendingToolCalls: ToolCall[]; // 等待执行的工具调用
}
class AgentLoop {
private state: AgentLoopState;
private config: AgentConfig;
private tools: ToolRegistry;
private permissions: PermissionPolicy;
private contextManager: ContextManager;
private apiClient: APIClient;
async run(initialPrompt: string): Promise<AgentResult> {
// 将用户的初始输入加入消息历史
this.state.messages.push({
role: 'user',
content: initialPrompt,
});
// ========== 核心循环开始 ==========
while (true) {
try {
// 第一步:上下文管理——确保消息不超过上下文窗口限制
// 这是整个循环中最关键的工程决策之一
this.state.context = await this.contextManager.compress(
this.state.messages,
this.config.maxContextTokens
);
// 第二步:调用 LLM API
// 传入压缩后的上下文和所有可用工具的定义
const response = await this.apiClient.createMessage({
model: this.config.model,
system: this.state.context.systemPrompt,
messages: this.state.context.messages,
tools: this.tools.getDefinitions(), // 注册的所有工具
max_tokens: this.config.maxOutputTokens,
stream: true, // 流式响应
});
// 第三步:处理流式响应
// Claude 可能在一次响应中混合文本和工具调用
const processedResponse = await this.processStream(response);
// 第四步:将 assistant 的响应加入消息历史
this.state.messages.push({
role: 'assistant',
content: processedResponse.content,
});
// 第五步:检查是否有工具调用需要执行
const toolCalls = extractToolCalls(processedResponse);
if (toolCalls.length === 0) {
// 没有工具调用 = LLM 认为任务完成或需要用户输入
// 将响应展示给用户,等待下一步指令
this.displayResponse(processedResponse);
const userInput = await this.waitForUserInput();
if (userInput === null) {
// 用户选择退出
return { status: 'completed', messages: this.state.messages };
}
this.state.messages.push({
role: 'user',
content: userInput,
});
continue; // 继续循环
}
// 第六步:执行所有工具调用
// 注意:某些工具调用可能需要用户确认(权限系统)
const toolResults = await this.executeToolCalls(toolCalls);
// 第七步:将工具执行结果加入消息历史
for (const result of toolResults) {
this.state.messages.push({
role: 'user', // 工具结果以 user 消息的形式传回
content: [{
type: 'tool_result',
tool_use_id: result.id,
content: result.output,
}],
});
}
// 更新轮次计数和 Token 统计
this.state.turnCount++;
this.state.totalTokens += processedResponse.usage.total_tokens;
// 第八步:安全检查——防止无限循环
if (this.state.turnCount >= this.config.maxTurns) {
this.displayWarning('达到最大轮次限制,自动停止');
return { status: 'max_turns', messages: this.state.messages };
}
} catch (error) {
// 错误处理:网络错误、API 限流、工具执行失败等
const recovered = await this.handleError(error);
if (!recovered) {
return { status: 'error', error, messages: this.state.messages };
}
// 如果恢复成功,继续循环
}
}
// ========== 核心循环结束 ==========
}
}
这段代码虽然经过简化,但已经完整呈现了 Claude Code Agent Loop 的核心逻辑。让我们逐一解析其中的关键设计决策。
循环中的状态管理

Agent Loop 的状态管理是整个系统中最微妙的部分。状态不仅包括消息历史,还包括上下文窗口、Token 计数、工具调用状态等多个维度。
// state/conversation.ts - 对话状态管理(简化还原)
// 状态管理的核心挑战:如何在有限的上下文窗口中维护尽可能多的有用信息
interface ConversationState {
// === 消息层 ===
fullHistory: Message[]; // 完整的消息历史(可能超过上下文窗口)
activeContext: Message[]; // 当前活跃的上下文(在上下文窗口内)
// === 工具状态层 ===
activeTools: Map<string, ToolExecution>; // 正在执行的工具
completedTools: ToolResult[]; // 已完成的工具结果
toolCallChain: ToolCallChain; // 工具调用链(用于调试)
// === 会话元数据 ===
sessionId: string; // 会话唯一标识
startTime: number; // 会话开始时间
turnCount: number; // 当前轮次
tokenUsage: TokenUsage; // Token 使用统计
// === 错误恢复状态 ===
lastCheckpoint: Checkpoint; // 最近一次检查点(用于会话恢复)
retryState: RetryState; // 重试状态(避免重复失败的操作)
}
class StateManager {
// 创建检查点——在关键操作前保存状态快照
// 这使得会话恢复成为可能
async checkpoint(state: ConversationState): Promise<Checkpoint> {
const snapshot: Checkpoint = {
id: generateId(),
timestamp: Date.now(),
messages: deepClone(state.fullHistory),
metadata: {
turnCount: state.turnCount,
tokenUsage: state.tokenUsage,
},
};
// 异步持久化到磁盘,不阻塞主循环
await this.persistence.save(snapshot);
return snapshot;
}
// 状态压缩——当消息历史过长时进行压缩
// 这是上下文管理的核心操作
async compress(state: ConversationState): Promise<ConversationState> {
const tokenCount = this.countTokens(state.fullHistory);
if (tokenCount <= this.config.maxContextTokens) {
return state; // 未超过限制,无需压缩
}
// 压缩策略:保留系统提示 + 最近 N 轮 + 关键工具结果摘要
const compressed = await this.contextManager.compress(state.fullHistory);
return {
...state,
activeContext: compressed,
// 注意:fullHistory 仍然保留完整历史,只是 activeContext 被压缩了
};
}
}
这里有一个重要的设计决策:fullHistory 和 activeContext 的分离。完整历史始终保留在内存中(或持久化到磁盘),但只有 activeContext 会被发送给 LLM。这种设计使得:
- 会话恢复成为可能——即使 activeContext 被压缩了,完整历史仍然可用
- 调试追踪可以回溯到任何一轮的完整状态
- 压缩是有损的但可逆的——如果需要,可以从 fullHistory 重新构建 activeContext
错误处理与重试
在生产环境中,Agent Loop 面临的错误类型远比想象中多样:
// agent-loop/error-handler.ts - 错误处理与重试策略(简化还原)
// 这个模块体现了「在生产环境中,一切都会出错」的工程信念
class AgentErrorHandler {
// 错误分类——不同类型的错误需要不同的处理策略
private classifyError(error: Error): ErrorCategory {
if (error instanceof APIRateLimitError) {
return { type: 'rate_limit', retryable: true, backoff: 'exponential' };
}
if (error instanceof APITimeoutError) {
return { type: 'timeout', retryable: true, backoff: 'linear' };
}
if (error instanceof ContextWindowExceededError) {
return { type: 'context_overflow', retryable: true, backoff: 'compress' };
}
if (error instanceof ToolExecutionError) {
// 工具执行错误需要特殊处理——可能是权限问题,也可能是工具本身的 bug
return { type: 'tool_error', retryable: this.isToolRetryable(error), backoff: 'none' };
}
if (error instanceof AuthenticationError) {
return { type: 'auth', retryable: false, backoff: 'none' };
}
return { type: 'unknown', retryable: false, backoff: 'none' };
}
async handleError(error: Error, state: AgentLoopState): Promise<RecoveryResult> {
const category = this.classifyError(error);
switch (category.type) {
case 'rate_limit':
// 限流错误:等待 Retry-After 头指定的时间后重试
const retryAfter = error.retryAfter || 60;
await this.sleep(retryAfter * 1000);
return { recovered: true, action: 'retry' };
case 'context_overflow':
// 上下文溢出:触发紧急压缩,然后重试
state.context = await this.contextManager.emergencyCompress(
state.messages,
// 紧急压缩会更激进地裁剪历史
{ aggressive: true, preserveSystemPrompt: true }
);
return { recovered: true, action: 'retry_with_compressed_context' };
case 'tool_error':
// 工具错误:将错误信息作为工具结果返回给 LLM
// 让 LLM 自己决定如何处理——这是 ReAct 模式的精髓
const toolResult: ToolResult = {
tool_use_id: error.toolCallId,
content: `Error: ${error.message}`,
is_error: true,
};
state.messages.push({
role: 'user',
content: [{ type: 'tool_result', ...toolResult }],
});
return { recovered: true, action: 'continue_with_error' };
case 'auth':
// 认证错误:无法自动恢复,需要用户介入
this.ui.displayError('认证失败,请检查 API Key 或重新登录');
return { recovered: false, action: 'abort' };
default:
// 未知错误:记录日志,尝试有限次数的重试
if (state.retryCount < this.config.maxRetries) {
state.retryCount++;
await this.sleep(1000 * state.retryCount);
return { recovered: true, action: 'retry' };
}
return { recovered: false, action: 'abort' };
}
}
}
这段代码中最值得关注的设计是工具错误的处理方式:当一个工具执行失败时,Claude Code 不会简单地重试或终止,而是将错误信息作为工具结果返回给 LLM。这体现了 ReAct 模式的一个核心优势——LLM 可以理解错误并自主决定下一步行动。比如,如果 git commit 失败因为有未暂存的更改,LLM 可能会决定先执行 git add 再重试。
代码示例:核心循环伪代码还原
为了帮助理解,让我们用更简洁的伪代码形式还原 Agent Loop 的核心逻辑:
# agent_loop_pseudocode.py - Agent 主循环伪代码
# 这段伪代码浓缩了 Claude Code Agent Loop 的核心思想
# 去掉了所有工程细节,只保留了最本质的逻辑
def agent_loop(user_message: str, tools: list[Tool]) -> str:
"""Agent 主循环 - 所有 AI Agent 的心脏"""
# 初始化状态
messages = [Message(role="user", content=user_message)]
turn_count = 0
while True:
# ===== 第一步:上下文压缩 =====
# 如果消息总 Token 数超过上下文窗口限制,进行压缩
# 压缩策略包括:消息裁剪、工具结果截断、历史摘要等
if count_tokens(messages) > MAX_CONTEXT_TOKENS:
messages = context_manager.compress(messages)
# ===== 第二步:调用 LLM =====
# 将消息历史和工具定义发送给 Claude API
# 使用流式响应以提供实时反馈
response = claude_api.create_message(
messages=messages,
tools=tools, # 所有可用工具的定义
system=SYSTEM_PROMPT, # 系统提示词
stream=True, # 流式响应
)
# ===== 第三步:解析响应 =====
# Claude 的响应可能包含文本和工具调用的混合
text_content = extract_text(response)
tool_calls = extract_tool_calls(response)
# 将 assistant 的响应加入消息历史
messages.append(Message(role="assistant", content=response.content))
# ===== 第四步:判断是否需要继续 =====
if not tool_calls:
# 没有工具调用 = LLM 认为任务完成
# 展示响应,等待用户输入
display(text_content)
user_input = wait_for_input()
if user_input is None:
return "Session ended"
messages.append(Message(role="user", content=user_input))
continue # 继续循环,处理用户的下一条消息
# ===== 第五步:执行工具调用 =====
for tool_call in tool_calls:
# 权限检查:某些操作需要用户确认
if needs_permission(tool_call):
granted = ask_user_permission(tool_call)
if not granted:
# 用户拒绝了操作,将拒绝信息反馈给 LLM
messages.append(create_tool_result(
tool_call, "Permission denied by user", is_error=True
))
continue
# 执行工具
try:
result = execute_tool(tool_call)
except ToolError as e:
# 工具执行失败,将错误信息反馈给 LLM
# LLM 会理解错误并决定下一步行动
result = create_tool_result(tool_call, str(e), is_error=True)
# 将工具结果加入消息历史
messages.append(create_tool_result(tool_call, result))
# ===== 第六步:安全检查 =====
turn_count += 1
if turn_count >= MAX_TURNS:
return "Reached maximum turns"
# 循环回到第一步,开始下一轮
这段伪代码清晰地展示了 Agent Loop 的六个核心步骤:上下文压缩 → 调用 LLM → 解析响应 → 判断继续 → 执行工具 → 安全检查。整个循环就是这六个步骤的不断重复。
为什么 while-loop 是护城河
看到这里,你可能会问:既然 Agent Loop 的逻辑这么简单,为什么说它是护城河?
答案在于:简单的循环逻辑 × 复杂的工程实现 = 巨大的护城河。
让我用一个对比表格来说明:
| 维度 | 学术论文中的 Agent Loop | Claude Code 的 Agent Loop |
|---|---|---|
| 上下文管理 | 假设无限上下文窗口 | 五层压缩管线,动态裁剪 |
| 错误处理 | 忽略或简单重试 | 分类错误 + 智能恢复 + LLM 自主决策 |
| 工具执行 | 同步、无超时 | 异步、超时、重试、并行 |
| 状态管理 | 内存中的简单列表 | 持久化、检查点、会话恢复 |
| 安全控制 | 无 | 权限系统 + 沙箱 + 审计日志 |
| 用户交互 | 一次性输入输出 | 多轮对话 + 实时反馈 + 打断 |
| 生产就绪度 | 原型级别 | 数百万用户级别 |
每一个「看起来简单」的步骤,在生产环境中都需要大量的工程工作:
- 「调用 LLM」:需要处理流式响应、超时重试、限流退避、模型切换
- 「执行工具」:需要权限校验、沙箱隔离、超时控制、并行执行
- 「将结果喂回去」:需要上下文压缩、Token 计数、消息格式转换
- 「判断是否继续」:需要处理用户中断、最大轮次限制、错误恢复
这些工程细节的累积,构成了巨大的护城河。一个团队可以复制 Claude Code 的循环逻辑,但要复制它的工程成熟度,需要数月甚至数年的迭代。
护城河的三个层次
-
技术护城河:五层上下文压缩管线、智能错误恢复、流式处理优化——这些技术实现需要深入理解 LLM 的行为特性和生产环境的约束。
-
数据护城河:数百万用户的使用数据,使得 Anthropic 可以持续优化循环中的每一个决策点——什么时候压缩、什么时候重试、什么时候询问用户。
-
迭代护城河:每一个生产环境中的 bug 和 edge case,都会被转化为循环中的防御性代码。这些代码是时间的结晶,无法被简单复制。
总结
Agent 主循环是 Claude Code 乃至所有 AI Agent 系统的核心。它看起来简单——一个 while-loop,调模型,跑工具,喂结果——但在生产环境中的工程实现却极其复杂。
通过本章的源码分析,我们可以看到:
- Agent Loop 的本质是 ReAct 模式的工程化实现:思考 → 行动 → 观察 → 重复
- 状态管理是循环中最微妙的部分:fullHistory 和 activeContext 的分离是有意为之的设计
- 错误处理不是事后添加的功能,而是循环的核心逻辑:工具错误被反馈给 LLM 让其自主决策
- 简单的循环逻辑 × 复杂的工程实现 = 巨大的护城河
在下一篇文章中,我们将聚焦于 Claude Code 的启动链路——从用户输入 claude 命令到 Agent Loop 开始运行之间,发生了什么。
参考资料
- Claude Code v2.1.88 源码分析 — 基于 2025 年 3 月泄露的 npm 包逆向分析
- Yao, S. et al. (2023). “ReAct: Synergizing Reasoning and Acting in Language Models” — ICLR 2023 — Agent Loop 的理论基础
- Anthropic (2024). “Tool Use (Function Calling) with Claude” — https://docs.anthropic.com/en/docs/tool-use — Claude 工具调用的官方文档
- Shinn, N. et al. (2023). “Reflexion: Language Agents with Verbal Reinforcement Learning” — NeurIPS 2023 — Agent 错误处理与自我反思的理论基础
- Anthropic (2025). “Claude Code Best Practices” — https://www.anthropic.com/engineering/claude-code-best-practices — Claude Code 工程实践的官方分享
本文是「Claude Code 源码深度解析」系列的第二篇。下一篇文章将聚焦于启动链路——从 claude 命令到 Agent Loop 启动之间的完整初始化过程。
本系列覆盖 AI 大模型基础、Agent 开发、MCP 协议、Skill 开发、RAG、模型微调、部署推理 七大方向,从入门到实战的全栈内容持续更新中。
所有文章的 Markdown 源文件、可运行代码、高清配图已整理成完整资料包。
👍 点赞 + ⭐ 关注,评论区扣「1」,挨个发你领取方式 👇
更多推荐


所有评论(0)