第10章 Agent Loop —— Harness的心跳

上一篇我们全景扫描了开源 Harness 生态。本章我们深入核心——Agent Loop,理解 observe→think→act→feedback 的持续循环。

Agent Loop 是什么

一句话定义:Agent 的核心心跳——持续循环的感知-推理-行动机制。

Agent Loop = observe → think → act → feedback 定位:Harness 的第一层,驱动整个 Agent 运行的心跳

图表 代码
flowchart LR
    O[observe
观察环境
接收 messages] --> T[think
推理决策
Model 推理] T --> A[act
执行行动
调用工具] A --> F[feedback
收集反馈
工具输出] F -->|循环| O
flowchart LR
    O[observe
观察环境
接收 messages] --> T[think
推理决策
Model 推理] T --> A[act
执行行动
调用工具] A --> F[feedback
收集反馈
工具输出] F -->|循环| O
flowchart LR
    O[observe
观察环境
接收 messages] --> T[think
推理决策
Model 推理] T --> A[act
执行行动
调用工具] A --> F[feedback
收集反馈
工具输出] F -->|循环| O
flowchart LR
    O[observe<br/>观察环境<br/>接收 messages] --> T[think<br/>推理决策<br/>Model 推理]
    T --> A[act<br/>执行行动<br/>调用工具]
    A --> F[feedback<br/>收集反馈<br/>工具输出]
    F -->|循环| O

核心机制:

  • observe:接收 messages 数组(对话历史 + 工具输出)
  • think:Model 推理,决定下一步行动(工具调用或结束对话)
  • act:执行工具调用(Bash、Read、Write、Edit)
  • feedback:收集工具输出,追加到 messages 数组

Agent Loop 的核心组件

1. messages 数组 —— 对话历史存储

一句话定义:存储完整对话历史的数组——用户消息 + Model 响应 + 工具输出。

图表 代码
graph LR
    subgraph MessagesArray["messages 数组"]
        M1[用户消息1
role: user] M2[Model 响应1
role: assistant
tool_use] M3[工具输出1
role: user
tool_result] M4[Model 响应2
role: assistant
tool_use] M5[工具输出2
role: user
tool_result] M6[Model 响应3
role: assistant
end_turn] end M1 --> M2 --> M3 --> M4 --> M5 --> M6
graph LR
    subgraph MessagesArray["messages 数组"]
        M1[用户消息1
role: user] M2[Model 响应1
role: assistant
tool_use] M3[工具输出1
role: user
tool_result] M4[Model 响应2
role: assistant
tool_use] M5[工具输出2
role: user
tool_result] M6[Model 响应3
role: assistant
end_turn] end M1 --> M2 --> M3 --> M4 --> M5 --> M6
graph LR
    subgraph MessagesArray["messages 数组"]
        M1[用户消息1
role: user] M2[Model 响应1
role: assistant
tool_use] M3[工具输出1
role: user
tool_result] M4[Model 响应2
role: assistant
tool_use] M5[工具输出2
role: user
tool_result] M6[Model 响应3
role: assistant
end_turn] end M1 --> M2 --> M3 --> M4 --> M5 --> M6
graph LR
    subgraph MessagesArray["messages 数组"]
        M1[用户消息1<br/>role: user]
        M2[Model 响应1<br/>role: assistant<br/>tool_use]
        M3[工具输出1<br/>role: user<br/>tool_result]
        M4[Model 响应2<br/>role: assistant<br/>tool_use]
        M5[工具输出2<br/>role: user<br/>tool_result]
        M6[Model 响应3<br/>role: assistant<br/>end_turn]
    end
    
    M1 --> M2 --> M3 --> M4 --> M5 --> M6

消息结构:

消息类型rolecontent示例
用户消息user文本或结构化内容{"role": "user", "content": "帮我写一个测试"}
Model 响应(工具调用)assistanttool_use 信息{"role": "assistant", "tool_use": {"name": "Read"}}
工具输出user工具执行结果{"role": "user", "content": "文件内容..."}
Model 响应(结束)assistant最终回答{"role": "assistant", "content": "测试已写好"}

2. Model 推理 —— 决策引擎

一句话定义:Model 根据 messages 数组推理,决定下一步行动。

图表 代码
graph LR
    Messages[messages 数组
对话历史] --> Model[Claude/GPT
推理引擎] Model --> Decision{决策} Decision -->|需要工具| ToolUse[tool_use
调用工具] Decision -->|任务完成| EndTurn[end_turn
结束对话] ToolUse -->|追加| Messages
graph LR
    Messages[messages 数组
对话历史] --> Model[Claude/GPT
推理引擎] Model --> Decision{决策} Decision -->|需要工具| ToolUse[tool_use
调用工具] Decision -->|任务完成| EndTurn[end_turn
结束对话] ToolUse -->|追加| Messages
graph LR
    Messages[messages 数组
对话历史] --> Model[Claude/GPT
推理引擎] Model --> Decision{决策} Decision -->|需要工具| ToolUse[tool_use
调用工具] Decision -->|任务完成| EndTurn[end_turn
结束对话] ToolUse -->|追加| Messages
graph LR
    Messages[messages 数组<br/>对话历史] --> Model[Claude/GPT<br/>推理引擎]
    Model --> Decision{决策}
    Decision -->|需要工具| ToolUse[tool_use<br/>调用工具]
    Decision -->|任务完成| EndTurn[end_turn<br/>结束对话]
    
    ToolUse -->|追加| Messages

推理流程:

  1. Model 接收 messages 数组作为输入
  2. Model 推理当前任务进展
  3. Model 判断下一步需要什么(工具调用或结束)
  4. Model 输出 stop_reason(tool_use 或 end_turn)

3. stop_reason —— 循环控制信号

一句话定义:Model 输出的控制信号——决定继续循环还是结束对话。

stop_reason含义Agent 行为
tool_use需要工具调用执行工具,收集结果,追加到 messages,继续循环
end_turn任务完成返回最终结果,结束对话
图表 代码
graph LR
    Model[Model 推理] --> Stop{stop_reason}
    Stop -->|tool_use| Loop[继续循环
执行工具] Stop -->|end_turn| End[结束对话
返回结果] Loop --> Messages[messages 数组] Messages --> Model
graph LR
    Model[Model 推理] --> Stop{stop_reason}
    Stop -->|tool_use| Loop[继续循环
执行工具] Stop -->|end_turn| End[结束对话
返回结果] Loop --> Messages[messages 数组] Messages --> Model
graph LR
    Model[Model 推理] --> Stop{stop_reason}
    Stop -->|tool_use| Loop[继续循环
执行工具] Stop -->|end_turn| End[结束对话
返回结果] Loop --> Messages[messages 数组] Messages --> Model
graph LR
    Model[Model 推理] --> Stop{stop_reason}
    Stop -->|tool_use| Loop[继续循环<br/>执行工具]
    Stop -->|end_turn| End[结束对话<br/>返回结果]
    
    Loop --> Messages[messages 数组]
    Messages --> Model

4. 工具执行 —— 行动落地

一句话定义:根据 Model 的 tool_use 决策,执行具体工具。

图表 代码
sequenceDiagram
    participant Loop as Agent Loop
    participant Model as Model 推理
    participant ToolExec as 工具执行器
    participant Env as 环境
    
    Loop->>Model: 发送 messages
    Model-->>Loop: 返回 tool_use
    Loop->>ToolExec: 执行工具
    ToolExec->>Env: 调用环境(文件系统/终端)
    Env-->>ToolExec: 返回结果
    ToolExec-->>Loop: 返回工具输出
    Loop->>Loop: 追加到 messages
sequenceDiagram
    participant Loop as Agent Loop
    participant Model as Model 推理
    participant ToolExec as 工具执行器
    participant Env as 环境
    
    Loop->>Model: 发送 messages
    Model-->>Loop: 返回 tool_use
    Loop->>ToolExec: 执行工具
    ToolExec->>Env: 调用环境(文件系统/终端)
    Env-->>ToolExec: 返回结果
    ToolExec-->>Loop: 返回工具输出
    Loop->>Loop: 追加到 messages
sequenceDiagram
    participant Loop as Agent Loop
    participant Model as Model 推理
    participant ToolExec as 工具执行器
    participant Env as 环境
    
    Loop->>Model: 发送 messages
    Model-->>Loop: 返回 tool_use
    Loop->>ToolExec: 执行工具
    ToolExec->>Env: 调用环境(文件系统/终端)
    Env-->>ToolExec: 返回结果
    ToolExec-->>Loop: 返回工具输出
    Loop->>Loop: 追加到 messages
sequenceDiagram
    participant Loop as Agent Loop
    participant Model as Model 推理
    participant ToolExec as 工具执行器
    participant Env as 环境
    
    Loop->>Model: 发送 messages
    Model-->>Loop: 返回 tool_use
    Loop->>ToolExec: 执行工具
    ToolExec->>Env: 调用环境(文件系统/终端)
    Env-->>ToolExec: 返回结果
    ToolExec-->>Loop: 返回工具输出
    Loop->>Loop: 追加到 messages

Agent Loop 的完整流程

流程详解

图表 代码
flowchart TB
    subgraph Loop["Agent Loop 完整流程"]
        A[用户消息] --> B[初始化 messages]
        B --> C[Model 推理]
        C --> D{stop_reason}
        
        D -->|tool_use| E[解析工具调用]
        E --> F[权限检查]
        
        F -->|deny| G[❌ 禁止执行
返回错误] F -->|ask| H[⚠️ 请求审批
等待确认] F -->|allow| I[执行工具] G --> J[错误信息追加] H --> J I --> K[工具输出追加] J --> L[上下文检查] K --> L L -->|溢出| M[上下文压缩] L -->|正常| N[继续循环] M --> N N --> C D -->|end_turn| O[返回最终结果] end
flowchart TB
    subgraph Loop["Agent Loop 完整流程"]
        A[用户消息] --> B[初始化 messages]
        B --> C[Model 推理]
        C --> D{stop_reason}
        
        D -->|tool_use| E[解析工具调用]
        E --> F[权限检查]
        
        F -->|deny| G[❌ 禁止执行
返回错误] F -->|ask| H[⚠️ 请求审批
等待确认] F -->|allow| I[执行工具] G --> J[错误信息追加] H --> J I --> K[工具输出追加] J --> L[上下文检查] K --> L L -->|溢出| M[上下文压缩] L -->|正常| N[继续循环] M --> N N --> C D -->|end_turn| O[返回最终结果] end
flowchart TB
    subgraph Loop["Agent Loop 完整流程"]
        A[用户消息] --> B[初始化 messages]
        B --> C[Model 推理]
        C --> D{stop_reason}
        
        D -->|tool_use| E[解析工具调用]
        E --> F[权限检查]
        
        F -->|deny| G[❌ 禁止执行
返回错误] F -->|ask| H[⚠️ 请求审批
等待确认] F -->|allow| I[执行工具] G --> J[错误信息追加] H --> J I --> K[工具输出追加] J --> L[上下文检查] K --> L L -->|溢出| M[上下文压缩] L -->|正常| N[继续循环] M --> N N --> C D -->|end_turn| O[返回最终结果] end
flowchart TB
    subgraph Loop["Agent Loop 完整流程"]
        A[用户消息] --> B[初始化 messages]
        B --> C[Model 推理]
        C --> D{stop_reason}
        
        D -->|tool_use| E[解析工具调用]
        E --> F[权限检查]
        
        F -->|deny| G[❌ 禁止执行<br/>返回错误]
        F -->|ask| H[⚠️ 请求审批<br/>等待确认]
        F -->|allow| I[执行工具]
        
        G --> J[错误信息追加]
        H --> J
        I --> K[工具输出追加]
        
        J --> L[上下文检查]
        K --> L
        
        L -->|溢出| M[上下文压缩]
        L -->|正常| N[继续循环]
        M --> N
        
        N --> C
        
        D -->|end_turn| O[返回最终结果]
    end

循环计数

问题:一个任务需要多少次循环?

任务复杂度循环次数示例
简单任务2-5 次创建一个文件
中等任务5-10 次修改代码 + 测试
复杂任务10-20 次重构模块 + 测试 + 修复
大型任务20-50 次完整项目开发

循环计算:

循环次数 = 工具调用次数 + 最终响应
例如:Read + Edit + Bash + Write + end_turn = 5 次

最小 Agent Loop 实现

Python 伪代码

# 最小 Agent Loop 实现
def agent_loop(user_input, model, tools):
    messages = [{"role": "user", "content": user_input}]
    
    while True:
        # observe: 发送 messages 到 Model
        response = model.chat(messages)
        
        # think: Model 推理并返回 stop_reason
        if response.stop_reason == "end_turn":
            # 任务完成,返回最终结果
            return response.content
        
        if response.stop_reason == "tool_use":
            # act: 解析工具调用
            tool_name = response.tool_name
            tool_args = response.tool_args
            
            # 执行工具
            if tool_name in tools:
                tool_result = tools[tool_name].execute(tool_args)
            else:
                tool_result = f"Error: Unknown tool {tool_name}"
            
            # feedback: 追加结果到 messages
            messages.append({
                "role": "assistant",
                "content": response,
                "tool_use": {"name": tool_name, "args": tool_args}
            })
            messages.append({
                "role": "user",
                "content": tool_result
            })

关键设计点

图表 代码
graph TB
    subgraph Design["最小 Loop 设计要点"]
        D1[while True 循环
持续运行直到 end_turn] D2[messages 数组
追加历史,形成上下文] D3[stop_reason 判断
Model 决定继续或结束] D4[工具执行
根据 tool_use 执行具体工具] D5[结果追加
工具输出追加到 messages] end
graph TB
    subgraph Design["最小 Loop 设计要点"]
        D1[while True 循环
持续运行直到 end_turn] D2[messages 数组
追加历史,形成上下文] D3[stop_reason 判断
Model 决定继续或结束] D4[工具执行
根据 tool_use 执行具体工具] D5[结果追加
工具输出追加到 messages] end
graph TB
    subgraph Design["最小 Loop 设计要点"]
        D1[while True 循环
持续运行直到 end_turn] D2[messages 数组
追加历史,形成上下文] D3[stop_reason 判断
Model 决定继续或结束] D4[工具执行
根据 tool_use 执行具体工具] D5[结果追加
工具输出追加到 messages] end
graph TB
    subgraph Design["最小 Loop 设计要点"]
        D1[while True 循环<br/>持续运行直到 end_turn]
        D2[messages 数组<br/>追加历史,形成上下文]
        D3[stop_reason 判断<br/>Model 决定继续或结束]
        D4[工具执行<br/>根据 tool_use 执行具体工具]
        D5[结果追加<br/>工具输出追加到 messages]
    end

设计格言:

“One loop & Bash is all you need” —— 一个工具 + 一个循环 = 一个 Agent

Agent Loop 的扩展机制

管理层插入

权限检查:

# 权限检查插入 Loop
def agent_loop_with_permissions(user_input, model, tools, permissions):
    messages = [{"role": "user", "content": user_input}]
    
    while True:
        response = model.chat(messages)
        
        if response.stop_reason == "end_turn":
            return response.content
        
        if response.stop_reason == "tool_use":
            tool_name = response.tool_name
            tool_args = response.tool_args
            
            # 权限检查(管理层插入)
            permission = check_permission(tool_name, tool_args, permissions)
            
            if permission == "deny":
                tool_result = "Error: Operation denied"
            elif permission == "ask":
                tool_result = ask_user_confirmation(tool_name, tool_args)
            else:  # allow
                tool_result = tools[tool_name].execute(tool_args)
            
            messages.append({"role": "assistant", "content": response})
            messages.append({"role": "user", "content": tool_result})

Hooks 插入

PreToolUse / PostToolUse:

# Hooks 插入 Loop
def agent_loop_with_hooks(user_input, model, tools, hooks):
    messages = [{"role": "user", "content": user_input}]
    
    while True:
        response = model.chat(messages)
        
        if response.stop_reason == "end_turn":
            return response.content
        
        if response.stop_reason == "tool_use":
            tool_name = response.tool_name
            tool_args = response.tool_args
            
            # PreToolUse Hook
            modified_args = run_hooks("PreToolUse", tool_name, tool_args, hooks)
            
            # 执行工具
            tool_result = tools[tool_name].execute(modified_args)
            
            # PostToolUse Hook
            modified_result = run_hooks("PostToolUse", tool_name, tool_result, hooks)
            
            messages.append({"role": "assistant", "content": response})
            messages.append({"role": "user", "content": modified_result})

设计格言:

“挂在循环上, 不写进循环里” —— 在工具前后留插口,不改主循环也能扩展

Agent Loop 的上下文管理

上下文溢出问题

问题:messages 数组持续增长,超过 Model 的上下文窗口限制。

Model上下文窗口messages 数组增长
Claude 4.x200K tokens每 5-10 个循环可能溢出
GPT-4128K tokens更容易溢出
图表 代码
graph LR
    Messages[messages 数组
持续增长] -->|接近上限| Overflow[上下文溢出
超出窗口限制] Overflow -->|触发| Compression[上下文压缩
释放空间] Compression -->|继续| Loop[继续循环]
graph LR
    Messages[messages 数组
持续增长] -->|接近上限| Overflow[上下文溢出
超出窗口限制] Overflow -->|触发| Compression[上下文压缩
释放空间] Compression -->|继续| Loop[继续循环]
graph LR
    Messages[messages 数组
持续增长] -->|接近上限| Overflow[上下文溢出
超出窗口限制] Overflow -->|触发| Compression[上下文压缩
释放空间] Compression -->|继续| Loop[继续循环]
graph LR
    Messages[messages 数组<br/>持续增长] -->|接近上限| Overflow[上下文溢出<br/>超出窗口限制]
    Overflow -->|触发| Compression[上下文压缩<br/>释放空间]
    Compression -->|继续| Loop[继续循环]

上下文压缩触发

触发时机:

  • messages 数组长度超过阈值
  • Token 计数接近上下文窗口上限
  • 用户手动触发压缩(/compact 命令)

压缩策略:详见第十三章(上下文管理)。

Agent Loop 的并发执行

多工具并发调用

问题:Model 可以在一次响应中调用多个工具,如何并发执行?

图表 代码
graph TB
    Model[Model 响应
多个 tool_use] --> Parse[解析工具列表] Parse --> Parallel[并发执行] Parallel --> T1[工具1] Parallel --> T2[工具2] Parallel --> T3[工具3] T1 --> Collect[收集所有结果] T2 --> Collect T3 --> Collect Collect --> Append[追加到 messages]
graph TB
    Model[Model 响应
多个 tool_use] --> Parse[解析工具列表] Parse --> Parallel[并发执行] Parallel --> T1[工具1] Parallel --> T2[工具2] Parallel --> T3[工具3] T1 --> Collect[收集所有结果] T2 --> Collect T3 --> Collect Collect --> Append[追加到 messages]
graph TB
    Model[Model 响应
多个 tool_use] --> Parse[解析工具列表] Parse --> Parallel[并发执行] Parallel --> T1[工具1] Parallel --> T2[工具2] Parallel --> T3[工具3] T1 --> Collect[收集所有结果] T2 --> Collect T3 --> Collect Collect --> Append[追加到 messages]
graph TB
    Model[Model 响应<br/>多个 tool_use] --> Parse[解析工具列表]
    Parse --> Parallel[并发执行]
    
    Parallel --> T1[工具1]
    Parallel --> T2[工具2]
    Parallel --> T3[工具3]
    
    T1 --> Collect[收集所有结果]
    T2 --> Collect
    T3 --> Collect
    
    Collect --> Append[追加到 messages]

并发示例:

# Model 返回多个 tool_use
response = {
    "stop_reason": "tool_use",
    "tool_uses": [
        {"name": "Read", "args": {"file_path": "/src/a.ts"}},
        {"name": "Read", "args": {"file_path": "/src/b.ts"}},
        {"name": "Grep", "args": {"pattern": "function"}}
    ]
}

# 并发执行
results = parallel_execute(response.tool_uses)

# 追加结果
for result in results:
    messages.append({"role": "user", "content": result})

并发限制

维度限制原因
最大并发数10-20避免资源耗尽
文件并发5-10文件系统限制
Shell 并发1-2避免 Shell 冲突

Agent Loop 的错误处理

工具执行错误

错误类型:

错误类型原因Model 处理
工具不存在tool_name 未注册Model 收到错误,推理替代方案
参数错误tool_args 格式错误Model 收到错误,修正参数重试
执行失败工具执行抛出异常Model 收到错误,调整策略
图表 代码
graph LR
    ToolCall[工具调用] --> Execute[执行]
    Execute -->|成功| Success[返回结果]
    Execute -->|失败| Error[返回错误]
    
    Error --> Model[Model 推理]
    Model -->|重试| ToolCall
    Model -->|换路子| Alternative[替代方案]
graph LR
    ToolCall[工具调用] --> Execute[执行]
    Execute -->|成功| Success[返回结果]
    Execute -->|失败| Error[返回错误]
    
    Error --> Model[Model 推理]
    Model -->|重试| ToolCall
    Model -->|换路子| Alternative[替代方案]
graph LR
    ToolCall[工具调用] --> Execute[执行]
    Execute -->|成功| Success[返回结果]
    Execute -->|失败| Error[返回错误]
    
    Error --> Model[Model 推理]
    Model -->|重试| ToolCall
    Model -->|换路子| Alternative[替代方案]
graph LR
    ToolCall[工具调用] --> Execute[执行]
    Execute -->|成功| Success[返回结果]
    Execute -->|失败| Error[返回错误]
    
    Error --> Model[Model 推理]
    Model -->|重试| ToolCall
    Model -->|换路子| Alternative[替代方案]

设计格言:

“错误不是终点, 是重试的起点” —— 出错时会重试、腾空间、换路子

Loop 崩溃恢复

崩溃场景:

  • Model API 超时
  • 工具执行崩溃
  • 系统异常

恢复机制:

  • 检查点(保存 messages 数组)
  • 重试机制(最多 3 次)
  • 错误报告(记录崩溃原因)

Agent Loop 的性能考量

循环延迟

延迟来源延迟时间优化方法
Model API1-5 秒缓存、模型选择
工具执行0.1-30 秒并发执行、后台执行
权限审批5-60 秒(用户响应)批量审批、预设权限

总延迟计算:

总延迟 = 循环次数 × (Model延迟 + 工具延迟 + 权限延迟)
例如:10次循环 × (2秒 + 1秒 + 0秒) = 30秒

循环优化

优化方法效果代价
并发工具执行减少 50% 循环时间需并发支持
后台执行用户感知延迟降低需后台机制
上下文压缩减少 Model 延迟可能丢失历史
子 Agent 隔离减少主 Loop 复杂度增加通信开销

本章小结

  • Agent Loop 定义:observe→think→act→feedback 的持续循环
  • 核心组件:messages数组、Model推理、stop_reason、工具执行
  • 完整流程:用户消息→Model推理→stop_reason判断→工具执行→结果追加→循环
  • 最小实现:while True循环 + messages数组 + stop_reason判断 + 工具执行
  • 扩展机制:权限检查、Hooks、上下文压缩插入Loop
  • 并发执行:Model可返回多个tool_use,并发执行提升效率
  • 错误处理:工具错误→Model推理→重试或换路子
  • 性能考量:循环延迟 = Model延迟 + 工具延迟 + 权限延迟

预告:下一篇我们深入工具系统——从Bash到MCP的工具分发机制。



相关内容

如果这篇文章对你有帮助,欢迎请我喝杯咖啡 ☕
Cygnus Yang 支付宝支付宝
Cygnus Yang 微信微信