60 KiB
Executor Structured Output V2 可执行设计与实施 Issue
状态:阶段三实施中
严重程度:高
创建日期:2026-07-07
最后更新:2026-07-08
文档类型:可执行设计 issue / 分阶段实施说明
范围:Chat Executor 输出结构、Gatekeeper、Verifier、Composer 数据契约调整
关联问题:
executor-evidence-attribution-hallucinationexecutor-self-evidence-loop-design-notechat-verifier-agentexecutor-evidence-output-contract
0. 给后续实现 Agent 的执行入口
这份文档是 Executor Structured Output V2 的可执行设计 issue,不是单纯的数据结构草案。后续 agent 接手时,先按本节确认背景、当前阶段、执行纪律和验收边界,再进入后面的字段契约。
0.0 当前接手快照
截至 2026-07-08,阶段状态如下:
| 阶段 | 状态 | 记录 |
|---|---|---|
| 阶段一:Executor V2 输出契约 | 已完成、已归档、已提交 | 050cbc8 feat(agent): add executor evidence v2 contract |
| 阶段二:Gatekeeper 接入 VerifierInputHook | 已完成、已归档、已提交 | c5e496e feat(agent): add executor gatekeeper hook |
| 阶段三:Verifier V2 可推导性校验 | 实施中 | active change:openspec/changes/executor-verifier-claim-checks |
| 阶段四:Composer 输出最终答案 | 未开始 | 等阶段三归档并提交后再启动 |
| 阶段五:回归评测与审计闭环 | 未开始 | 等 Composer 链路稳定后补齐 |
接手时的第一动作:
- 先运行
git status --short,确认是否已有阶段三未提交改动。 - 如果
openspec/changes/executor-verifier-claim-checks仍存在,继续阶段三,不要跳到 Composer。 - 阶段三完成后,必须先验证、归档 OpenSpec、回填 devflow、单独 commit,再进入阶段四。
推荐阅读顺序:
- 先读
0.1 - 0.10:理解为什么要改、当前问题在哪里、目标链路是什么、哪些事情明确不做,以及如何按 OpenSpec/devflow 分阶段落地。 - 再读
20 - 25:理解代码影响面、阶段拆分、每阶段验收、回滚策略和最终完成标准。 - 最后按需查阅
2 - 19:这些是实现时使用的详细数据契约、Gatekeeper 规则、Verifier/Composer 输入输出。
一句话目标:
把 Executor 从“诊断 + 表达”收敛为“证据收集 + 结构化事实输出”,
在 Verifier 前加 Gatekeeper 做确定性拦截,
让 Verifier 只判断 claim 是否能由证据合理推出,
最终用户答案交给 Composer 生成。
执行约束:
- 必须按阶段实施,不允许把五个阶段揉成一个大改动。
- 每个阶段都要先有 OpenSpec change,再实现、验证、归档到 devflow,并单独提交。
- 阶段未归档、未提交前,不进入下一阶段。
- 验收失败如果是代码问题,agent 自行修复;如果是设计决策不明确,停下来问用户。
- 本 issue 不要求一次性完成所有阶段;后续 agent 应从当前 git/OpenSpec/devflow 状态继续推进。
交付纪律:
- 每个阶段只解决该阶段的问题,不顺手做下一阶段。
- 每个阶段至少要有主成功路径和新增失败路径测试。
- 提交前必须确认 diff 只包含当前阶段内容。
- 不提交、不推送,除非用户明确要求;本 issue 默认只指导阶段实现和本地提交。
0.1 背景
近期 Chat 复杂诊断链路中,多条诊断会话被 Verifier 判为 LOW_CONFID。这些会话并不是没有调用工具;Executor 通常已经调用了 lookup_knowledge、query_metrics、query_logs 等 evidence tools。
真正的问题是:Executor 在综合输出阶段把三类内容混在一起写成“当前事故结论”:
- 本轮工具真实返回的事实。
- runbook、skill 或知识库里的通用模式。
- 模型基于经验补全的推断。
当前 Executor 输出中还包含最终面向用户的自然语言字段:
diagnosis_summary
user_facing_answer
这会诱导 Executor 提前进入“诊断报告 / 用户表达”模式,在证据不充分时把“可能方向”写成“确认结论”。后续 Verifier 虽然会拦截,但它面对的是自然语言答案和结构化字段混在一起的输出,校验边界不稳定。
典型风险包括:
- Executor 编造不存在的
source_invocation_ids。 - Executor 使用真实 invocation id,但
evidence_excerpt与真实工具输出不一致。 - Executor 把 hypothesis 写成 confirmed claim。
- Executor 在
user_facing_answer里夹带 claims 中没有的根因、错误码、指标值或修复建议。 - Verifier 被迫从自然语言里逐字抽事实,校验边界不稳定。
因此,本次改造不是为了“多加几个 Agent 显得完整”,而是为了收紧证据归因链路:Executor 只产出可校验材料,Verifier 只判断材料是否被证据支撑,最终表达交给 Composer。
0.2 当前问题定义
本 issue 要解决的是:
Executor 证据归因幻觉
更具体地说,Executor 已经调用了工具,但在最终输出时没有严格区分:
| 类型 | 定义 | 应进入哪里 |
|---|---|---|
| direct evidence | 本轮工具直接观测到的事实 | claims |
| reasonable inference | 能由证据合理推出但不是逐字出现的判断 | claims,但需要标注 indirect,并由 Verifier 判断是否说重 |
| hypothesis | 值得排查但尚未被当前证据确认的方向 | hypotheses |
| missing evidence | 还缺少哪些证据才能确认 | missing_info |
| user expression | 面向用户的自然语言答案 | Composer 输出,不由 Executor 输出 |
如果不拆开这些边界,Verifier 会长期承担两个不同任务:
- 从自然语言中抽事实。
- 判断事实是否有证据支撑。
这会让 Verifier 的职责过重,也让最终答案容易夹带未经校验的内容。
0.3 当前实现
当前代码中的复杂 Chat 链路是三 Agent 顺序执行:
chat_planner -> chat_executor -> chat_verifier
对应入口在:
ChatService.executeChatComplex(...)
当前数据流:
用户问题
-> Planner 生成 planner_plan
-> Executor 调用工具并输出 executor_evidence_v1
-> VerifierInputHook 构造 verifier payload
-> Verifier 输出 facts_checked/verdict
-> ChatService 根据 verdict 生成最终 answer
当前关键问题在 Executor 与 Verifier 之间:
Executor 同时输出结构化 claims 和 user_facing_answer
Verifier 既要校验 structured output,又要扫描自然语言答案
这会让最终答案和证据归因之间出现缝隙。
0.4 目标设计
目标链路仍保持单条顺序链路,不新增 Controller,不改 Planner:
chat_planner
-> chat_executor
-> VerifierInputHook 内 Gatekeeper
-> chat_verifier
-> chat_composer
-> final answer
职责边界:
| 组件 | 职责 |
|---|---|
| Planner | 选择 skill、拆解计划;本次不改 |
| Executor | 调用工具收集证据,输出结构化 claims/hypotheses/actions/missing_info |
| Gatekeeper | 在 Verifier 前做确定性校验,拦截伪造 ID、工具名不匹配、明显张冠李戴、非法 schema |
| Verifier | 判断 claims 是否能由证据合理推出,不再逐字扫描最终答案 |
| Composer | 只根据 Verifier 允许的材料生成最终用户答案 |
目标数据流:
Executor raw JSON
-> parse JSON
-> Gatekeeper.validate(...)
-> Verifier claim-level validation
-> ChatService 过滤 allowed_claims/allowed_hypotheses
-> Composer 生成 user_facing_answer
核心原则:
- 单智能体优先:不为了抽象而新增 Controller;只有已经明确的职责边界才拆 Agent。
- Planner 不改:本 issue 的问题不在计划拆解,而在 Executor 输出和 Verifier 校验边界。
- Gatekeeper 不做诊断:只做确定性校验,拦截伪造 ID、字段退化、明显张冠李戴。
- Verifier 不再逐字扫描最终答案:主校验对象是结构化
claims。 - Composer 不补事实:只把 Verifier 允许的材料组织成中文答案。
0.5 分阶段实施总览
| 阶段 | 名称 | 目标 | 状态 | 退出条件 |
|---|---|---|---|---|
| 1 | Executor V2 输出契约 | 移除 Executor 最终表达字段,只输出结构化诊断材料 | 已完成并归档 | executor_evidence_v2 可运行,最终用户不再看到 raw JSON |
| 2 | Gatekeeper 接入 VerifierInputHook | 在 Verifier 前加入确定性拦截与审计 | 已完成并归档 | gatekeeper_result 进入 Verifier payload 和 self_evaluation |
| 3 | Verifier V2 可推导性校验 | 从 facts_checked 转向 claim_checks,判断 claims 是否可由证据推出 |
实施中 | Verifier 不再从自然语言答案抽取额外事实 |
| 4 | Composer 最终表达 | 由 Composer 根据 Verifier 允许材料生成最终用户答案 | 未开始 | PASS/LOW_CONFID/REJECT 都不读取 Executor user_facing_answer |
| 5 | 回归评测与审计闭环 | 用测试和 eval fixtures 证明幻觉拦截链路有效 | 未开始 | 伪造 ID、张冠李戴、hypothesis 写成事实等场景都有回归覆盖 |
更详细的实施拆解见 21. 实施阶段。
阶段推进顺序必须串行:
阶段一 archive + commit
-> 阶段二 archive + commit
-> 阶段三 archive + commit
-> 阶段四 archive + commit
-> 阶段五 archive + commit
不能在阶段三未归档提交时启动 Composer,也不能在 Composer 未稳定时把回归评测阶段提前做成大杂烩。
0.6 范围与非目标
In scope
- 调整 Executor 输出契约,从
executor_evidence_v1演进到executor_evidence_v2。 - 在
VerifierInputHook中接入 Gatekeeper。 - 将 Gatekeeper 结果写入 Verifier payload 和
diagnosis_session.self_evaluation。 - 将 Verifier 主输出从
facts_checked迁移到claim_checks,兼容期保留旧字段。 - 新增 Composer 表达层,最终用户答案只来自 Verifier 允许材料。
- 补齐关键回归测试和 eval fixtures。
Out of scope
本 issue 第一版明确不做以下事情:
- 不改 Planner 的输入输出。
- 不引入 Controller 或新的编排层。
- 不改变现有 retry 机制。
- 不要求 Gatekeeper 失败后自动回退 Executor 重试。
- 不新增数据库表。
- 不让 Gatekeeper 做根因判断。
- 不让 Composer 调用工具或重新诊断。
0.7 关键设计决策
| 决策 | 结论 | 原因 |
|---|---|---|
| 是否改 Planner | 不改 | 当前问题主要在 Executor 输出和 Verifier 校验边界 |
| Gatekeeper 放在哪里 | 放在 VerifierInputHook |
保持当前 workflow,不改 Executor hook 和编排 |
| Gatekeeper 失败是否重试 | 第一版不重试 | 先建立拦截和审计闭环,避免扩大改动面 |
| Executor 是否继续输出最终答案 | 不输出 | 防止未经 Verifier 的自然语言结论泄露 |
| Verifier 是否逐字扫描答案 | 不扫描 | 主校验对象改为 claims |
| Composer 是否可以新增事实 | 不可以 | Composer 只是表达层,不是诊断层 |
facts_checked 是否保留 |
兼容期保留 | 当前低置信模板、retry_context、评测可能依赖 |
0.8 实现约束
实现时必须遵守:
- 优先保持现有三 Agent 复杂链路可运行。
- 每个阶段都应能单独测试和回滚。
- 第一版以“降低幻觉风险”为目标,不追求一次性重构所有历史字段。
- 所有最终用户可见答案都必须来自 Verifier 允许输出的材料。
- 所有 Gatekeeper 校验结果都必须进入审计,至少能回溯到
self_evaluation.verifier_evaluation.gatekeeper_result。 - 如果结构化输出 malformed,不允许回退到自然语言逐字抽取后给 PASS。
0.9 OpenSpec / devflow 落地要求
这个 issue 后续按 OpenSpec-first 的方式实施。每个阶段都必须留下可审计痕迹:
OpenSpec proposal/design/spec/tasks
-> 实现代码
-> 最小验证
-> archive 到 openspec/changes/archive
-> 回填 devflow/projects
-> git commit
阶段交付物:
| 交付物 | 要求 |
|---|---|
| OpenSpec change | 每个阶段一个独立 change,不能复用上个阶段的 active change |
| tests | 至少覆盖该阶段的新增失败路径和主成功路径 |
| devflow | 记录 brief、decisions、evidence、acceptance |
| commit | 每个阶段单独提交,提交前确认 diff 只包含本阶段内容 |
推荐验证命令:
mvn "-Dtest=VerifierInputHookTest,ChatServiceSequentialAgentTest" test
cmd /c openspec validate --specs
如阶段新增专门测试,应把测试类加入 Maven -Dtest 列表。
后续 agent 每个阶段的固定执行清单:
- 确认当前阶段:读取本 issue、
git status --short、openspec status。 - 确认 OpenSpec:没有 active change 时先创建;已有当前阶段 active change 时继续使用。
- 实现阶段内代码:只改该阶段要求的 prompt/service/hook/test,不夹带下一阶段。
- 验证:运行该阶段建议测试和
cmd /c openspec validate ...。 - 回填任务:更新 OpenSpec
tasks.md、decisions.md,记录实际验证命令和结果。 - 归档:创建或更新
devflow/projects/<date>-<change-id>/,然后cmd /c openspec archive <change-id> -y。 - 复验:归档后再次运行最小测试和
cmd /c openspec validate --specs。 - 提交:检查
git diff --cached --stat,确认只包含当前阶段,再单独 commit。
若步骤 4 或 7 失败:
- 代码或测试问题:自行修复并重新验证。
- 阶段边界或协议设计问题:停止,向用户说明阻塞点,不进入下一阶段。
0.10 推荐实现切片
建议不要把所有改动塞进一个大 PR。推荐拆成以下可独立验收的切片:
| 切片 | 内容 | 可单独合入条件 |
|---|---|---|
| PR-1 | Executor V2 prompt + parse-only 边界调整 | Executor 能输出 V2 JSON;不会再通过 Executor 直接生成最终用户答案 |
| PR-2 | Gatekeeper 基础框架 + schema/invocation 规则 | Verifier payload 和审计中出现 gatekeeper_result;伪造 invocation id 可被拦截 |
| PR-3 | Verifier V2 claim_checks + facts_checked 兼容 | Verifier 主校验 claims;现有低置信模板和 retry_context 不坏 |
| PR-4 | Composer 接入 + 最终答案渲染 | PASS/LOW_CONFID/REJECT 最终答案都不再读取 Executor user_facing_answer |
| PR-5 | eval fixtures + 回归测试补齐 | 覆盖伪造 ID、工具名不匹配、excerpt 张冠李戴、hypothesis 写成事实 |
每个切片都应保留当前复杂 Chat 链路可运行。若某个切片失败,优先回滚该切片,不要连带回滚已经稳定的前置切片。
0.11 为什么这样拆阶段
本 issue 的改造目标不是一次性重写 Chat 诊断链路,而是逐步切断“证据不足但自然语言先下结论”的通道。阶段拆分按风险来源分层:
| 阶段 | 切断的风险 | 为什么不能合并 |
|---|---|---|
| 阶段一 | Executor 提前输出最终诊断话术 | 先移除污染源,否则后面 Verifier/Composer 仍会被旧答案影响 |
| 阶段二 | 伪造 ID、工具名不匹配、非法 schema 等物理级幻觉 | 这类问题不需要 LLM 判断,先用代码低成本拦截 |
| 阶段三 | claim 与证据之间是否可推导 | 这是 Verifier 的语义职责,必须在 Gatekeeper 之后处理 |
| 阶段四 | 最终用户答案夹带未验证事实 | 只有 Verifier 输出稳定后,Composer 才知道哪些材料可用 |
| 阶段五 | 新链路是否真的防住历史幻觉场景 | 等链路完整后再做系统化回归,避免测试绑死临时实现 |
每个阶段的设计都必须满足两个条件:
- 可以独立验证,不依赖后续阶段已经完成。
- 失败时可以独立回滚,不破坏已经归档提交的前置阶段。
因此后续实现时不要把“顺手移除临时 renderer”“顺手扩展 Gatekeeper 规则”“顺手接 Composer”混进阶段三。阶段三只解决 Verifier claim_checks 和有效 verdict guardrail。
1. 设计目标
当前 executor_evidence_v1 同时包含结构化诊断材料和自然语言表达字段:
diagnosis_summaryuser_facing_answer
这两个字段容易让 Executor 提前进入“总结报告 / 用户表达”模式,并可能把未证实内容写成确认结论。
V2 的目标是让 Executor 只输出结构化诊断材料,不负责最终用户表达。
2. 输出结构
{
"answer_version": "executor_evidence_v2",
"claims": [
{
"claim_id": "claim-1",
"claim_type": "symptom",
"claim_text": "payment-service 出现请求超时日志。",
"support_level": "direct",
"evidence_bindings": [
{
"source_type": "tool_trace",
"source_id": "trace-1",
"tool_name": "query_logs",
"source_invocation_ids": [394],
"evidence_excerpt": "request timeout"
}
]
}
],
"hypotheses": [
{
"hypothesis_text": "连接池压力可能参与了超时问题。",
"basis": "当前已有超时现象,但缺少连接池 active/idle/pending 指标。",
"needed_evidence": ["HikariCP active/idle/pending 指标"]
}
],
"recommended_actions": [
{
"action_text": "补充查询连接池 active/idle/pending 指标。",
"reason": "用于确认连接池是否达到上限。",
"evidence_bindings": []
}
],
"missing_info": [
"缺少连接池指标,无法确认连接池耗尽是根因。"
]
}
3. 字段定义
| 字段 | 类型 | 必填 | 定义 |
|---|---|---|---|
answer_version |
string | 是 | 固定为 executor_evidence_v2 |
claims |
array | 是 | 已确认或有明确支撑的事实断言 |
hypotheses |
array | 否 | 合理怀疑但未被当前证据确认的方向 |
recommended_actions |
array | 否 | 建议补证据、排查或处理动作 |
missing_info |
array | 否 | 当前无法确认结论所缺少的证据 |
4. claims
{
"claim_id": "claim-1",
"claim_type": "symptom",
"claim_text": "payment-service 出现请求超时日志。",
"support_level": "direct",
"evidence_bindings": []
}
| 字段 | 类型 | 必填 | 定义 |
|---|---|---|---|
claim_id |
string | 是 | claim 唯一标识 |
claim_type |
string | 是 | claim 类型,例如 symptom / root_cause / impact / risk |
claim_text |
string | 是 | 已确认或有限确认的事实断言 |
support_level |
string | 是 | direct / indirect |
evidence_bindings |
array | 是 | 支撑该 claim 的证据绑定,不能为空 |
5. evidence_bindings
{
"source_type": "tool_trace",
"source_id": "trace-1",
"tool_name": "query_logs",
"source_invocation_ids": [394],
"evidence_excerpt": "request timeout"
}
| 字段 | 类型 | 必填 | 定义 |
|---|---|---|---|
source_type |
string | 是 | 证据来源类型,例如 tool_trace |
source_id |
string | 否 | evidence block id、trace_ref 或可定位标识;第一版不作为硬校验依据 |
tool_name |
string | 是 | 来源工具名 |
source_invocation_ids |
array | 是 | 来源 tool_invocation.id |
evidence_excerpt |
string | 是 | 从工具返回中摘取的原话、指标值、日志片段或关键数据 |
6. hypotheses
{
"hypothesis_text": "连接池压力可能参与了超时问题。",
"basis": "当前已有超时现象,但缺少连接池 active/idle/pending 指标。",
"needed_evidence": ["HikariCP active/idle/pending 指标"]
}
| 字段 | 类型 | 必填 | 定义 |
|---|---|---|---|
hypothesis_text |
string | 是 | 未证实但值得排查的方向 |
basis |
string | 是 | 它基于哪些已知证据,以及为什么仍只是推测 |
needed_evidence |
array | 是 | 要确认该假设还缺少的证据 |
7. recommended_actions
{
"action_text": "补充查询连接池 active/idle/pending 指标。",
"reason": "用于确认连接池是否达到上限。",
"evidence_bindings": []
}
| 字段 | 类型 | 必填 | 定义 |
|---|---|---|---|
action_text |
string | 是 | 建议动作 |
reason |
string | 是 | 建议原因 |
evidence_bindings |
array | 否 | 建议动作关联的证据绑定,可为空 |
8. 移除字段
V2 移除以下字段:
| 字段 | 移除原因 |
|---|---|
diagnosis_summary |
容易让 Executor 提前总结诊断故事 |
user_facing_answer |
容易夹带 claims 之外的确认式事实 |
最终用户答案应由 ChatService 或后续专门渲染阶段基于结构化字段生成。
9. Gatekeeper 设计
Executor 输出后、Verifier 接收前,增加一层确定性校验:
Executor raw output
-> parse JSON
-> Gatekeeper
-> Verifier payload
Gatekeeper 的目标是拦截物理级幻觉,而不是替代 Verifier 做语义判断。
9.1 职责边界
| 组件 | 职责 |
|---|---|
| Gatekeeper | schema、引用 ID、工具名、excerpt、黑名单措辞等确定性校验 |
| Verifier | 判断 claim 是否被证据语义支撑、是否说重、是否缺关键证据 |
Gatekeeper 不判断根因是否正确,也不做新的检索。
9.2 接入位置
V1 接入位置保持在 VerifierInputHook 内,不改 Executor hook,不改 Chat workflow 编排:
parseExecutorOutput(...)
-> buildVerifierTraceSummary(...)
-> Gatekeeper.validate(...)
-> verifierInput.put("gatekeeper_result", ...)
VerifierInputHook 的职责收敛为:
- 解析 Executor raw output。
- 构造
executor_output_parse_status。 - 构造
tool_trace_summary。 - 调用 Gatekeeper。
- 组装 Verifier payload。
除 JSON parse 与最小可解析性判断外,VerifierInputHook 不再维护独立校验规则。
后续持久化时,将 gatekeeper_result 写入:
diagnosis_session.self_evaluation.verifier_evaluation.gatekeeper_result
10. Gatekeeper 规则加载
Gatekeeper 采用“规则代码稳定、规则配置可调”的设计。
Rule Index
-> Rule Metadata
-> Rule Implementation
10.1 索引层
索引层列出所有可用规则名和一句话描述,用于加载、展示和审计。
rules:
- id: schema.executor_v2
name: Executor V2 Schema
description: 校验 Executor 输出是否符合 V2 schema
enabled: true
- id: evidence.invocation_ref
name: Invocation Reference Check
description: 校验 source_invocation_ids 是否属于当前 session 且工具名匹配
enabled: true
- id: evidence.excerpt_similarity
name: Excerpt Similarity Check
description: 校验 evidence_excerpt 与真实工具输出是否相似
enabled: true
- id: claim.hallucination_phrases
name: Hallucination Phrase Check
description: 禁止 confirmed claims 使用经验化、推测化措辞
enabled: true
- id: claim.evidence_utilization
name: Evidence Utilization Check
description: 检查 claim_text 与 evidence_excerpt 的关键词覆盖率
enabled: false
10.2 元数据层
元数据层定义规则参数、阈值、严重级别和适用字段。调整阈值或黑名单时只改元数据,不改代码。
ruleMetadata:
schema.executor_v2:
severity: error
verdictImpact: low_confid_on_fail
params:
requiredFields:
- answer_version
- claims
deprecatedFields:
- diagnosis_summary
- user_facing_answer
evidence.excerpt_similarity:
severity: error
verdictImpact: low_confid_on_fail
params:
passThreshold: 0.5
warnThreshold: 0.3
compareAgainst:
- output_preview
- retrieval_details
- tool_trace_summary
claim.hallucination_phrases:
severity: error
verdictImpact: low_confid_on_fail
params:
targetFields:
- claims[].claim_text
blacklist:
- 通常情况下
- 根据经验
- 一般来说
- 我认为
- 理论上
- 可能是
- 推测
allowInFields:
- hypotheses[].hypothesis_text
- hypotheses[].basis
claim.evidence_utilization:
severity: warning
verdictImpact: warn_only
params:
minKeywordCoverage: 0.5
evidence.invocation_ref:
severity: error
verdictImpact: reject_on_fail
10.3 规则实现层
规则实现使用代码中的固定接口。新增全新类型规则需要代码;调整阈值、词表、启用状态不需要改代码。
GatekeeperRule
id()
validate(context, metadata)
-> RuleResult
10.4 与 VerifierInputHook 原有校验的关系
V2 中,VerifierInputHook 原有的结构判断只保留 parse-only 能力:
raw text
-> sanitized JSON
-> JsonNode / Map
-> parse status
以下校验统一迁移到 Gatekeeper:
- schema 字段是否完整。
answer_version是否正确。claims是否为空或类型错误。claims[].evidence_bindings是否为空。source_invocation_ids是否存在。tool_name是否匹配。evidence_excerpt是否可信。- 是否出现已移除字段
diagnosis_summary/user_facing_answer。
这样避免 VerifierInputHook 和 Gatekeeper 出现两套规则、两套错误口径。
11. 推荐规则集
11.1 Schema 校验
规则 id:
schema.executor_v2
校验内容:
- 输出必须是 JSON object。
answer_version必须为executor_evidence_v2。claims必须存在且为 array。hypotheses、recommended_actions、missing_info缺省时按空数组处理。- 不允许出现
diagnosis_summary。 - 不允许出现
user_facing_answer。
Gatekeeper 输出给 Verifier 前应 normalize 结构:
missing hypotheses -> []
missing recommended_actions -> []
missing missing_info -> []
也就是说,Verifier 和 Composer 可以假定这三个字段存在且为数组。
11.2 Invocation 引用校验
规则 id:
evidence.invocation_ref
校验内容:
claims[].evidence_bindings不能为空。source_invocation_ids必须为非空数组。- 每个 invocation id 必须属于当前 session。
tool_name必须和对应tool_invocation.tool_name匹配。- 不允许引用其它 session 的工具调用。
该规则只检查 claims[].evidence_bindings。recommended_actions[].evidence_bindings 可以为空,不参与硬失败判定。
11.3 Excerpt 相似度校验
规则 id:
evidence.excerpt_similarity
校验内容:
- 将
evidence_excerpt与真实工具输出进行相似度比对。 - 可比较来源包括:
tool_invocation.output_previewtool_invocation.retrieval_detailstool_trace_summary.output_summary
建议阈值:
| 相似度 | 结果 |
|---|---|
>= 0.5 |
pass |
0.3 - 0.5 |
warn |
< 0.3 |
fail |
如果只是 excerpt 相似度低,第一版判为 LOW_CONFID 级风险,不直接 REJECT,因为 output_preview 可能被截断,tool_trace_summary 也可能是摘要。
只有以下情况才进入 REJECT 级别:
- invocation id 不存在。
- invocation id 不属于当前 session。
tool_name与 invocation 的真实工具名不匹配。- invocation 存在但 excerpt 明显指向另一个服务、错误码或指标值。
11.4 幻觉措辞校验
规则 id:
claim.hallucination_phrases
校验内容:
claims[].claim_text不允许出现经验化、推测化措辞。hypotheses中允许出现推测措辞。
默认黑名单:
[
"通常情况下",
"根据经验",
"一般来说",
"可能是",
"推测",
"我认为",
"理论上"
]
11.5 证据利用率校验
规则 id:
claim.evidence_utilization
校验内容:
- 提取
claim_text中的实词。 - 提取
evidence_excerpt中的实词。 - 计算关键词覆盖率。
伪代码:
def check_evidence_utilization(claim_text, excerpt):
claim_keywords = extract_keywords(claim_text)
excerpt_keywords = extract_keywords(excerpt)
overlap = claim_keywords & excerpt_keywords
utilization = len(overlap) / len(claim_keywords)
return utilization >= 0.5
第一版建议作为 warning,不作为硬拒绝条件。
12. Gatekeeper 输出
Gatekeeper 第一版只保留必要审计字段。
{
"gatekeeper_result": {
"status": "fail",
"failed_rules": ["evidence.invocation_ref"],
"warnings": ["evidence.excerpt_similarity"],
"errors": [
{
"rule_id": "evidence.invocation_ref",
"target": "claims[0].evidence_bindings[0]",
"message": "source_invocation_ids not found in current session"
}
]
}
}
12.1 字段定义
| 字段 | 类型 | 定义 |
|---|---|---|
status |
string | pass / warn / fail |
failed_rules |
array | 失败规则 id 列表 |
warnings |
array | warning 规则 id 列表 |
errors |
array | 最小错误明细 |
errors[].rule_id |
string | 失败规则 id |
errors[].target |
string | 出问题的字段路径 |
errors[].message |
string | 人类可读原因 |
12.2 总状态计算
任一规则 fail -> gatekeeper status = fail
否则任一规则 warn -> gatekeeper status = warn
否则 gatekeeper status = pass
第一版不记录 version、rule_set、summary、severity、details、checked_at 等字段,避免审计结构过重。
Verifier 如需判断 Gatekeeper fail 是否应导致 REJECT,使用规则元数据中的 verdictImpact,不依赖落库字段。
13. 审计落点
Gatekeeper 结果需要进入两处:
13.1 Verifier payload
{
"executor_structured_output": {},
"executor_output_parse_status": {},
"tool_trace_summary": [],
"gatekeeper_result": {}
}
Verifier 可根据 gatekeeper_result 调整判定,尤其是:
gatekeeper_result.status=fail时,不应输出PASS。- schema 或 invocation 引用失败时,应倾向
LOW_CONFID或REJECT。
13.1.1 parse status 与 schema status 边界
executor_output_parse_status 只表示 JSON 解析结果:
| parse status | 含义 |
|---|---|
valid |
raw output 可解析为 JSON object |
missing |
raw output 为空或不是 JSON |
malformed |
raw output 像 JSON 但解析失败 |
schema 合法性不再由 parse status 表达,统一交给 Gatekeeper:
JSON parse valid but schema invalid
-> executor_output_parse_status.status = valid
-> gatekeeper_result.status = fail
-> failed_rules contains schema.executor_v2
13.2 self_evaluation
持久化位置:
diagnosis_session.self_evaluation.verifier_evaluation.gatekeeper_result
审计链路:
executor_raw_output
-> executor_output_parse_status
-> gatekeeper_result
-> verifier_evaluation
-> final_answer
14. Verifier 输入结构
V2 中 Verifier 以结构化输出为主输入,不再逐字抽取自然语言事实。
{
"original_query": "用户原始问题",
"executor_raw_output": "{raw executor output, debug/fallback only}",
"executor_structured_output": {
"answer_version": "executor_evidence_v2",
"claims": [],
"hypotheses": [],
"recommended_actions": [],
"missing_info": []
},
"executor_output_parse_status": {
"status": "valid",
"detail": "parsed executor evidence contract"
},
"tool_trace_summary": [],
"gatekeeper_result": {
"status": "pass",
"failed_rules": [],
"warnings": [],
"errors": []
},
"retry_context": null
}
| 字段 | 用途 |
|---|---|
original_query |
判断结构化诊断是否围绕用户问题 |
executor_raw_output |
原始输出,仅用于 debug/fallback,不作为主校验来源 |
executor_structured_output |
Verifier 主校验对象 |
executor_output_parse_status |
判断结构化输出是否可用 |
tool_trace_summary |
证据索引 |
gatekeeper_result |
Gatekeeper 物理级校验结果 |
retry_context |
第二轮时判断缺口是否被补足 |
兼容期可以保留旧字段名:
executor_final_answer
第一期 payload 可以同时包含 executor_raw_output 和 executor_final_answer,二者内容相同。executor_final_answer 语义上视为 deprecated;Verifier 不应在结构化输出合法时从该字段抽取额外事实。
15. Verifier 字段使用规则
15.1 executor_structured_output
Verifier 主要校验:
claimshypothesesrecommended_actionsmissing_info
重点是 claims:
claim_text 是否能由 evidence_bindings 指向的证据合理推出?
support_level 是否说重?
是否引入证据外的新实体、新指标、新错误码或新根因?
15.2 tool_trace_summary
tool_trace_summary 用于判断 evidence binding 是否有语义支撑。
Verifier 不要求 claim 与证据逐字一致,而是判断可推导性:
证据:payment-service CPU 92%,线程数 245
claim:payment-service 当前存在 CPU 使用率过高现象
=> direct_observation
claim:CPU 过高可能导致支付接口超时
=> reasonable_inference
claim:CPU 过高是支付超时的唯一根因
=> overstated
15.3 gatekeeper_result
Gatekeeper 负责物理合法性,Verifier 消费结果:
status = pass
-> 正常校验 claims
status = warn
-> 正常校验 claims,但 rationale 可提及 warning
status = fail
-> 不允许 PASS
-> 相关 claim 至少应判 unsupported / external_unknown / contradicted
15.4 executor_output_parse_status
status = valid
-> 使用结构化可推导性校验
status = missing / malformed
-> 不再回退到自然语言逐字抽取
-> verdict = LOW_CONFID
-> groundedness_score = 0
15.5 executor_raw_output
executor_raw_output 只用于审计和 debug。
当 executor_structured_output 合法时,Verifier 不应从 executor_raw_output 中抽取额外事实。
16. Verifier 输出结构
V2 Verifier 输出从 facts_checked 转向 claim_checks。
{
"verdict": "LOW_CONFID",
"groundedness_score": 0.62,
"claim_checks": [
{
"claim_id": "claim-1",
"verification": "direct_observation",
"detail": "query_metrics 显示 CPU 使用率为 92%,可直接支撑 CPU 过高现象。",
"evidence_refs": [
{
"trace_ref": "trace-1",
"tool_name": "query_metrics",
"source_invocation_ids": [394],
"note": "指标摘要包含 CPU=92%"
}
]
}
],
"hypothesis_checks": [
{
"hypothesis_index": 0,
"verification": "reasonable_hypothesis",
"detail": "该假设明确标注为未证实,并列出需要补充的证据。"
}
],
"rationale": "已有证据支持超时现象,但根因仍缺少直接证据。"
}
兼容期建议同时保留 facts_checked:
claim_checks作为 V2 主字段。facts_checked由claim_checks映射生成,供现有 Trace Workbench、eval 和降级输出继续使用。- 待前端和评测全部迁移后,再移除
facts_checked。
16.1 字段定义
| 字段 | 类型 | 定义 |
|---|---|---|
verdict |
string | PASS / LOW_CONFID / REJECT |
groundedness_score |
number | 结构化 claim 的证据支撑评分 |
claim_checks |
array | 对 claims 的可推导性校验结果 |
claim_checks[].claim_id |
string | 被校验的 claim id |
claim_checks[].verification |
string | 可推导性判定 |
claim_checks[].detail |
string | 判定说明 |
claim_checks[].evidence_refs |
array | 使用的证据引用 |
hypothesis_checks |
array | 对 hypotheses 的边界校验 |
hypothesis_checks[].hypothesis_index |
number | hypothesis 数组下标 |
hypothesis_checks[].verification |
string | reasonable_hypothesis / unsupported_hypothesis / overstated_as_fact |
hypothesis_checks[].detail |
string | 判定说明 |
rationale |
string | 总体判定理由 |
16.1.1 facts_checked 兼容映射
兼容期内,ChatService 需要从 claim_checks 生成旧字段 facts_checked。
映射规则:
claim_checks[].verification |
facts_checked[].verification |
|---|---|
direct_observation |
direct_evidence |
reasonable_inference |
indirect_support |
overstated |
indirect_support |
unsupported |
no_evidence |
external_unknown |
no_evidence |
contradicted |
contradicted |
facts_checked[].fact 建议格式:
{claim_id}: {claim_text}
facts_checked[].is_critical 规则:
claim_type in ["root_cause", "symptom", "impact", "risk"] -> true
其它 -> false
facts_checked[].evidence_refs 直接复用 claim_checks[].evidence_refs。
16.2 claim verification 枚举
| verification | 含义 |
|---|---|
direct_observation |
证据直接观测到该事实 |
reasonable_inference |
证据没有逐字说明,但可以合理推出 |
overstated |
有部分依据,但 claim 说得太满 |
unsupported |
证据不足 |
external_unknown |
引入证据外的新服务、数值、错误码、根因等 |
contradicted |
与证据冲突 |
external_unknown 需要区分严重程度:
| 场景 | 建议 verdict |
|---|---|
| 引入证据外的核心服务名、订单号、错误码、关键指标值、根因 | REJECT |
| 引入非核心背景实体或表达不清 | LOW_CONFID |
17. Verifier 判定矩阵
PASS
必须同时满足:
gatekeeper_result.status != fail- 所有核心 claims 均为
direct_observation或reasonable_inference - 至少一个关键 claim 为
direct_observation - 不存在
overstated - 不存在
unsupported - 不存在
external_unknown - 不存在
contradicted
LOW_CONFID
满足任一条件:
- 存在
unsupported - 存在
external_unknown - 存在
overstated - 所有核心 claims 都只是
reasonable_inference executor_output_parse_status.status为missing或malformedgatekeeper_result.status = fail,但失败不属于严重伪造
REJECT
满足任一条件:
- 任一核心 claim 为
contradicted - Gatekeeper 发现严重伪造:
- 编造
source_invocation_ids - 跨 session 引用
tool_name与 invocation 不匹配- 使用真实 id 但 excerpt 明显张冠李戴
- 编造
- Verifier 发现核心
external_unknown:- 新增证据外服务名
- 新增证据外订单号
- 新增证据外错误码
- 新增证据外关键指标值
- 新增证据外根因
严重伪造由 Gatekeeper 规则元数据中的 verdictImpact=reject_on_fail 定义。
18. 与 V1 Verifier 的差异
| 项目 | V1 | V2 |
|---|---|---|
| 主校验对象 | executor_final_answer 和 structured output |
executor_structured_output |
| 校验方式 | 从自然语言中抽取 facts 并逐条校验 | 对 claims 做可推导性校验 |
| 原始文本作用 | 事实抽取来源之一 | debug/fallback only |
| 输出字段 | facts_checked |
claim_checks |
| Gatekeeper | 无 | 前置物理级校验 |
| malformed 输出 | 可回退自然语言校验 | 直接 LOW_CONFID |
19. Composer 设计
Composer 是最终表达层。它不负责诊断,不调用工具,不补充新事实。
Executor structured output
-> Gatekeeper
-> Verifier
-> Composer
-> final answer
19.1 职责边界
| 组件 | 职责 |
|---|---|
| Executor | 输出结构化诊断材料 |
| Gatekeeper | 做物理级确定性校验 |
| Verifier | 判断 claims 是否可由证据合理推出 |
| Composer | 将 Verifier 允许输出的材料组织成用户可读中文答案 |
Composer 禁止:
- 调用工具。
- 重新诊断。
- 重新判断根因。
- 新增服务名、订单号、时间、指标值、错误码、根因。
- 把 hypothesis 写成 confirmed claim。
- 把相关性写成因果。
19.2 Composer 输入
Composer 输入应由 ChatService 根据 Executor、Gatekeeper、Verifier 的结果过滤得到。
{
"original_query": "用户原始问题",
"verdict": "LOW_CONFID",
"allowed_claims": [
{
"claim_id": "claim-1",
"claim_type": "symptom",
"claim_text": "订单123支付失败期间出现 ERR_TIMEOUT,请求耗时 5.3 秒。"
}
],
"allowed_hypotheses": [
{
"hypothesis_text": "网关超时阈值可能偏低。",
"needed_evidence": ["网关超时阈值配置"]
}
],
"missing_info": [
"缺少网关侧具体配置参数,无法确认阈值是否过低。"
],
"recommended_actions": [
{
"action_text": "检查网关侧超时阈值配置",
"reason": "当前缺少网关配置参数,需确认阈值是否低于实际请求耗时。"
}
],
"rationale": "已有证据支持请求超时现象,但根因仍缺少直接证据。"
}
| 字段 | 类型 | 定义 |
|---|---|---|
original_query |
string | 用户原始问题 |
verdict |
string | Verifier verdict |
allowed_claims |
array | Verifier 允许作为确认事实输出的 claims |
allowed_hypotheses |
array | Verifier 允许作为合理推测输出的 hypotheses |
missing_info |
array | 证据缺口 |
recommended_actions |
array | 允许输出的建议动作 |
rationale |
string | Verifier 总体判定理由 |
Composer 不应接收 raw tool output,也不应接收未经筛选的完整 Executor 输出。
19.2.1 Composer 输入组装规则
ChatService 根据 Verifier 输出组装 Composer 输入:
| Verifier 结果 | Composer 输入 |
|---|---|
claim_checks[].verification = direct_observation |
放入 allowed_claims |
claim_checks[].verification = reasonable_inference |
可放入 allowed_claims,但表达时不得写成唯一根因 |
claim_checks[].verification = overstated |
不放入 allowed_claims,可降级为 allowed_hypotheses 或 missing_info |
claim_checks[].verification = unsupported |
不输出为事实,放入 missing_info |
claim_checks[].verification = external_unknown |
不输出,必要时放入 missing_info |
claim_checks[].verification = contradicted |
不输出,触发 REJECT 降级表达 |
allowed_hypotheses 只允许来自:
- 原始
hypotheses。 hypothesis_checks判为reasonable_hypothesis的条目。- 被 Verifier 判为
overstated后降级的 claim。
Composer 执行条件:
Verifier 输出有效 verdict 后才执行 Composer。
executor_output_parse_status = missing/malformed 时,可跳过 Composer,直接使用固定低置信模板。
gatekeeper_result.status = fail 且 verdict = REJECT 时,Composer 只能接收 allowed_claims、missing_info、recommended_actions,不接收 allowed_hypotheses。
REJECT 场景下:
allowed_hypotheses必须为空。user_facing_answer不得出现根因结论。- 推荐动作只能是补证据或人工复核类动作。
19.3 Composer 输出
Composer 输出严格 JSON。
{
"answer_summary": "当前已确认订单123支付失败期间出现 ERR_TIMEOUT,请求耗时 5.3 秒;网关阈值是否过低尚未确认。",
"recommended_actions": [
{
"action_text": "检查网关侧超时阈值配置",
"reason": "当前缺少网关配置参数,需确认阈值是否低于实际请求耗时。"
}
],
"user_facing_answer": "您的订单123在支付失败期间出现了请求超时,记录显示 ERR_TIMEOUT,耗时约 5.3 秒。目前还不能确认网关阈值配置就是根因,因为缺少网关侧具体配置参数。建议下一步检查网关超时阈值,并与该请求耗时进行比对。"
}
| 字段 | 类型 | 定义 |
|---|---|---|
answer_summary |
string | 一句话摘要,只能总结 Verifier 允许输出的内容 |
recommended_actions |
array | 面向用户的建议动作 |
recommended_actions[].action_text |
string | 建议动作 |
recommended_actions[].reason |
string | 建议原因 |
user_facing_answer |
string | 最终面向用户的中文答案 |
19.4 Verdict 表达规则
PASS
- 可以表达确认结论。
- 只能使用
allowed_claims和recommended_actions。 - 只有当
allowed_claims中存在 root cause 类型 claim 时,才能使用“根因已确认”类表述。
LOW_CONFID
- 必须说明当前证据仍有缺口。
- 必须区分“已确认信息”和“可能方向”。
- 不得把
allowed_hypotheses写成确认结论。 - 必须包含至少一个证据缺口或下一步建议。
REJECT
- 必须说明当前无法基于已获取证据生成可靠结论。
- 不得输出根因结论。
- 只输出已确认信息和下一步建议。
19.5 Composer Prompt 草案
你是 Answer Composer。你的职责是把 Verifier 允许输出的结构化材料,组织成用户可读的中文答案。
边界:
- 你不负责诊断。
- 你不调用工具。
- 你不补充新事实。
- 你不重新判断根因。
- 你只能使用输入中的 allowed_claims、allowed_hypotheses、missing_info、recommended_actions、rationale。
输入字段:
- original_query:用户原始问题
- verdict:PASS / LOW_CONFID / REJECT
- allowed_claims:允许作为确认事实输出的结论
- allowed_hypotheses:允许作为合理推测输出的内容
- missing_info:证据缺口
- recommended_actions:建议动作
- rationale:Verifier 判定理由
表达规则:
1. 如果 verdict=PASS:
- 可以表达确认结论。
- 只能使用 allowed_claims 和 recommended_actions。
2. 如果 verdict=LOW_CONFID:
- 必须说明当前证据仍有缺口。
- 必须区分“已确认”和“可能方向”。
- 不得把 allowed_hypotheses 写成确认结论。
3. 如果 verdict=REJECT:
- 必须说明当前无法基于已获取证据生成可靠结论。
- 不得输出根因结论。
- 只输出已确认信息和下一步建议。
禁止:
- 禁止新增输入中不存在的服务名、订单号、时间、指标值、错误码、根因。
- 禁止把相关性写成因果。
- 禁止把假设写成事实。
- 禁止输出 Markdown。
- 禁止输出 JSON 之外的任何文字。
输出格式:
{
"answer_summary": "...",
"recommended_actions": [
{
"action_text": "...",
"reason": "..."
}
],
"user_facing_answer": "..."
}
20. 当前代码影响面
本 issue 按当前三 Agent 实现增量落地,不改 Planner,不引入 Controller。
| 模块 | 当前职责 | 本次改动 |
|---|---|---|
chat-planner-prompt.md |
选择 skill、拆解计划 | 不改 |
chat-executor-prompt.md |
执行工具并输出 executor_evidence_v1 |
改为输出 executor_evidence_v2,移除最终表达字段 |
VerifierInputHook |
解析 Executor 输出、构造 Verifier payload | 收敛为 parse + trace summary + Gatekeeper + payload |
ToolTraceSummaryService |
从 tool_invocation 汇总证据索引 |
原则上不改;只有 Gatekeeper 缺少比对字段时才补最小字段 |
chat-verifier-prompt.md |
校验 executor_final_answer 与证据 |
改为校验 executor_structured_output.claims 的可推导性 |
ChatService.parseVerifierDecision(...) |
解析 facts_checked |
增加 claim_checks 解析和 facts_checked 兼容生成 |
ChatService.persistVerifierEvaluation(...) |
写入 verifier 审计结果 | 增加 gatekeeper_result、claim_checks、Composer 结果 |
ChatService 最终答案渲染 |
PASS 使用 user_facing_answer,LOW_CONFID/REJECT 用模板 |
改为由 Composer 或固定降级模板生成最终答案 |
第一版落地原则:
- Planner 不改。
- Gatekeeper 仍放在 Verifier 的 hook,即
VerifierInputHook。 VerifierInputHook原有结构校验迁移到 Gatekeeper,hook 自身只保留 JSON parse。- 兼容期保留
executor_final_answer,但只作为 raw debug 字段。 - 兼容期保留
facts_checked,但由claim_checks映射生成。
21. 实施阶段
每个阶段都必须作为独立 OpenSpec change 落地。后续 agent 不应直接从本节复制代码实现,而应把本节转成该阶段的 proposal.md、design.md、spec.md 和 tasks.md。
阶段一:Executor V2 输出契约
状态:已完成并归档。
已完成记录:
- OpenSpec archive:
openspec/changes/archive/2026-07-07-executor-v2-output-contract - devflow:
devflow/projects/2026-07-07-executor-v2-output-contract - commit:
050cbc8 feat(agent): add executor evidence v2 contract
目标:先让 Executor 不再输出最终诊断话术,只输出结构化诊断材料。
改动范围:
- 更新
chat-executor-prompt.md。 answer_version从executor_evidence_v1改为executor_evidence_v2。- 移除
diagnosis_summary和user_facing_answer。 - 保留
claims、hypotheses、recommended_actions、missing_info的整体形状。
验收标准:
- Executor 示例输出可被 JSON 解析。
- 输出中不再出现
diagnosis_summary、user_facing_answer。 claims[].evidence_bindings仍要求非空。- 现有流程即使尚未接入 Composer,也不会把 Executor raw JSON 直接当最终答案泄露给用户。
已知阶段性债务:
ChatService仍有临时 V2 renderer,用于 Composer 上线前避免 raw JSON 外泄。- Verifier 仍以
facts_checked为主,claim_checks等待阶段三。
阶段二:Gatekeeper 接入 VerifierInputHook
状态:已完成并归档。
已完成记录:
- OpenSpec archive:
openspec/changes/archive/2026-07-07-executor-gatekeeper-hook - devflow:
devflow/projects/2026-07-07-executor-gatekeeper-hook - commit:
c5e496e feat(agent): add executor gatekeeper hook
目标:在 Verifier 之前用确定性规则拦截物理级幻觉。
改动范围:
- 新增 Gatekeeper 校验服务,第一版先覆盖必要规则。
- 在
VerifierInputHook中调用 Gatekeeper。 - 将
gatekeeper_result写入 Verifier payload。 - 将
gatekeeper_result持久化到self_evaluation.verifier_evaluation。 - 更新
chat-verifier-prompt.md,明确 Gatekeeper fail 时不得 PASS。
第一版规则范围:
| 规则 | 必须实现 | 说明 |
|---|---|---|
schema.executor_v2 |
是 | 校验 V2 基础字段、移除字段、数组字段 |
evidence.invocation_ref |
是 | 校验 invocation id 存在于当前 session 且工具名匹配 |
evidence.excerpt_similarity |
否 | 可作为后续阶段或 warn-only 增强 |
claim.hallucination_phrases |
否 | 可作为后续配置化规则增强 |
claim.evidence_utilization |
否 | 第一版不建议硬启用,避免中文分词误杀 |
验收标准:
- JSON parse 成功但 schema 不合法时,
executor_output_parse_status.status=valid,gatekeeper_result.status=fail。 - 编造不存在的
source_invocation_ids时,failed_rules包含evidence.invocation_ref。 tool_name与真实 invocation 不匹配时,Gatekeeper fail。- 缺省
hypotheses、recommended_actions、missing_info时,传给 Verifier 前会 normalize 为[]。 - 旧的
VerifierInputHook结构校验不再和 Gatekeeper 重复维护。 diagnosis_session.self_evaluation.verifier_evaluation.gatekeeper_result可回溯到本轮校验结果。
建议测试:
ExecutorGatekeeperServiceTestVerifierInputHookTestChatServiceSequentialAgentTest
建议验证命令:
mvn "-Dtest=ExecutorGatekeeperServiceTest,VerifierInputHookTest,ChatServiceSequentialAgentTest" test
cmd /c openspec validate --specs
阶段退出条件:
- OpenSpec change 已 archive。
- devflow 已回填 brief、evidence、decisions、acceptance。
- 本阶段代码和文档已单独 commit。
阶段三:Verifier V2 可推导性校验
状态:实施中。
当前 OpenSpec change:
openspec/changes/executor-verifier-claim-checks
接手说明:
- 如果该 active change 仍存在,继续完成阶段三,不要启动阶段四。
- 阶段三已有 OpenSpec proposal/design/spec/tasks,后续实现应先对照
tasks.md逐项完成。 - 阶段三完成后必须 archive、回填 devflow、单独 commit,推荐提交信息:
feat(agent): add verifier claim checks。
目标:Verifier 不再逐字扫描自然语言,而是校验 claim 是否能由证据合理推出。
改动范围:
- 更新
chat-verifier-prompt.md。 - Verifier 主输入改为
executor_structured_output。 - 输出新增
claim_checks。 - 兼容期继续输出或由代码生成
facts_checked。 parseVerifierDecision(...)能处理claim_checks和旧facts_checked。
核心设计:
- Verifier 主校验对象是
executor_structured_output.claims。 executor_raw_output/executor_final_answer只作为 debug/fallback 字段,不作为新增事实来源。- Verifier 不要求 claim 与证据逐字一致,而是判断是否可以合理推出。
- Gatekeeper fail 时,Verifier 不允许输出
PASS。
验收标准:
direct_observation、reasonable_inference、overstated、unsupported、external_unknown、contradicted均有测试覆盖。gatekeeper_result.status=fail时 Verifier 不允许输出PASS。- malformed/missing Executor 输出不回退自然语言抽事实,直接进入
LOW_CONFID。 facts_checked兼容字段能继续支撑现有低置信模板、retry_context 和评测用例。
建议测试重点:
parseVerifierDecision(...)能解析claim_checks。facts_checked能从claim_checks兼容映射。gatekeeper_result.status=fail+ Verifier 返回 PASS 时,代码侧应降级或测试 prompt 禁止该行为。unsupported/overstated能进入低置信模板需要的 missing evidence 语义。
建议验证命令:
mvn "-Dtest=ExecutorGatekeeperServiceTest,VerifierInputHookTest,ChatServiceSequentialAgentTest" test
cmd /c openspec validate executor-verifier-claim-checks
cmd /c openspec validate --specs
阶段退出条件:
- Verifier prompt 已不再要求逐字扫描最终自然语言答案。
- 审计中同时可见
claim_checks和兼容facts_checked。 - 现有低置信重试链路不回归。
- OpenSpec change 已 archive。
- devflow 已回填 brief、evidence、decisions、acceptance。
- 本阶段代码和文档已单独 commit。
阶段四:Composer 输出最终答案
状态:未开始。
目标:把最终用户表达从 Executor 中移出,由 Composer 基于 Verifier 允许的材料生成。
改动范围:
- 新增
chat-composer-prompt.md或等价 Composer 调用。 - ChatService 在 Verifier 之后组装 Composer 输入。
- Composer 只接收
allowed_claims、allowed_hypotheses、missing_info、recommended_actions、rationale。 - PASS/LOW_CONFID/REJECT 均不再读取 Executor 的
user_facing_answer。
核心设计:
- Composer 是表达层,不是诊断层。
- Composer 不接收 raw tool output。
- Composer 不接收未经筛选的完整 Executor output。
- ChatService 负责根据
claim_checks过滤出allowed_claims。 overstatedclaim 不得作为确认事实输出,可降级为 hypothesis 或 missing_info。
验收标准:
- Composer 输出严格 JSON,包含
answer_summary、recommended_actions、user_facing_answer。 - Composer 不接收 raw tool output。
REJECT时allowed_hypotheses=[],最终答案不出现根因结论。LOW_CONFID时必须区分已确认信息和可能方向。PASS时只有存在 root cause 类型 allowed claim,才允许表达“根因已确认”。ChatService不再依赖临时 V2 renderer 生成 PASS 用户答案。
建议测试重点:
- Composer 输入不包含 raw tool output。
- 未通过 Verifier 的 claim 不进入最终答案。
LOW_CONFID能输出已确认信息、缺口、下一步建议。REJECT不输出 root cause 结论。- Composer malformed 输出时有降级策略,不向用户泄露 raw JSON。
阶段退出条件:
- 最终用户答案唯一来源是 Composer 或固定降级模板。
- Executor 的
user_facing_answer不再参与任何最终答案路径。 - Stage 1 的临时 renderer 被移除或明确只作为关闭 Composer 时的安全降级,不恢复 Executor 表达。
阶段五:回归评测与审计闭环
状态:未开始。
目标:确认新链路真的降低证据归因幻觉,而不是只改变字段名。
改动范围:
- 扩展
VerifierInputHookTest。 - 扩展
ChatServiceSequentialAgentTest。 - 增加诊断 eval fixtures,覆盖伪造 ID、张冠李戴、过度推断、缺证据降级。
- 检查
diagnosis_session.self_evaluation中的审计字段。
验收标准:
- 伪造 invocation id 必须进入
REJECT或至少不可PASS。 - excerpt 相似度不足但 invocation 合法时,默认进入
LOW_CONFID,不直接误杀为严重伪造。 - Executor 把 hypothesis 写进 claim 时,Verifier 至少判
overstated或unsupported。 - 最终答案中不再出现未通过 Verifier 的 claim。
- 审计链路能从 final answer 回溯到 Composer 输入、Verifier 判定、Gatekeeper 结果、tool invocation。
建议 fixture 场景:
| 场景 | 预期 |
|---|---|
| 伪造不存在的 invocation id | Gatekeeper fail,最终不可 PASS |
| 引用真实 id 但 tool_name 不匹配 | Gatekeeper fail,倾向 REJECT |
| excerpt 与真实工具输出明显不符 | Gatekeeper fail 或 warn,最终不可 PASS |
| hypothesis 写成 root_cause claim | Verifier 判 overstated 或 unsupported |
| Composer 输入不含某错误码但输出包含该错误码 | 测试失败 |
阶段退出条件:
- 关键回归测试已进入 CI 可运行测试集合。
- eval fixture 覆盖证据归因幻觉的主路径。
- 文档、OpenSpec、devflow 与实际代码行为一致。
22. 兼容与回滚策略
22.1 兼容字段
第一版保留以下兼容字段,降低一次性改动风险:
| 字段 | 保留原因 | 后续处理 |
|---|---|---|
executor_final_answer |
当前 Verifier payload、fallback、日志中仍使用该名称 | 作为 deprecated raw 字段保留一版 |
facts_checked |
当前低置信模板、retry_context、评测可能依赖 | 从 claim_checks 映射生成 |
traceability_version |
当前审计结构已有字段 | V2 可改为 v2,但不作为功能判断依据 |
22.2 回滚开关
建议保留最小运行时开关:
structuredOutputV2.enabled
gatekeeper.enabled
composer.enabled
回滚策略:
- 仅 Executor V2 出问题:关闭
structuredOutputV2.enabled,回到 V1 prompt。 - Gatekeeper 误杀:关闭
gatekeeper.enabled,Verifier 仍可按 V2 claim 校验运行。 - Composer 输出异常:关闭
composer.enabled,回到固定 LOW_CONFID/REJECT 模板;PASS 暂不直接使用 Executor 输出。
即使回滚 Composer,也不能恢复使用 Executor 的 user_facing_answer,否则会重新引入本 issue 要解决的问题。
23. 数据库与审计最小字段
不新增表,第一版继续写入 diagnosis_session.self_evaluation。
建议结构:
{
"verifier_evaluation": {
"verdict": "LOW_CONFID",
"groundedness_score": 0.62,
"critical_fact_count": 1,
"claim_checks": [],
"facts_checked": [],
"rationale": "...",
"round": 1,
"traceability_version": "v2",
"executor_output_parse_status": {},
"executor_structured_output": {},
"tool_trace_summary": [],
"gatekeeper_result": {},
"composer_output": {}
}
}
字段控制原则:
- Gatekeeper 只落
status、failed_rules、warnings、errors。 - 不在数据库中保存规则元数据完整快照。
- 不新增
checked_at、rule_set_version、details等重字段。 - 如果后续需要复盘规则版本,再单独设计审计版本字段。
24. 实施前待确认点
以下问题不阻塞第一阶段,但实施前需要明确默认答案:
| 问题 | 建议默认 |
|---|---|
| Composer 是第四个 Agent 还是普通服务调用? | 作为 chat_composer Agent,但由 ChatService 在 Verifier 后过滤输入再调用 |
schema.executor_v2 失败是否 REJECT? |
第一版 LOW_CONFID,因为它可能是格式退化,不一定是伪造 |
evidence.excerpt_similarity 失败是否 REJECT? |
默认 LOW_CONFID;只有明显张冠李戴才 REJECT |
claim.evidence_utilization 是否启用? |
第一版禁用或 warn-only,避免中文分词误杀 |
| 是否改 Planner? | 不改 |
| 是否改现有重试机制? | 不改;Gatekeeper 失败第一版不触发自动回退重试 |
25. Definition of Done
本 issue 完成时,需要同时满足:
- Executor 不再输出
diagnosis_summary和user_facing_answer。 - Gatekeeper 已接入
VerifierInputHook,并进入 Verifier payload 与self_evaluation。 - Verifier 以
claim_checks为主输出,并保留facts_checked兼容。 - Composer 负责最终
user_facing_answer。 - PASS 答案不包含未通过 Verifier 的 claim。
- LOW_CONFID/REJECT 答案不泄露 Executor 原始结论。
- 关键回归测试覆盖伪造 ID、工具名不匹配、excerpt 张冠李戴、hypothesis 写成事实、schema 退化。