S01: The Agent Loop 智能体循环
"One loop & Bash is all you need" -- 一个工具 + 一个循环 = 一个智能体。
Harness 层: 循环 -- 模型与真实世界的第一道连接。
🎯 问题
语言模型能推理代码,但碰不到真实世界 -- 不能读文件、跑测试、看报错。没有循环,每次工具调用你都得手动把结果粘回去。你自己就是那个循环。
💡 解决方案
Claude Code 的核心是一个无限循环,持续运行直到模型不再调用工具:
+--------+ +-------+ +---------+
| User | ---> | LLM | ---> | Tool |
| prompt | | | | execute |
+--------+ +---+---+ +----+----+
^ |
| tool_result |
+----------------+
(loop until stop_reason != "tool_use")
🔍 源码分析
QueryEngine.ts 核心结构
TypeScript
// 源码位置: src/QueryEngine.ts
export type QueryEngineConfig = {
cwd: string // 工作目录
tools: Tools // 可用工具
commands: Command[] // 斜杠命令
mcpClients: MCPServerConnection[] // MCP 客户端
agents: AgentDefinition[] // Agent 定义
canUseTool: CanUseToolFn // 工具可用性检查
getAppState: () => AppState // 状态获取
setAppState: (f) => void // 状态更新
// ...
}
Agent Loop 伪代码
TypeScript
async function agentLoop(query: string, config: QueryEngineConfig) {
const messages: Message[] = [
{ role: 'user', content: query }
]
while (true) {
// 1. 调用 LLM
const response = await anthropic.messages.create({
model: config.mainLoopModel,
system: systemPrompt,
messages: messages,
tools: config.tools,
max_tokens: 8000,
})
// 2. 追加助手响应
messages.push({ role: 'assistant', content: response.content })
// 3. 检查退出条件
if (response.stop_reason !== 'tool_use') {
return messages
}
// 4. 执行工具调用
const toolResults = []
for (const block of response.content) {
if (block.type === 'tool_use') {
const output = await executeTool(block.name, block.input, config)
toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
content: output,
})
}
}
// 5. 追加工具结果
messages.push({ role: 'user', content: toolResults })
}
}
🔑 关键设计点
1. 消息累积
// 消息持续累积,形成完整对话历史
messages: Message[]
// 每次循环追加两条消息
messages.push(assistantMessage) // 助手响应
messages.push(userMessage) // 工具结果
2. 退出条件
// 只有 stop_reason 不是 "tool_use" 才退出
if (response.stop_reason !== 'tool_use') {
return // 可能是 "end_turn", "max_tokens", "stop_sequence"
}
3. 工具执行隔离
// 每个工具独立执行,结果收集后一起返回
for (const block of response.content) {
if (block.type === 'tool_use') {
const output = await executeTool(block.name, block.input)
results.push(output)
}
}
📐 设计模式
懒加载优化
启动时预取,减少首次调用延迟
Feature Flags
编译时消除未使用代码
错误重试
可重试错误自动重试
✏️ 实践练习
练习 1: 最小 Agent Loop
实现一个最小化的 Agent Loop,只支持 Bash 工具
练习 2: 添加工具支持
扩展上述实现,支持 Read/Write/Edit 工具
🤔 思考题
- 为什么使用
stop_reason而不是检查content中是否有工具调用? - 消息累积会导致什么问题?如何解决?
- 如何处理工具执行失败的情况?
📎 延伸阅读
Agent 大专题(含 生态对比):论文/热点摘要 + 技能包(Superpowers / GStack)与 Subagents vs Teams 等长文入口。