01 Agent 核心循环与循环检测
学习目标
本章要解决什么问题:Cline 的 Agent 循环如何保证不会无限空转、不会陷入重复调用?当 LLM 卡住或网络失败时,循环如何恢复?
前置知识
本章涉及 Agent 核心循环的通用原理,建议先阅读:
下文假设你已理解 Session/Turn 模型和事件驱动架构,直接聚焦 Cline 的具体实现。
项目实践
核心循环结构
Cline 的 Agent 循环实现在 Task 类中,采用 while (!abort) 结构。调用链:
initiateTaskLoop() └── while (!abort) └── recursivelyMakeClineRequests() ├── attemptApiRequest() → 流式调用 LLM └── presentAssistantMessage() → 解析并执行工具调用关键设计:每次 recursivelyMakeClineRequests 返回后,循环判断是否继续。如果模型调用了工具但未调用 attempt_completion(完成信号),循环自动进入下一轮,用 noToolsUsed 提示推动模型继续。
noToolsUsed 推动机制
当模型既不调用工具也不完成任务时,Cline 不会等待用户介入,而是自动注入引导提示词:
# 伪代码:推动逻辑if (parsedMessage.toolCalls.length === 0 && !message.isComplete) { systemPrompt += "你必须调用工具来推进任务..." continue // 进入下一轮循环}这保证了 Agent 不会因为模型”只说不动”而停滞。
循环检测(loop-detection.ts)
Cline 使用两级阈值检测模型是否陷入重复调用:
检测维度:
- 工具名称完全匹配
- 参数 SHA-256 哈希匹配
- 连续调用计数(非累计)
升级策略:
- 达到软阈值(3 次):向下一轮请求注入”你正在重复调用同一工具,请改变策略”
- 达到硬阈值(5 次):调用
abortTask()终止,向用户报告循环详情
流式失败重试
attemptApiRequest 中的重试策略:
退避间隔选择:2s 起步平衡了快速恢复和避免 API 限流,8s 上限避免了用户体验过差。
问题与规避
| 陷阱 | 场景 | 对策 |
|---|---|---|
| noToolsUsed 误触发 | 模型输出纯文本但意图是继续(如思考下一步) | 仅在确认无工具调用且任务未完成时注入 |
| 循环检测误杀 | 合理的重试(如网络超时重连)被判定为循环 | 软阈值给模型自我纠正机会,仅硬阈值强制终止 |
| 退避时间过长 | 连续重试导致用户等待 >14s | 3 次自动重试后降级到用户决策,不无限重试 |
设计取舍
自动推动 vs 等待用户
| 方案 | 优势 | 代价 |
|---|---|---|
| 自动推动 | 流畅、不中断、模型自主性高 | 可能产生意外行为 |
| 等待用户 | 绝对可控 | 体验差、效率低 |
Cline 选择自动推动 + 循环检测兜底:先尝试让模型自我纠正,检测到持续循环后强制终止。
两级阈值 vs 单级阈值
| 方案 | 优势 | 代价 |
|---|---|---|
| 单级(直接终止) | 实现简单 | 误杀合理重试 |
| 两级(警告 + 终止) | 给模型纠正机会 | 多消耗 Token |
Cline 选择两级,因为 3 次重复可能是合理重试,但 5 次几乎可以确认是无效循环。
参考来源
- Cline 源码:
apps/vscode/src/core/task/index.ts - Cline 源码:
apps/vscode/src/core/task/loop-detection.ts