循环检测
wescode 中的 CycleDetector——检测 AI Agent 死循环并终止。
CycleDetector 概念
CycleDetector 是 wesgine 引擎内唯一的自动终止来源。它检测"可证明的死循环"——完全相同的操作 + 完全相同的结果重复出现。
设计哲学
CycleDetector 不是"你跑太久了"
CycleDetector 是"你在做完全一样的事情并得到完全一样的结果"
引擎不因 counter 阈值(轮次/token/时间)主动终止(INV-TERM-01)
四级工具分类
CycleDetector 对工具的分类决定了 streak 阈值和 halving 行为:
分类优先级
1. 硬编码 map(wesgine 内置 ~20 个工具)
→ read/grep/edit/exec 等有固定分类
2. ExtraCategories(消费方声明)
→ wescode 显式声明某些工具的分类
3. ToolMeta 自动推断
→ ReadOnly=true 或 Dimension=perceive → exploration
→ wescode 的 20/24 自定义工具自动推断为 exploration
4. 默认 → categoryAction
两种分类
| 分类 | 含义 | 阈值特点 |
|---|---|---|
categoryExploration | 探索/只读 | 阈值宽松 |
categoryAction | 写入/变更 | 阈值严格 |
两阶段终止协议
阶段一:自愈
首次达到 TermThreshold 时:
首次 CycleTerm 触发
→ 注入 system 纠正消息
→ 重置 streak counter
→ 给 AI 第二次机会
阶段二:HITL
第二次达到 TermThreshold 时:
第二次 CycleTerm 触发
→ HITL 问用户"怎么办?"
→ 等待用户响应(超时 60s)
→ 超时 → 自动终止 + 保存进度
wescode 阈值配置
wescode 为编程场景调高了阈值(编程时频繁 edit 同一文件是正常操作):
主阈值
| 分类 | Warn | Term | 说明 |
|---|---|---|---|
| Exploration | 50 | 100 | 大代码库分析 40+ 文件正常 |
| Action | 25 | 50 | 大重构 edit 同文件 20+ 次正常 |
| IdenticalCall | 4 | 7 | 完全相同调用 7 次是真循环 |
ToolOverrides
ToolOverrides: map[string][2]int{
"exec": {30, 60}, // exec 可能反复运行测试
"exec:readonly": {50, 100}, // 只读 exec 更宽松
"edit": {30, 60}, // 编辑同一文件多次正常
"write": {30, 60}, // 写文件多次正常
"apply_patch": {30, 60}, // 补丁多次正常
"plan": {15, 30}, // plan 操作不应太频繁
}
完整配置
cycleCfg := wesgine.CycleDetectConfig{
ExplorationWarnThreshold: 50,
ExplorationTermThreshold: 100,
ActionWarnThreshold: 25,
ActionTermThreshold: 50,
IdenticalCallWarnThreshold: 4,
IdenticalCallTermThreshold: 7,
ToolOverrides: map[string][2]int{
"exec": {30, 60},
"exec:readonly": {50, 100},
"edit": {30, 60},
"write": {30, 60},
"apply_patch": {30, 60},
"plan": {15, 30},
},
}
Halving 机制
当工具调用的参数发生变化时,streak 减半:
streak = 10,调用 edit("file.go", patch_A)
→ streak = 11
streak = 11,调用 edit("file.go", patch_B) // 参数变了!
→ streak = 11 / 2 = 5 // halving
Progress-Aware 工具
ExtraProgressAware 声明哪些工具参与 halving:
CycleDetectConfig{
ExtraProgressAware: []string{"my_mutation_tool"},
}
wescode 的大多数写入工具(read/edit/grep 等)已经通过 ToolMeta 自动推断。
ExtraCategories 声明
wescode 显式声明部分工具分类(当 ToolMeta 自动推断不适用时):
CycleDetectConfig{
ExtraCategories: map[string]wesgine.ToolCategoryHint{
"my_readonly_tool": wesgine.CategoryExploration,
},
}
wescode 的 20 个自定义只读工具已通过 BaseTool{Meta: ToolMeta{ReadOnly: true}} 自动推断为 exploration——无需额外配置。
ErrorStreak 伪进展检测
概念(INV-STREAK-01)
ErrorStreak 检测连续失败——AI 看似在工作但每次都失败:
调用 exec("go test") → 失败
调用 edit("fix.go") → 成功(看似有进展)
调用 exec("go test") → 失败(同样的错误)
调用 edit("fix.go") → 成功
调用 exec("go test") → 失败
→ ErrorStreak 检测到伪进展模式
终止流程
ErrorStreak 检测到连续失败
→ HITL 问用户"AI 似乎卡住了,继续还是停止?"
→ 超时 → 自动终止
终止源比较
| 终止源 | 触发条件 | 终止方式 |
|---|---|---|
| CycleDetector | 死循环(相同操作+结果) | 自愈 → HITL |
| ErrorStreak | 连续失败(伪进展) | HITL |
| 用户中断 | POST /sessions/{sid}/interrupt | 立即 |
| BudgetCheckFn | 外部预算耗尽 | 立即 |
| Cell Stop | Cell 生命周期结束 | 排空 |
引擎不因 counter 阈值主动终止(INV-TERM-01)——没有轮次上限、没有 token 上限、没有时间上限。
CycleDetect 覆盖
委派子 Run 可以使用更严格的阈值:
events, err := cell.Runtime().Run(ctx, AppRunRequest{
// ...
CycleDetectOverride: &CycleDetectConfig{
ActionTermThreshold: 40,
IdenticalCallTermThreshold: 5,
},
})
子 Run 的任务更聚焦,较低的阈值可以更快检测死循环。
最佳实践
- 信任默认配置:wescode 的阈值已为编程场景优化
- 不要降低 IdenticalCallTermThreshold:7 次完全相同的调用是真循环
- 关注 HITL 提示:当 AI 卡住时引擎会提示你
- ErrorStreak 也会提示:不只是死循环,连续失败也会被检测