Chapter 25: 自修复循环 -- 让 Agent 从失败中学习

Agent 会犯错。这不是假设,而是必然——LLM 的非确定性输出加上真实项目的复杂性,错误是工作常态。Ch 22 的「优雅降级」原则告诉我们系统不应该在错误面前崩溃。本章更进一步:系统不仅不崩溃,还能自动修复。这就是 VERIFY 和 CORRECT 两根支柱的协作。

关键概念:CI 验证流水线(GitHub Actions + Semgrep + PR-Agent + ArgoCD)、自修复循环(失败检测 → 修复任务 → 重试 → 升级)、三次上限与升级策略、Agent 级可观测性指标。

Agent 提交代码
     │
     ▼
╔══ VERIFY 支柱 ══════════════════════════════════════════╗
║                                                        ║
║  GitHub Actions CI ──► Semgrep 安全扫描 ──► PR-Agent   ║
║  (编译/测试/lint)       (漏洞/反模式)      (AI 审查)    ║
║                                                        ║
╠══════════════════════════════════════════════════════════╣
║              │                    │                     ║
║         全部通过              任一失败                   ║
║              │                    │                     ║
║              ▼                    ▼                     ║
║         ArgoCD 部署     ══ CORRECT 支柱 ═══════════╗   ║
║                         ║                          ║   ║
║                         ║  检测失败                 ║   ║
║                         ║     │                    ║   ║
║                         ║  attempt < 3 ?           ║   ║
║                         ║   │         │            ║   ║
║                         ║   YES       NO           ║   ║
║                         ║   │         │            ║   ║
║                         ║  创建修复   升级给人类     ║   ║
║                         ║  任务       通知          ║   ║
║                         ║   │                      ║   ║
║                         ║  Agent 读取               ║   ║
║                         ║  错误日志                  ║   ║
║                         ║   │                      ║   ║
║                         ║  修复并重新提交            ║   ║
║                         ║   │                      ║   ║
║                         ║   └──► 回到 VERIFY ──────╝   ║
║                                                        ║
╚════════════════════════════════════════════════════════╝

25.1 Agent 的代码为什么会失败

问题

一个有趣的统计:即使是经验丰富的人类开发者,Pull Request 的首次 CI 通过率也只有 60-70%。对 AI Agent 来说,这个数字通常更低——特别是在复杂项目中,Agent 可能不完全理解项目的构建配置、依赖关系和测试约定。

Agent 写的代码失败的常见原因:

  1. 编译/类型错误:Agent 引用了不存在的类型或 import 路径
  2. 测试失败:Agent 的实现逻辑有 bug,或者没有正确理解测试断言的含义
  3. Lint 违规:Agent 不了解项目特定的 lint 规则(比如 max line length、命名约定)
  4. 安全问题:Agent 使用了不安全的 API(硬编码密钥、SQL 拼接、不安全的反序列化)
  5. 依赖冲突:Agent 引入了与现有依赖不兼容的新包
  6. 构建配置:Agent 没有正确更新 build 文件(Makefile、tsconfig、pyproject.toml)

在 Ch 22 的框架中,传统做法是改提示词——「请确保代码通过 lint」「请检查所有 import 是否正确」。但 Ch 23 的第二条原则告诉我们:Agent 失败时修 Harness 不修 Prompt。提示词只能将错误率从 40% 降到 20%(概率性改善),而 CI 验证可以将 100% 的错误拦截在合并之前(确定性保障)。

问题不是「如何让 Agent 不犯错」——那是不可能的。问题是「Agent 犯错之后,系统如何自动发现并修复」。

思路

OpenHarness 用两根支柱的串联来解决这个问题:

VERIFY 支柱 负责「发现错误」——通过确定性的验证流水线,检查 Agent 的每一个产出。发现错误是修复的前提。

CORRECT 支柱 负责「修复错误」——将 VERIFY 发现的错误信息转化为新的 Agent 任务,让 Agent 自己修复自己的错误。修复是发现的自然延续。

两根支柱的分离是刻意的:VERIFY 不知道 CORRECT 的存在(它只管检查和报告),CORRECT 不知道 VERIFY 的内部逻辑(它只消费 VERIFY 的输出)。这种松耦合意味着你可以单独升级验证规则而不影响修复逻辑,也可以单独调整修复策略而不影响验证流程。


25.2 VERIFY 支柱:四层验证流水线

问题

Agent 提交了一个 Pull Request。在合并之前,需要确认代码的质量。一个简单的 npm test 能发现编译和逻辑错误,但发现不了安全问题。一个 Semgrep 扫描能发现安全反模式,但发现不了架构层面的设计问题。单一的检查手段不够全面。

思路

OpenHarness 的 VERIFY 支柱包含四层验证,从机械检查到智能审查逐步深入:

Layer 1: GitHub Actions CI            ← 确定性,秒级
         编译 → 单元测试 → lint → 类型检查
              │
              ▼ 通过
Layer 2: Semgrep 安全扫描              ← 确定性,秒级
         已知漏洞模式 → 反模式检测 → 许可证合规
              │
              ▼ 通过
Layer 3: PR-Agent AI 审查              ← 非确定性,分钟级
         代码质量 → 架构一致性 → 风格建议
              │
              ▼ 通过
Layer 4: ArgoCD 部署验证               ← 确定性,分钟级
         K8s 配置校验 → 健康检查 → 回滚门控

四层之间的顺序不是随意的——它遵循两个原则:

成本递增:Layer 1(CI)几乎零成本(GitHub 免费额度),Layer 3(PR-Agent)需要 LLM 调用(有成本)。把廉价检查放在前面,昂贵检查放在后面,用廉价检查过滤掉大部分问题,减少昂贵检查的触发次数。

确定性优先:Layer 1 和 2 是完全确定性的——同样的代码永远得到同样的结果。Layer 3 是非确定性的——AI 审查可能在不同运行中给出不同的反馈。把确定性检查放在前面,确保基线质量,再用非确定性的 AI 审查发现更深层的问题。

这和 Ch 9 的权限三层防线是同一个设计模式:白名单(确定性放行)→ 黑名单(确定性拒绝)→ 灰名单(需要判断)。VERIFY 的 Layer 1-2 对应白/黑名单(通过或失败,没有歧义),Layer 3 对应灰名单(AI 审查的建议可能需要人工判断)。

实现

每一层的具体职责:

Layer 1: GitHub Actions CI

// .github/workflows/agent-ci.yml (伪代码)
on: pull_request

jobs:
  build-and-test:
    steps:
      - checkout code
      - install dependencies
      - run: compile / tsc --noEmit
      - run: test suite (pytest / jest / go test)
      - run: lint (ruff / eslint / golangci-lint)
      - run: type check (mypy / tsc)
    
    // 输出结构化错误报告
    on_failure:
      create_artifact:
        name: "ci-error-report"
        content:
          failed_step: string       // 哪一步失败
          error_log: string         // 完整错误日志
          exit_code: int            // 退出码
          affected_files: string[]  // 涉及的文件

关键设计:失败时输出的是结构化的错误报告,不是原始日志。这个报告会被 CORRECT 支柱消费——Agent 需要理解「哪一步失败了」「错误信息是什么」「涉及哪些文件」。如果只给 Agent 一个 1000 行的原始日志,它很可能抓不住重点。结构化报告就像 Ch 16 的 System Prompt 组装——精心选择和组织信息,而不是 dump everything。

Layer 2: Semgrep 安全扫描

// Semgrep 规则集 (伪代码)
rules:
  - id: hardcoded-secret
    pattern: "password = '$VALUE'"
    severity: ERROR
    message: "Hardcoded password detected"
  
  - id: sql-injection
    pattern: "query(f'SELECT ... {$USER_INPUT} ...')"
    severity: ERROR
    message: "Potential SQL injection"
    
  - id: insecure-deserialization
    pattern: "pickle.loads($DATA)"
    severity: WARNING
    message: "pickle.loads is unsafe with untrusted data"

Semgrep 是模式匹配而非语义分析——它不理解代码的含义,只检测已知的危险模式。这意味着它的误报率较低(匹配就是匹配),但漏报率较高(变体写法可能绕过)。对 Agent 产出来说,这已经足够:Agent 倾向于生成常见模式的代码,而 Semgrep 恰好擅长检测常见模式。

Layer 3: PR-Agent AI 审查

// PR-Agent 审查配置 (伪代码)
pr_agent:
  model: claude-sonnet  // 用比 Agent 自己更经济的模型
  review_scope:
    - code_quality       // 代码质量
    - architecture       // 架构一致性
    - test_coverage      // 测试覆盖
    - naming             // 命名规范
  
  // 只在 Layer 1-2 通过后触发
  trigger: on_ci_pass
  
  // 输出结构化审查结果
  output:
    approval: APPROVE | REQUEST_CHANGES | COMMENT
    issues: [{file, line, severity, message}]

PR-Agent 用另一个 LLM 审查 Agent 的代码产出——这是 Agent 审查 Agent。听起来像是同义反复,但有两个关键区别:第一,PR-Agent 看到的是 diff 而非完整文件,视角不同;第二,PR-Agent 使用更经济的模型(Sonnet 而非 Opus),成本更低但足以发现常见问题。

这里有一个和 Ch 21 的 Dream 系统类似的设计选择:Dream 让 Agent 在「不同时间」审视自己的记忆,PR-Agent 让 Agent 在「不同角色」审视自己的代码。两者的本质都是视角切换——同一个人很难同时当作者和审稿人,但两个独立的角色可以。

Layer 4: ArgoCD 部署验证

Layer 4 只在代码涉及基础设施变更(Kubernetes manifest、Terraform 配置)时触发。ArgoCD 的 sync policy 确保集群状态与 Git 仓库一致,如果 Agent 提交了不合法的 K8s 配置,ArgoCD 会拒绝同步并报告错误。这是 Ch 23「确定性脚手架」的又一个实例——K8s 的 admission controller 不关心配置是人写的还是 Agent 写的,它只关心配置是否合法。


25.3 CORRECT 支柱:自修复循环的工程

问题

VERIFY 发现了错误。传统做法是通知人类开发者来修复。但如果错误是 Agent 犯的——比如一个 import 路径写错了——让 Agent 自己修复不是更高效吗?人类花 5 分钟看日志、找问题、改代码。Agent 花 30 秒读取错误报告,直接生成修复 patch。

但自动修复有一个致命风险:无限循环。Agent 修了 A 错误,引入了 B 错误。修了 B 错误,又引入了 C 错误。如果不设上限,系统会陷入死循环,不断消耗 API 调用和 CI 资源。

Ch 22 的断路器模式(autocompact 的 3 次连续失败上限)已经给出了答案的雏形。OpenHarness 将这个模式扩展为完整的自修复循环。

思路

自修复循环的状态机有五个状态:

                   ┌──────────────────────────────────┐
                   │                                  │
                   ▼                                  │
  ┌─────────┐  CI失败  ┌───────────┐  修复   ┌──────────┐
  │ WORKING │────────►│ FAILED    │───────►│ FIXING   │
  │ (工作中) │         │ (已失败)   │        │ (修复中)  │
  └─────────┘         └───────────┘        └──────────┘
       ▲                    │                    │
       │               attempt>=3               完成
       │                    │                    │
       │                    ▼                    │
       │              ┌───────────┐              │
       │              │ ESCALATED │              │
       │              │ (已升级)   │              │
       │              └───────────┘              │
       │                                         │
       └─────── CI通过 ◄────────────────────────┘
                    │
                    ▼
              ┌───────────┐
              │ COMPLETED │
              │ (已完成)   │
              └───────────┘

每个状态转换的触发条件和动作:

转换触发条件动作
WORKING → FAILEDCI 流水线返回失败API Server 创建自修复任务
FAILED → FIXING自修复任务被 Agent 领取Agent 读取错误报告,开始修复
FIXING → WORKINGAgent 提交修复代码触发新一轮 CI
WORKING → COMPLETEDCI 通过更新任务状态,通知人类
FAILED → ESCALATEDattempt >= 3通知人类,提供错误历史

实现

自修复任务的创建过程:

function onCIFailure(prId, ciResult) {
  task = db.getTaskByPR(prId)
  
  if task.fix_attempts >= MAX_FIX_ATTEMPTS {  // MAX = 3
    escalateToHuman(task, ciResult)
    task.status = "ESCALATED"
    db.save(task)
    return
  }
  
  // 创建自修复任务(优先级最高)
  fixTask = {
    type: "SELF_FIX",
    priority: PRIORITY_HIGHEST,  // 优先于所有新功能任务
    parent_task_id: task.id,
    fix_attempt: task.fix_attempts + 1,
    context: {
      error_report: ciResult.structured_report,
      failed_step: ciResult.failed_step,
      error_log: truncate(ciResult.raw_log, 5000),  // 截断过长日志
      previous_fixes: task.fix_history,  // 之前的修复尝试
      affected_files: ciResult.affected_files
    }
  }
  
  db.createTask(fixTask)
  task.fix_attempts += 1
  task.fix_history.append(ciResult)
  db.save(task)
}

几个关键设计决策值得展开:

自修复任务的优先级最高。原因是:如果队列中有 10 个新功能任务和 1 个修复任务,先做哪个?直觉是先做新功能(积压多),但正确答案是先修复。因为未修复的 PR 阻塞了 CI 流水线——它在 GitHub 上处于 checks-failing 状态,无法合并。如果同一仓库有其他 PR 依赖于这个分支的变更,整条链路都会被阻塞。修复一个失败的 PR 解除的阻塞,比完成一个新功能创造的价值更大。

这和操作系统的中断优先级是同一个道理:硬件中断优先于用户进程。系统的健康(CI 通过)优先于系统的扩展(新功能)。

之前的修复尝试被传入上下文previous_fixes 字段包含了前几次修复的错误报告和代码变更。这防止了一个常见的自修复陷阱:Agent 在第二次修复时做了和第一次一样的事情(因为它不知道第一次尝试过什么)。通过传入历史,Agent 知道「我上次试过 X,没用,这次要试 Y」。

这与 Ch 21 的 Dream 系统有一个深刻的结构相似性:Dream 的 Phase 2(Gather Recent Signal)收集最近的会话信息来指导整合决策。自修复循环的 previous_fixes 收集最近的修复历史来指导新的修复尝试。两者都是「从历史中学习」的模式。

错误日志被截断到 5000 字符。完整的 CI 日志可能有几万行,但大部分是冗余信息(依赖安装日志、测试框架的 banner 等)。发送完整日志给 Agent 会浪费上下文窗口。5000 字符的截断加上结构化的 failed_stepaffected_files,通常足以让 Agent 定位问题。

这对应 Ch 5 的上下文窗口管理原则:不是给更多信息就更好,而是给精确的信息更好。Ch 5 中,压缩算法把 200 轮对话压缩成摘要而不丢失关键信息。这里,错误报告把 10000 行日志压缩成结构化摘要而不丢失根因。


25.4 为什么最多 3 次?

问题

3 这个数字看起来很任意。为什么不是 2 次(更保守)或 5 次(给 Agent 更多机会)?

思路

3 次的选择来自三个考量的交集:

考量一:错误类型的分布。 实践中,Agent 的错误分为两大类:

  • 表面错误(typo、import 路径、缺少逗号):通常 1-2 次修复就能解决。Agent 看到错误消息,直接定位修复。
  • 深层错误(架构误解、需求理解偏差、依赖不兼容):3 次修复也不太可能解决。这类错误的根因不在代码层面,而在理解层面——Agent 对项目的理解有根本性偏差,反复修补症状不能解决病因。

3 次恰好覆盖了绝大多数表面错误(2 次足够)加一次「兜底尝试」(第 3 次可能发现深层问题的变通方案)。超过 3 次基本意味着问题超出了 Agent 的能力范围,再试只是浪费资源。

考量二:成本边界。 每次自修复尝试消耗一次完整的 Agent 会话(LLM 调用 + CI 运行)。如果一个任务的正常成本是 $2,3 次自修复将额外消耗 $6。5 次将消耗 $10——单个任务的修复成本可能超过任务本身。3 次上限将最坏情况的成本控制在正常成本的 4 倍以内(1 次正常 + 3 次修复)。

考量三:Ch 22 的断路器先例。 Ch 22 提到自动压缩的断路器阈值也是 3 次。注释记录了没有断路器时的真实数据:1,279 个会话出现了 50 次以上的连续失败。断路器的值不在于阈值的精确数字,而在于它的存在——它将「潜在的无限消耗」变成了「有界的成本」。3 是一个足够小的数字,使得失败场景的总成本可预测。

无断路器:
  失败 → 修复 → 失败 → 修复 → 失败 → 修复 → ... → 无限
  成本:无界

3 次断路器:
  失败 → 修复 → 失败 → 修复 → 失败 → 修复 → 失败 → 升级
  成本:最多 4x 正常成本

实现

升级给人类时,系统不是简单地发一条「修不了,你来」的通知。它提供了完整的错误考古记录:

function escalateToHuman(task, latestCIResult) {
  escalation = {
    task_id: task.id,
    original_requirement: task.requirement,
    total_attempts: task.fix_attempts,
    
    // 每次尝试的详细记录
    attempt_history: task.fix_history.map(attempt => {
      return {
        attempt_number: attempt.number,
        error_type: attempt.failed_step,   // 编译/测试/lint/安全
        error_summary: attempt.error_report.summary,
        fix_applied: attempt.code_diff,     // Agent 做了什么修改
        why_still_failed: attempt.next_error // 修改后出现了什么新问题
      }
    }),
    
    // Agent 的自我诊断
    agent_diagnosis: generateDiagnosis(task),  // "我认为问题根因是..."
    
    // 建议的人工干预点
    suggested_action: classifyEscalation(task)
    // "ARCHITECTURE_ISSUE" | "DEPENDENCY_CONFLICT" | "TEST_ENV_PROBLEM"
  }
  
  notifyHuman(escalation)
}

attempt_history 是升级的核心价值。人类看到的不是「Agent 失败了」这个结论,而是「Agent 试了 3 次,每次做了什么、为什么失败」的完整记录。这就像一个住院病历——接手的医生不需要从头诊断,只需要看前几次的治疗记录和结果,就能判断下一步该怎么做。

suggested_action 将失败分类为不同的人工干预类型。这帮助人类快速定位问题域:如果是 ARCHITECTURE_ISSUE,人类需要提供更多的架构指导;如果是 DEPENDENCY_CONFLICT,人类需要手动解决依赖冲突;如果是 TEST_ENV_PROBLEM,问题可能不在代码而在 CI 环境。


25.5 可观测性:Agent 级别的度量

问题

Ch 22 的第七条原则「可观测性」强调:你无法改进你无法测量的东西。自修复循环引入了新的度量维度——不仅要追踪单个任务的成本和延迟,还要追踪 Agent 整体的可靠性

思路

OpenHarness 定义了三个核心的 Agent 级指标:

┌────────────────────────────────────────────────────────────┐
│                 Agent 可观测性仪表盘                         │
│                                                            │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │
│  │ 首次通过率    │  │ 自修复成功率  │  │ 升级率       │     │
│  │ (First Pass) │  │ (Fix Rate)   │  │ (Escalation) │     │
│  │              │  │              │  │              │     │
│  │    72%       │  │    85%       │  │    4.2%      │     │
│  │  ▲ +3% (周)  │  │  ▲ +1% (周)  │  │  ▼ -0.5%(周)│     │
│  └──────────────┘  └──────────────┘  └──────────────┘     │
│                                                            │
│  指标关系:                                                 │
│  首次通过 + (首次失败 × 自修复率) = 最终通过率               │
│  首次失败 × (1 - 自修复率) = 升级率                         │
│                                                            │
│  示例:                                                     │
│  100 个任务 → 72 个首次通过                                 │
│              28 个首次失败 → 24 个自修复成功 (85%)           │
│                           → 4 个升级给人类 (15%)            │
│  最终通过率 = 72 + 24 = 96%                                │
│  升级率 = 4 / 100 = 4%                                     │
└────────────────────────────────────────────────────────────┘

首次通过率(First Pass Rate) 衡量 INFORM 支柱的质量。如果首次通过率低,说明 Agent 没有获得足够的上下文(AGENTS.md 不够详细?知识库缺少关键信息?四层上下文的覆盖不够?)。提升首次通过率的杠杆在 INFORM 支柱,不在 CORRECT。

自修复成功率(Fix Rate) 衡量 CORRECT 支柱的有效性。如果自修复成功率低,说明错误报告的质量不够(CI 输出的结构化报告是否包含了足够的定位信息?)或者 Agent 的修复策略有问题。

升级率(Escalation Rate) 是最终的健康指标。它等于 (1 - 首次通过率) × (1 - 自修复成功率)。升级率越低,人类需要干预的频率越低,系统的自治程度越高。

这三个指标构成了一个诊断链:升级率高 → 先看自修复成功率 → 如果修复率低,改善错误报告质量 → 如果修复率高但升级率仍高,说明首次通过率太低 → 改善 INFORM 支柱。

实现

指标的采集和展示:

// Prometheus 指标定义 (伪代码)
counter agent_task_total {labels: [project, status]}
  // status: "first_pass" | "fixed" | "escalated"

histogram agent_fix_duration_seconds {labels: [project, attempt]}
  // 每次修复的耗时分布

gauge agent_first_pass_rate {labels: [project]}
  // 滑动窗口计算的首次通过率

// Grafana 告警规则
alert AgentFirstPassRateLow {
  condition: agent_first_pass_rate < 0.5 for 1h
  // 首次通过率低于 50% 持续 1 小时
  action: notify team
  message: "项目 {project} 的 Agent 首次通过率异常低,请检查 AGENTS.md 和知识库"
}

alert AgentEscalationRateHigh {
  condition: rate(agent_task_total{status="escalated"}) > 0.1 for 2h
  // 升级率超过 10% 持续 2 小时
  action: notify team
  message: "项目 {project} 的升级率异常高,请检查 CI 环境和修复策略"
}

告警的设计体现了一个重要原则:告警指向行动AgentFirstPassRateLow 的告警消息不是「首次通过率低」(描述现象),而是「请检查 AGENTS.md 和知识库」(指向行动)。AgentEscalationRateHigh 不是「升级率高」,而是「请检查 CI 环境和修复策略」。这和 Ch 22 的可观测性原则一致——度量不是目的,驱动改进才是。


25.6 与 Dream 系统的对比:两种自我修复的哲学

问题

Ch 21 的 Dream 系统也是一种「自我修复」机制——它检测记忆退化并在后台自动整合。CORRECT 支柱的自修复循环也是「自我修复」。两者有什么异同?

思路

                Dream 系统 (Ch 21)          自修复循环 (Ch 25)
──────────      ──────────────────          ──────────────────

修复对象        记忆质量退化                代码质量不达标
触发时机        会话之间(后台)             CI 失败时(即时)
触发条件        时间门 + 会话门 + 锁        CI 结果 + 重试计数
执行者          fork 受限子 Agent            新的 Agent 会话
执行环境        只读 bash                   完整执行权限
反馈信号        内省(自己判断什么过时)      外部(CI 明确说什么失败)
确定性          低(依赖 LLM 判断质量)      高(CI 结果是确定性的)
上限            无显式上限(靠门控节流)      3 次断路器
失败后果        记忆不更新,下次补             升级给人类

最关键的区别在「反馈信号」这一行:

Dream 的反馈是内省的——Agent 自己判断哪些记忆过时、哪些冗余、哪些需要合并。这个判断本身是非确定性的,可能出错(误删有用记忆、遗漏过时信息)。Dream 的可靠性依赖整合提示词的质量。

自修复循环的反馈是外部的——CI 流水线明确告诉 Agent「第 47 行有一个 TypeError: property 'foo' does not exist on type 'Bar'」。这个反馈是确定性的、精确的、不需要 Agent 自己判断的。自修复循环的可靠性依赖 CI 流水线的质量。

这个区别决定了两者的可靠性天花板不同。Dream 的可靠性受限于 LLM 的内省能力(目前还不够完美)。自修复循环的可靠性受限于 CI 流水线的覆盖率(可以通过增加测试来提升)。

从 Ch 23 的原则视角看:自修复循环更符合「确定性脚手架包围非确定性行为」——CI 是确定性的脚手架,Agent 的修复行为是非确定性的。Dream 则更接近「非确定性对非确定性」——用一个非确定性的判断(什么记忆该保留)来修复另一个非确定性的过程(记忆积累)。

这不意味着 Dream 的设计有问题——记忆质量的退化本身就没有确定性的检测手段(你怎么用一个测试断言「记忆是否合理」?)。Dream 在它的约束空间内已经是最优解。但这个对比提醒我们:当确定性的反馈信号可用时,优先使用它


25.7 闭环的完整图景

回顾本章,VERIFY 和 CORRECT 的协作构成了一个完整的质量闭环:

    INFORM 支柱提供上下文
           │
           ▼
    Agent 执行任务,产出代码
           │
           ▼
    ┌─── VERIFY 支柱 ───────────────────┐
    │                                   │
    │  CI → Semgrep → PR-Agent → ArgoCD │
    │                                   │
    │  输出:结构化错误报告              │
    └──────────────┬────────────────────┘
                   │
              通过 / 失败
              │       │
              ▼       ▼
         完成     ┌─── CORRECT 支柱 ──────────────┐
                  │                                │
                  │  attempt < 3 ?                 │
                  │  YES: 创建自修复任务            │
                  │       注入错误报告 + 修复历史   │
                  │       Agent 修复并重新提交      │
                  │       → 回到 VERIFY             │
                  │  NO:  升级给人类                │
                  │       提供完整错误考古记录       │
                  │                                │
                  │  度量:首次通过率、修复率、升级率│
                  └────────────────────────────────┘
                            │
                            ▼
                  反馈到 INFORM 支柱
                  (错误模式 → 更新 AGENTS.md / 知识库)

最后的「反馈到 INFORM 支柱」是闭环的关键。如果同一类错误反复出现(比如 Agent 总是忘记更新 pyproject.toml 的版本号),这个模式应该被识别并注入 AGENTS.md:「修改 Python 包时,必须同步更新 pyproject.toml 的 version 字段」。这就将 CORRECT 支柱的经验沉淀为 INFORM 支柱的上下文——下一次 Agent 在读到这条规则后,可能就不犯这个错误了。

这种「运行时经验沉淀为配置」的模式,在前 22 章中多次出现:Ch 17 的记忆系统将对话中的有用信息提取为持久记忆,Ch 21 的 Dream 将碎片记忆整合为结构化知识。OpenHarness 将同一个模式扩展到了 CI/CD 层面。


思考题

  1. 自修复循环的第 2 次和第 3 次尝试,Agent 看到的上下文包含了前几次的错误报告和修复尝试。这意味着第 3 次的上下文窗口占用比第 1 次大得多。如果上下文窗口不够用(历史太长),你会如何压缩修复历史?提示:参考 Ch 5 的压缩策略。

  2. PR-Agent(Layer 3)是非确定性的——同样的 PR 可能在不同运行中得到不同的审查结果。如果 PR-Agent 在第一次运行时发现了问题(REQUEST_CHANGES),Agent 修复后 PR-Agent 又发现了新问题(因为它看到了不同的视角),这算「修复失败」还是「新问题」?如何在自修复循环中处理这种非确定性?

  3. 本章的度量指标(首次通过率、修复率、升级率)是项目级别的。如果你需要比较不同类型任务的质量(bug 修复 vs 新功能 vs 重构),你会添加哪些维度?这些维度如何指导 INFORM 支柱的优化?

  4. 升级给人类后,人类的修复行为本身也是有价值的信号——它告诉系统「Agent 在这种类型的问题上能力不足」。设计一种机制,将人类的修复行为自动转化为 Agent 的学习材料(更新 AGENTS.md 或知识库)。