代理循环深入解析
代理循环是 go-code 执行引擎的核心。但将其描述为"循环"掩盖了它的真实本质:它是一个状态机,每次迭代代表定义良好的状态之间的转换,由模型通过 stop_reason 机制控制。
将代理循环理解为状态机,而不是简单的迭代,揭示了某些设计决策的原因以及它们如何有助于可靠性。
状态机视角
在执行的任何时刻,代理都处于定义好的状态之一。这些状态之间的转换由模型的响应决定:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 代理状态机 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌───────┐ │ │
│ │ │ 请求 │───▶│ 等待 │───▶│ 响应 │───▶│ 决策 │ │ │
│ │ │ 状态 │ │ 状态 │ │ 状态 │ │ 状态 │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └────┬────┘ │ │
│ │ ▲ │ │ │ │ │
│ │ │ │ │ │ │ │
│ │ └──────────────┴──────────────┴───────────────┘ │ │
│ │ │ │ │
│ │ │ │ │
│ │ ┌───────────────┴───────────────┐ │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ ┌─────────────────┐ ┌─────────────────────────┐ │ │
│ │ │ end_turn │ │ tool_use │ │ │
│ │ │ (终止状态) │ │ (继续状态) │ │ │
│ │ │ │ │ │ │ │
│ │ │ 返回结果给用户 │ │ 1. 权限检查 │ │ │
│ │ └─────────────────┘ │ 2. 工具执行 │ │ │
│ │ │ 3. 添加结果到历史 │ │ │
│ │ │ 4. 转换回请求状态 │ │ │
│ │ └─────────────────────────┘ │ │
│ │ │ │ │
│ └──────────────────────────────────────────────────────────────┘ │ │
│ │
│ 错误转换: │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ max_tokens ──▶ 返回带警告的截断响应 │ │
│ │ max_turns ──▶ 返回带循环终止警告 │ │
│ │ error ──▶ 返回错误给用户 │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘stop_reason 作为状态转换
API 响应中的 stop_reason 字段不仅仅是状态代码 — 它是决定代理下一步行动的状态转换信号。每个可能的值代表不同的状态转换:
end_turn:终止状态
当 stop_reason 为 end_turn(或 stop_sequence)时,模型已完成其任务。响应包含给用户的最终消息,代理循环终止。这是一个终止状态 — 不会发生进一步转换。
这个状态代表模型对用户请求已完成的置信度。Harness 不会质疑这个决定 — 它信任模型的评估并返回结果。
tool_use:继续状态
当 stop_reason 为 tool_use 时,模型已请求执行一个或多个工具来收集额外信息或执行操作。代理转换到工具执行:
权限门:每个请求的工具都会经过权限系统。如果任何工具被拒绝,其结果为错误,但循环继续。
并行执行:多个工具请求并行执行。模型可以同时请求几个操作,Harness 协调它们的执行。
结果捕获:工具结果(或错误)被捕获并添加到对话历史。
状态转换:工具执行后,代理转换回请求状态,在那里使用更新的历史构建下一个 API 请求。
max_tokens:截断状态
当 stop_reason 为 max_tokens 时,响应因达到最大 token 限制而被截断。这不是失败 — 它是一个边界条件。Harness 返回部分响应并附带警告,允许用户决定是否继续。
max_turns:循环限制状态
当代理达到最大迭代次数(MAX_TURNS = 50)仍未完成时,它会终止并发出警告。这是一个安全边界 — 见下一节了解为什么存在这个。
为什么存在 MAX_TURNS
模型没有对时间、资源约束或迭代次数的内在理解。如果让它无限期运行,代理可能:
- 重复调用同一工具进入无限循环
- 在无效策略之间振荡
- 消耗无限制的计算资源
MAX_TURNS 提供了一个硬性安全边界。在 50 次迭代后,即使行为不当的代理也会终止。这个数字是根据经验观察选择的:复杂任务很少需要超过 50 次工具调用,而简单任务在此限制之前就能完成。
该值可配置,允许需要更多迭代的用户增加限制,同时仍保持对执行时间的某些边界。
MAX_TURNS 时会发生什么
达到限制时,代理:
- 停止发出新的 API 请求
- 将累积的响应返回给用户
- 包含指示循环已终止的警告消息
用户然后可以决定是否手动继续任务或调整 MAX_TURNS 设置。
长对话的历史管理
对话历史不仅仅是日志 — 它是允许模型理解正在进行的任务的上下文。管理这个历史是 Harness 最关键的职责之一。
Token 预算问题
历史中的每条消息都消耗模型上下文窗口的 token。具有 50 轮对话、每轮 6 次工具调用可能轻松超过 100,000 token — 超过大多数模型的支持。
没有管理,代理将要么:
- 在达到 token 限制时失败
- 截断任务所需的关键上下文
三级压缩
go-code 实施了三级历史管理方法:
第一级:完整保留 — 对话早期消息完整保留。这些建立了任务上下文和初始推理。
第二级:选择性压缩 — 对话中间的消息被压缩成保留关键信息(工具调用、其结果、重要决策)同时删除冗长细节的摘要。
第三级:修剪 — 如果对话超过一定长度,旧消息被完全修剪,只保留最近的上下文。
这种分层方法确保模型始终可以访问:
- 原始任务描述
- 最近的工具执行及其结果
- 当前工作状态
同时丢弃与当前任务不再相关的信息。
为什么压缩很重要
没有压缩,系统将限制在约 8-10 次工具调用,然后达到 token 限制。通过压缩,对话可以跨越 50+ 次迭代,同时保持足够的上下文让模型完成复杂任务。
压缩是有损的 — 某些信息不可避免地被丢弃。但这必要的权衡。替代方案(无压缩、限制对话长度)限制性更强。
权限门作为安全检查点
每个工具执行在执行前都经过权限系统。这不是可选的 — 它是保护危险操作的强制检查点。
为什么是门而不是过滤器
过滤器允许危险操作通过,依靠模型避免它们。门主动拦截并阻止不符合配置策略的操作。
这个区别至关重要,因为模型以用户的完整权限运行。没有门,行为不当的模型可能:
- 删除用户目录中的所有文件
- 执行恶意 shell 命令
- 窃取敏感数据
门架构
权限门在工具调用级别操作:
级别检查:根据用户权限级别(只读、工作区写入、危险完全访问)评估请求的工具。
路径匹配:对于文件操作,glob 模式确定是否允许特定路径。
操作评估:某些操作(如删除)被视为固有危险,可能需要额外批准。
用户提示:如果启用人工在环模式且操作需要批准,在执行继续之前提示用户。
决策执行:门返回批准或拒绝。如果被拒绝,工具不执行,并向模型返回错误结果。
模型看到什么
模型看不到权限门 — 它只看到工具结果。如果工具被拒绝,模型会收到指示操作被阻止的错误结果。然后模型必须决定如何继续:尝试不同方法、请求许可,或向用户报告问题。
流式传输和实时反馈
代理循环支持流式输出,允许用户在生成响应时看到模型的响应。这不仅仅是用户体验功能 — 它提供早期进度指示。
为什么流式传输重要
没有流式传输,用户将默默等待完整响应到达 — 对于长输出可能需要许多秒。通过流式传输,用户可以:
- 看到请求已被接收并正在处理
- 观察长响应的部分进度
- 如果看起来不对,可以中断操作
流式传输如何工作
API 客户端增量接收 token,并为每个 token 增量调用回调。Harness 使用此回调来:
- 将文本输出流式传输到终端
- 在工具执行期间显示进度指示器
- 为用户提供实时反馈
循环本身继续处理完整响应 — 流式传输纯粹是显示优化。
状态机术语中的执行流程
1. 初始化
- 创建新会话历史
- 将用户消息添加到历史
- 转换到: 请求
2. 请求
- 如需要应用压缩
- 构建 API 请求(消息 + 工具)
- 发送请求到 API
- 转换到: 等待
3. 等待
- 接收流式响应
- 缓冲完整响应
- 转换到: 响应
4. 响应
- 解析 stop_reason
- 提取内容块
- 转换到: 决策
5. 决策(状态分支)
- end_turn → 终止
- tool_use → 工具执行
- max_tokens → 终止(带警告)
- max_turns → 终止(带警告)
- error → 终止(带错误)
6. 工具执行
- 对于每个 tool_use 块:
a. 权限门检查
b. 执行工具(带超时)
c. 捕获结果(或错误)
- 将所有结果添加到历史
- 转换到: 请求
7. 终止
- 格式化最终响应
- 返回给用户
- 执行结束