APAC 闭环深度分析

APAC = Agent-Plan-Act-Check — xwcode 的核心执行循环


1. 三层循环架构

xwcode 在不同抽象层级上运行三个不同的循环,理解它们的关系是掌握架构的关键。

┌────────────────────────────────────────────────────────────────────┐
│ Layer 1: ConversationRuntime 外层循环 (runtime.rs)                 │
│  通用 loop { LLM call → execute tools → push results }           │
│  管理: turn 边界 / 流处理 / token 预算 / 暂停处理 / hook 系统     │
│                                                                    │
│  ┌──────────────────────────────────────────────────────────────┐ │
│  │ Layer 2: EditAgent APAC 循环 (scene_agent.rs)               │ │
│  │  Analyze → Plan → Act → Check 四阶段标签循环                │ │
│  │  v0.81.2 从状态机改为 Prompt 引导式                          │ │
│  │  阶段 ≈ 工具白名单切换 + 系统提示变化                        │ │
│  │                                                              │ │
│  │  ┌────────────────────────────────────────────────────────┐  │ │
│  │  │ Layer 3: SpecDrivenAgent 四阶段状态机                  │  │ │
│  │  │  (spec_driven_agent.rs)                               │  │ │
│  │  │  Reading → Planning → Implementing → Verifying        │  │ │
│  │  │  代码级状态机,各阶段有独立工具白名单                    │  │ │
│  │  └────────────────────────────────────────────────────────┘  │ │
│  └──────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────┘

1.1 Layer 1: ConversationRuntime 循环 (runtime.rs:449)

位置: crates/xw-runtime/src/runtime.rs, run_turn_core() 第 449 行 体量: ~4900 行,项目中最核心的文件

伪代码:

loop:
  1. 心跳检测 (heartbeat::beat)
  2. 检查取消信号 (cancelled AtomicBool)
  3. 检查最大迭代次数
  4. 上下文压缩检测 (ContextManager)
  5. 构建 LLM 请求 (RequestBuilder)
  6. 流式调用 Provider (stream_message)
  7. 解析 SSE 事件 → ContentBlock
  8. 如果有 ToolUse → execute_tool()
  9. 推送结果到 Session
  10. 检查退出条件:
      - 无工具调用 + 无计划 → 自然结束
      - has_patch + 无验证表 → 注入验证提示 (最多 3 次)
      - token 预算耗尽 → TokenBudgetExhausted
      - 达到最大迭代次数 → MaxIterationsReached

关键职责:

  • 用户 turn 边界管理(每次用户输入进入一次 run_turn_core)
  • 流 API 的 300s 事件间隔超时
  • Token 预算强制执行(max_total_tokens)
  • 失败分类 + 工具重试(指数退避,最多 10 次)
  • 基于 Cargo 诊断差异的编辑验证
  • Hook 系统(PreSession / PreTool / PostTool)
  • SIGTERM 优雅关闭(30s 超时后强制退出)

场景路由(第 469-478 行):

IntentRouter.route(user_input)
  → detect_intent() → Intent::Explore/Edit/SpecDriven
  → to_default_scenario() → Scenario::BugFix/Analysis/etc.
  → set_scenario() ← 切换到对应场景的执行路径

1.2 Layer 2: EditAgent APAC 循环 (scene_agent.rs:1802)

位置: crates/xw-runtime/src/agent/scene_agent.rs, run_apac_cycle() 第 1802 行

阶段定义 (来自 loop_engine.rs):

阶段 核心工具 自动转换条件 说明
Analyze 只读工具 + bash 连续只读≥3 且找到目标 → Plan 探索代码,定位问题
Plan 只读工具(bash 已过滤) transition_to_done 或 plan_turns≥2 → Act 制定修复方案
Act 编辑工具 + bash transition_to_done 且 has_code_changes → Check 执行代码修改
Check 同 Act cargo test 全绿 → Done; 失败→最多 3 次→回 Analyze; 不确定→有修改则 Done 验证修改
Done — — 正常结束
Aborted — — 异常终止

v0.81.2 的关键设计决策(来自 loop_engine.rs 注释):

"移除了 PhaseRouter/Gate 状态机。主循环现在是简单的 while(true) + 退出条件,与行业标准模式一致。APAC 阶段指导位于系统提示中,而非代码状态转换中。"

这意味着阶段标签是纯信息性的。循环结构是统一的,但每个阶段的 LLM 请求中包含不同的系统提示段落和工具白名单。阶段之间没有代码层面的状态转换门禁——一切依赖 LLM 的指令遵循。

自动转换逻辑(try_auto_transition() 第 1970-2018 行):

Analyze → Plan:
  - 连续只读次数 ≥ 3 且 explore_found_target = true
  - 或 analyze_total_turns > max_iterations / 3

Plan → Act:
  - transition_to_done 被 LLM 调用
  - 或 plan_phase_turns ≥ 2(强制注入编辑提示)

Act → Check:
  - transition_to_done 被 LLM 调用
  - 且 has_code_changes = true

Check 结果:
  - 通过 (PASS) → Done
  - 失败 (FAIL) → 最多 3 次重试后回到 Analyze
  - 不确定 (UNKNOWN) → 有编辑则 Done,无则 Failed

停顿检测系统(第 1882-1921 行):

阶段感知的双阈值设计:
  Analyze/Plan: stall_threshold = 10 次无编辑循环
  Act/Check:    stall_threshold = 3 次无编辑循环

恢复机制:
  - 最多 2 次"催促"机会(set_stall_recovery 注入提示)
  - 2 次后硬停止 → Aborted
  - stall_recovery_remaining 初始化为 2(v0.81.2 B3 修复)

语义停顿检测 (semantic_progress.rs):

FileCoverageStagnant:    ≥4 次循环无新文件被读取
ReadingWithoutUnderstanding: 长序列只读操作无进展
PlanningStalled:         计划阶段的工具调用模式异常

1.3 Layer 3: SpecDrivenAgent 状态机 (spec_driven_agent.rs:454)

位置: crates/xw-runtime/src/agent/spec_driven_agent.rs, run_turn() 第 454 行 体量: ~2000 行,完全独立的四阶段状态机

阶段与工具白名单:

阶段 可用工具 转换条件
Reading read_file, glob_search, grep_search, list_dir, tree_view 检测到计划文本 或 read_iterations ≥ 6
Planning 同上 has_edited = true(已在 Planning 阶段编辑)
Implementing read_file, edit_file, write_file, apply_patch, bash, grep_search has_test_run = true(cargo_test 被调用)
Verifying bash, cargo_check, cargo_test, read_file 全 PASS → done;有 FAIL → 最多 2 轮回到 Implementing

核心机制:

计划检测:
  Regex 模式: ^#{1,3}\s*(Plan|计划|方案|...)
  → 未命中则 LLM 回退分类器 (PlanClassifier)
  → 128 条 LRU 缓存, 10s 频率限制

计划锚定:
  检测到的计划文本注入 Implementing 阶段系统提示
  防止 LLM 在实现阶段偏离原计划

零工具阶段重入 (v0.27.0):
  连续 4 轮零工具调用 → 阶段重入而非死锁
  inject_tool_reminder 标志注入工具选择提示

验证失败循环 (D-3):
  Verifying 阶段 FAIL → 回到 Implementing
  max_rounds = 2 防止无限循环
  verifying_consecutive_failures 计数

2. 数据流全景

用户输入或 TaskSpec
     │
     ▼
IntentRouter / detect_intent() ──→ Scenario 选择
     │
     ▼
ScenarioAwareRouter.execute()
     │
     ├── ChatAgent ──→ 简单问答(无工具强制)
     │
     ├── AnalysisAgent ──→ 只读分析模式
     │
     ├── BugFixAgent ──→ EditAgent.run_apac_cycle()
     │     │
     │     ├─ Analyze: LLM + read-only tools → 代码理解
     │     ├─ Plan:    LLM + read-only tools → 方案制定
     │     ├─ Act:     LLM + edit tools → 代码修改
     │     └─ Check:   LLM + cargo tools → 验证
     │           │
     │           └── EvaluationReport + ExecutionReport (后验评估)
     │
     ├── SpecDrivenAgent ──→ own 4-phase state machine
     │     │
     │     ├─ Reading: 理解 specs
     │     ├─ Planning: 制定实施计划
     │     ├─ Implementing: 编码实现
     │     └─ Verifying: 测试验证
     │
     ├── BenchmarkAgent ──→ SWE-bench 评估模式
     │
     ├── ArchitectureAgent ──→ 架构设计与任务分解
     │
     └── TestingAgent ──→ 测试设计与执行
           │
           ▼
     DynamicCoordinator (orchestrator.rs)
       └─ LLM → TaskPlan → DAG 并行执行 → WorkerPool

3. 评估与报告体系

3.1 ExecutionReport (execution_report.rs)

构建方式: 后验聚合 —— 通过重放 EventLog 构建

v0.81.7 新增:

bash 编辑模式检测:
  - heredoc:    cat >> <file> << 'EOF'
  - redirect:   echo/printf ... >> <file>
  - tee:        echo ... | tee -a <file>
  - python:     python3 -c "..." 写入文件
  - sed -i:     sed -i 's/.../.../' <file>

评估衰减:
  effective_edits = edit_file_edits + bash_edits * 0.5
  bash 编辑权重仅为 edit_file 的一半(鼓励使用结构化编辑工具)

3.2 EvaluationReport (evaluation_report.rs)

评分机制:

A: ≥90% 验收条件满足 — 完美
B: ≥75% — 良好(小瑕疵)
C: ≥50% — 及格(功能完成但方法欠佳)
D: ≥30% — 不足
F: <30% 或 0 edits — 失败

条件匹配:

  • 第一层: 子串匹配(字符串包含)
  • 第二层: LLM 回退分类(EvaluationJudge 用 temperature=0, max_tokens=10 判断)

3.3 VerificationProvider (verification_provider.rs)

验证器 执行命令 使用场景
RustVerifier cargo fmt --check + clippy -D warnings + cargo test --workspace SDD 模式的 Verifying 阶段
GenericVerifier 用户配置的 verify_cmd 非 Rust 项目

4. 风险项与反模式

R1: 双循环架构重复(高优先级)

ConversationRuntime(runtime.rs)和 EditAgent(scene_agent.rs)各自实现独立的 LLM→工具循环。

问题: 两套循环共享同样的底层模式(LLM 调用→解析→工具执行→结果推送),但代码完全独立。一处循环中的修复不会自动惠及另一处。

示例: v0.81.4 在 EditAgent 中修复了工具错误与 cancelled 状态解耦的问题,但 ConversationRuntime 中的同类问题需要独立排查。

R2: "无状态阶段"设计依赖模型遵循度(中优先级)

APAC 阶段标签本质上是装饰性的——循环结构是统一的 while(true),阶段通过系统提示内容变化来体现。

问题: 阶段转换的正确性完全取决于 LLM 的指令遵循能力。对于 flash 类小模型(已知弱跟随性),这可能导致阶段转换异常——例如在 Analyze 阶段就开始编辑,或在 Plan 阶段停留过久。

v0.81.2 修复: 增加了 explore_found_target 标志和自动转换启发式,一定程度上缓解了 LLM 不遵循提示的问题。但这意味着自动转换规则(如"连续只读≥3→Plan")是硬编码的,可能与 LLM 的实际进展不同步。

R3: 计划检测的机会性(中优先级)

SpecDrivenAgent 通过正则表达式(^#{1,3}\s*(Plan|计划|...))+ LLM 回退(PlanClassifier)来检测计划输出。

问题:

  • 正则模式只能检测 Markdown 标题格式的计划——如果 LLM 以其他格式输出计划则遗漏
  • LLM 回退分类器有 10s 频率限制和 128 条 LRU 缓存,但每次回退调用消耗 token
  • EditAgent 的 Plan 阶段根本没有正式的计划格式验证——转换发生在启用自动转换时

R4: EditAgent Check 阶段不完备(低优先级)

run_scoped_check() 仅运行 cargo check 和 cargo test,不运行 cargo fmt --check 和 clippy -D warnings。

设计理由: 格式化和 clippy 检查留给 pre-push hook,避免在迭代循环中增加不必要的等待时间。

风险: 代码可能通过 APAC Check 但被 pre-push hook 拦截,导致循环需要额外修复轮次。

R5: SpecDrivenAgent 潜在无限循环(低优先级)

虽然大多数循环有上限(max_rounds=2, max_read_iterations=6, max_phase_iterations=15),但以下场景可能产生问题:

  • 零工具阶段重入: 如果 LLM 持续产生无工具响应,zero_tool_consecutive 的 4 次阈值可能反复触发
  • 验证失败→Implementing 重入受 max_rounds 限制,但 round 计数只增不减,理论上状态机不会无限循环

R6: 评估框架的 bash 编辑检测可能误判(低优先级)

v0.81.7 F1 通过正则模式在 ToolOutput 中检测 bash 编辑。

风险: echo 命令可能被误判(设计上仅匹配 >> 重定向,但仍可能有误报)。sed -i 在不修改代码文件时(如修改 .gitignore)也会被计为一次编辑。


5. 改进建议

编号 建议 涉及组件 优先级
S1 统一 ConversationRuntime 和 EditAgent 的循环内核,消除重复 runtime.rs, scene_agent.rs P1
S2 在 APAC 阶段转换点增加轻量级代码校验(如 Check 阶段强制检查输出文件存在性),降低对模型遵循度的依赖 scene_agent.rs P1
S3 正式化 Plan 阶段输出格式(如 JSON 结构化计划),用代码验证而非仅靠 prompt spec_driven_agent.rs P2
S4 为 bash 编辑检测增加白名单路径过滤,只在 crates/ 和 src/ 中的文件变更才计入 execution_report.rs P2
S5 抽象 LoopRunner 特质,让三个循环共享相同的监控/暂停/恢复基础设施 runtime.rs + scene_agent.rs + spec_driven_agent.rs P2

附录: 关键代码路径速查

功能 文件 行号 函数
APAC 循环入口 scene_agent.rs 1802 EditAgent::run_apac_cycle()
自动转换逻辑 scene_agent.rs 1970 try_auto_transition()
停顿检测 scene_agent.rs 1882 detect_stall_v2()
外层循环 runtime.rs 449 run_turn_core()
SDD 状态机入口 spec_driven_agent.rs 454 SpecDrivenAgent::run_turn()
阶段转换 spec_driven_agent.rs 1248 check_transition()
计划检测 spec_driven_agent.rs 147 plan_regex()
计划分类器 spec_driven_agent.rs 158 PlanClassifier
循环监控 loop_monitor.rs 86 record_tool_call()
语义停顿 semantic_progress.rs — FileCoverageStagnant / ReadingWithoutUnderstanding
评估报告 evaluation_report.rs — compute_grade()
执行报告 execution_report.rs 97 from_replay()
bash 编辑检测 execution_report.rs (v0.81.7) 扫描 ToolOutput 事件
验证 provider verification_provider.rs — RustVerifier / GenericVerifier
Orchestrator orchestrator.rs — DynamicCoordinator::execute_plan()
工具执行 xw-tools/src/executor.rs — execute_tool()
工具权限 xw-tools/src/permission.rs — PermissionEnforcer