循环检测

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 同一文件是正常操作):

主阈值

分类WarnTerm说明
Exploration50100大代码库分析 40+ 文件正常
Action2550大重构 edit 同文件 20+ 次正常
IdenticalCall47完全相同调用 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 StopCell 生命周期结束排空

引擎不因 counter 阈值主动终止(INV-TERM-01)——没有轮次上限、没有 token 上限、没有时间上限。


CycleDetect 覆盖

委派子 Run 可以使用更严格的阈值:

events, err := cell.Runtime().Run(ctx, AppRunRequest{
    // ...
    CycleDetectOverride: &CycleDetectConfig{
        ActionTermThreshold:        40,
        IdenticalCallTermThreshold: 5,
    },
})

子 Run 的任务更聚焦,较低的阈值可以更快检测死循环。


最佳实践

  1. 信任默认配置:wescode 的阈值已为编程场景优化
  2. 不要降低 IdenticalCallTermThreshold:7 次完全相同的调用是真循环
  3. 关注 HITL 提示:当 AI 卡住时引擎会提示你
  4. ErrorStreak 也会提示:不只是死循环,连续失败也会被检测