题记:自我改进的第一课,不是学会修 Bug,而是分清什么根本不该由自己修。

1. 抱怨可能是真的,修法仍然可以是错的⌗

设想一个纯粹虚构的场景:Agent 要调用一个会改变外部状态的 API,系统要求确认。它如实报告:“这一步需要等待,任务没能继续。”

现象完全真实。接下来应该修什么?

  • 确认入口太难找,可以改交互。
  • 等待状态没有保存,可以改续跑。
  • 操作本来就需要确认,等待本身不一定是故障。
  • 直接取消确认,确实最快,但那已经是在修改授权规则。

如果自我改进系统采用一条过于顺滑的链路:

Agent 遇到阻力 → 提交问题 → 自动修复 → 阻力消失

它很容易把最后一种当成成功。测试可以全绿,任务可以更快,系统也可以更不符合使用者的要求。

局部执行者知道哪里难受,却不天然拥有决定哪些阻力应该消失的权利。

本次设计调整的核心,是把 Agent 的抱怨从“修复工单”降回“现场证据”。不是不相信它,而是不让观察结果自动携带变更授权。

2. 先把采集接口做轻,把结论拿走⌗

report_friction 的普通上报只要求一个字段:symptom,即一句中立的现象描述。工具和组件名称可以补充;线程、回合及相关执行上下文由框架采集。

接口不再接收 fix、suggestion、workaround 或 kind 等字段。failure-reports.test.ts 对此有明确断言:只有现象也能上报,夹带这些额外字段则解析失败。

这不是指望一个 schema 就能读懂所有句子的语义。Agent 依然可能把建议塞进现象文本。它做的是一个更有限、也更实际的改变:不要在接口结构上邀请执行者同时扮演诊断者和方案批准者。

信息应由谁提供为什么
发生了什么、哪里费劲当前执行的 Agent它离现场最近
发生在哪个回合、用了哪些工具框架采集不必依赖模型复述
多次现象是否同源后续分诊单个回合看不到全貌
修复是否改变既定约束分诊与修复阶段检查任务便利性不是唯一目标
是否允许放宽约束使用者规则的制定权没有转交给修复器

采集门槛低,行动门槛另算。让 Agent 随口报告小摩擦,不等于每句话都值得开一个分支。

3. 中间缺的不是一个分类器,是一个新的工作对象⌗

调整后的普通路径是:

抱怨事件 ──┐
自动信号 ──┼─→ 分诊:结合既有规则,解释共同根因
用户反馈 ──┘                 │
                             ▼
                      synthesis 信号
                             │
                 ┌───────────┼───────────┐
                 ▼           ▼           ▼
              自动修复    不修/关联    交由用户决定

这里的关键是 synthesis:根因综合不是一段临时思考,而是单独入库、能够接受处置的信号。

ImproveService.synthesize 接收根因、成员信号、引用事件、核对过的规则,以及是否放宽约束的声明。它创建新信号,再把原来的成员关联过去。原始抱怨没有被覆盖成诊断结论,证据和解释仍然可以分开追溯。

与之配套,普通抱怨本身只能被关联为已知问题,或者说明理由后不修。直接对它选择自动修复,或通过公开工具启动修复,都会被拒绝。即使只有一条抱怨,也要先完成根因综合。

这一步的意义并不是“至少凑够几条才值得修”,而是强制发生一次问题重述:

系统应该满足什么需求,而不是 Agent 当时想移除什么障碍?

另一个容易忽略的细节是:计数聚合不等于根因聚类。

上报信号可以按工具或组件聚合次数,但 complaintEvents 会取回相应的可见事件,分诊不是只看最后一条摘要。同一工具的多次失败未必同因,不同工具的抱怨也可能指向同一个需求。根因不能从一个计数器里直接长出来。

4. “我检查过规则了”不能成为万能通行证⌗

综合后的根因仍然可能提出错误修法。所以这次改动不只增加一个分诊步骤,还增加了约束变更声明:

relaxesConstraint: boolean

它询问的是:这个修复是否放宽了使用者刻意设置的限制,或者扩大了 Agent 能做的事情?

在 constraints.ts 中,没有声明就不能继续相应的修复处置;声明为放宽约束而没有用户批准,也不能继续。这个检查覆盖代码修复入口,也覆盖知识修复和模型行为校准。

这是必要的:改变行为未必需要改 TypeScript。换一段提示词,也可能让系统开始做原先不允许的事。

实现还区分了真正的用户选择与自动应用的默认选项。批准查询明确排除 decided_via = 'auto',也不把“不修”或“继续讨论”当成放行。

不过,声明本身依然由模型作出。于是还有第二道、性质不同的检查:

检查位置看什么作用
分诊与修复入口是否声明放宽约束、是否获准拦住已识别出的权限变更
修复进行中修复器是否发现方案需要放宽约束将任务交回用户,而非继续换模型试做
提交落地前分支是否触及登记的约束门代码路径不只相信修复器的自我描述

CONSTRAINT_GATE_PATHS 登记了工具策略、批准执行、写入审查、预演、可见性等关键实现路径。未经相应批准的分支触及这些文件,会被暂缓落地;测试文件在部分目录规则中被排除。

代价很直白:这些文件中的无害重构也可能需要审批。文件路径检查不懂语义,它用一定的误拦换取可解释的拦截点。

它也不是完整证明。没有命中登记路径,不代表改动一定不会间接改变权限。语义声明与路径检查互相补位,但两者相加也不等于形式化验证。

5. 综合证据,不等于集中所有数据⌗

跨事件分析有一个诱惑:既然看得越多越容易找规律,就把所有抱怨交给同一个分诊 Agent。

这会让改进系统为了维护隐私边界,先跨过隐私边界。相当有程序员幽默感,但不该真的这么做。

这次实现保留了作用域:

  • 抱怨事件查询使用当前查看者的可见性条件。
  • 综合服务拒绝把不同隔离范围或类别的成员合成一个根因。
  • 受限范围的抱怨由各自范围内的综合任务处理。
  • 调度预检负责登记任务,再由运行时在对应范围内启动。

这带来一个明确取舍:某些跨范围的共同模式不会被一个全局模型直接看见。系统选择保留数据边界,而不是把“更聪明的诊断”当成扩大读取权限的理由。

还有一个速度上的例外:明确处于“等修复”状态的线程,仍然会立即进入修复路径。 普通抱怨可以等待综合;真正被阻塞的任务不必陪着排队。

因此,不能把这个设计描述成“所有抱怨都必须先经过每日综合”。它拆开的是常态采集与常态行动,同时保留阻塞任务的加急路径。加急改变调度优先级,不应被理解成批准取消约束;修复阶段的交回机制和落地检查仍然重要。

6. 测试证明了哪些事,又没证明哪些事⌗

这类改动最值得检查的,不是补丁行数,而是哪些原本顺手的调用现在会失败。

下面是落地测试中的断言范围,不是本次写作重新执行测试得到的运行报告:

测试文件关键规格
failure-reports.test.ts现象可以单独上报;额外方案字段被拒绝;事件保留回合归属
triage.test.ts普通抱怨不能直接接受修复处置;分诊材料包含各条事件;综合后成员关联到根因
triage.test.ts缺少约束声明会被拒绝;放宽约束而未经批准不能自动修复
autofix.test.ts修复器发现约束变更时交回用户;改到约束门的分支不会直接进入落地
immediate.test.ts受限范围内的普通抱怨不逐条启动修复;对应综合任务在本范围启动;等修复路径仍然立即启动

这些测试能证明程序入口和状态转换被约束了,不能证明模型每次都能找到真正的根因。

同样,rulesChecked 是一份需要填写的核对记录,不是规则推理正确性的机器证明。服务会检查必填、成员范围与引用事件的可见性,但“检索是否充分”“规则是否读对”“这些事件是否真的同因”,仍然需要判断。

这不是设计失败,而是应该诚实标出的能力边界:确定性的程序负责让绕过步骤变难,模型负责解释问题,用户保留改变原则的决定权。

7. 真正应该优化的,不是阻力最小⌗

相比“发现问题就开自动修复”,这套设计多了一次综合、一些状态转换,以及偶尔让人觉得多余的审批。

它换来的不是修复速度必然更快,也不是故障率已经下降——代码和这些测试没有提供这样的生产统计。

它换来的是更清晰的权力划分:

Agent 可以广泛报告不便,分诊可以提出解释,修复器可以改善实现,但它们不能仅凭“不方便”就认定原来的规则应该消失。

自我改进系统最危险的成功,不是修不好,而是把不该修掉的东西修得干干净净。

门确实会挡路。那通常也是装门的原因。