Files
SuperBizAgent-java/mvp/issues/active/ISS-016-diagnosis-information-gain-stop-contract.md
T
zhuyongxin 38f781b157 feat(harness): complete protocol repair stop and archive ISS-016
Add repairable INVALID_PROGRESS_PROTOCOL observations, independent
PROGRESS_PROTOCOL_VIOLATED saturation, and controlled release paths.
Archive the OpenSpec change after syncing main specs and devflow.
2026-07-27 19:10:07 +08:00

359 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ISS-016 诊断 Agent 缺少基于信息增益的停止契约
**状态**:已完成(含协议修复反馈与连续协议错误兜底停止);OpenSpec 待用户确认归档
**严重程度**:高
**发现时间**:2026-07-26
**来源**:知识库无直接答案场景的真实 E2E 与停止策略复盘
**关联**:ISS-015、ISS-012、ISS-002、ISS-009
**架构设计**:[Diagnosis Agent 信息增益与停止控制架构](../../architecture/diagnosis-information-gain-stop-architecture.md)
---
## 1. 背景
当前单体 Diagnosis ReAct Agent 已具备 Tool 调用、Run 预算、EvidenceGuard、SemanticGuard、Trace 和安全发布边界,但停止条件仍主要依赖模型自行结束或 Harness 资源预算耗尽。
这使系统能够限制一次 Run 最多消耗多少资源,却不能稳定判断“继续查询是否还可能增加有效诊断信息”。当知识库只有通用参考资料、日志查询持续为空或用户缺少必要查询条件时,Agent 可能不断改写关键词和扩大尝试,最终由预算被动终止。
该问题不能简化为“Prompt 没写好”或“预算太大”:Prompt、模型终态契约、Tool 客观结果、模型语义评价和 Harness 强制停止之间缺少完整闭环。
## 2. 真实复现
用户 Query:
```text
诊断切换企业失败的问题
```
真实 E2E:
- `sessionId=iss015-enterprise-switch-e2e-20260726000222`
- `runId=054afbb1-0bb3-48d2-8cb8-ddb31d20b2b6`
- SSE:`metadata -> ROUTING -> DIAGNOSIS_RUNNING -> failure(INTERNAL_FAILURE) -> done(FAILED)`
- Run 耗时约 48 秒。
- Agent 已完成多轮模型和 Tool 调用。
- `query_logs` 多次返回 `NO_EVIDENCE`。
- `lookup_knowledge` 的检索层结果主要为 `REFERENCE`,但 Harness 投影仍表现为 `EVIDENCE_FOUND`。
- 最后一次 Tool 调用触发 `BUDGET_EXHAUSTED`,Run 未生成最终 Draft,也未进入安全校验阶段。
- 数据库和 Trace 中存在执行过程,但用户只能看到“当前暂时无法处理该请求,请稍后重试”。
这不是“没有执行过程”,而是 Agent 未能在无新增信息时主动结束,预算终止又被发布链路映射成了技术失败。
### 2.1 实施后验证
同一 Query 的最终 named SSE E2E:
- `sessionId=iss016-final-20260726-a`
- `runId=3ab22ed7-d0ed-45d8-b928-dce5790c0542`
- SSE 发布 `SAFE_FALLBACK`,`type=MISSING_REQUIRED_CONTEXT`,最终 `done.outcome=FALLBACK`。
- 数据库记录 `status=SUCCESS`、`intent=DIAGNOSIS`、`release_outcome=FALLBACK`、`tool_call_count=0`、`total_token_count=2890`,answer 非空。
- Trace 为 `RUN_STARTED -> ROUTING_ATTEMPT -> ROUTING_DECISION -> AGENT_MODEL_STEP -> EVIDENCE_GUARD_INITIAL -> RELEASE_DECISION/FALLBACK -> RUN_FINISHED/FALLBACK`。
本次零 Tool 是合法行为:原 Query 缺少企业、时间范围和错误信息,模型直接报告缺失上下文。另有 focused tests 固定“非法 Draft + 已验真 ProgressSnapshot -> INSUFFICIENT_EVIDENCE”和“非法 Draft + 无安全进展 -> FAILED”边界,避免模型随机返回非 JSON 时再次丢失已完成过程。
### 2.2 Token 与拒绝审计补充验证
2026-07-27 增加按模型组件/轮次的 Token 明细、AgentStep Token 回填、Run 对账摘要和 Tool 请求拒绝 Trace。审计事件不保存 Prompt、模型正文、Tool 参数或原始响应。
缺少上下文 E2E:
- `sessionId=audit-e2e-20260727-001`
- `runId=87f38bea-108b-482a-8b72-d0890cc825f2`
- Router Token `248`,Diagnosis Agent Token `2635`,Run 总 Token `2883`。
- `step_count=1`,对应 `AgentStep.token_count=2635`;Tool 预算计数和实际执行均为 `0`。
- `RUN_FINISHED.tokens_reconciled=true`,Trace 序列 `1..9` 连续。
有界 Tool E2E:
- `sessionId=audit-e2e-20260727-003`
- `runId=3f8f4a0c-3b96-4942-be1e-61cc1431b337`
- 5 次 Tool 实际执行:`lookup_knowledge=4`、`query_logs=1`。
- 9 次 Tool 请求被 `INVALID_PROGRESS_PROTOCOL` 拒绝,均有独立 `TOOL_REQUEST_REJECTED`,没有伪装成 canonical invocation。
- 13 个 Diagnosis Agent 轮次加 1 个 Router 调用,总 Token `68469`;各轮 AgentStep Token 之和加 Router Token与 Run 总量一致。
- `RUN_FINISHED.tokens_reconciled=true`,Trace 序列 `1..52` 连续。
该 E2E 证明审计可观察、可对账、可按组件和轮次重放,也暴露了新的停止缺口:连续协议拒绝当前不产生 `NO_GAIN`,模型可在 Harness 拒绝后继续消耗模型轮次。这个 Run 的 Token 消耗不合理;后续需要单独确认“连续 `INVALID_PROGRESS_PROTOCOL` 是否进入确定性停止”的行为设计,不能仅靠提高或降低预算掩盖。
## 3. 核心问题
### 3.1 成功定义只有“完成诊断”
如果模型的唯一合法输出是完整 `DiagnosisDraft`,那么“证据不足”和“需要用户补充信息”不是一等终态。即使模型已经知道当前信息无法支持根因,它仍可能认为任务尚未完成,并继续调用 Tool。
### 3.2 Tool 客观结果与诊断价值混淆
Tool 可以可靠说明查询是否成功、范围、数量、检索分数、是否为空和是否截断,但不能判断结果是否支持当前诊断假设。
例如:
- 检索到三篇相似文档,只能说明存在候选内容,即当前兼容状态 `EVIDENCE_FOUND`;
- 文档是直接证据、背景材料还是无关内容,需要模型结合当前假设判断;
- 当前范围没有日志,只能说明 scoped `NO_EVIDENCE`,不能说明故障不存在。
把检索层 `REFERENCE` 直接表达为诊断层 `EVIDENCE_FOUND`,会向模型发送“仍在取得进展”的错误信号。
### 3.3 模型没有显式的信息价值判断义务
当前系统主要通过“模型是否继续调用 Tool”推测它是否认为查询有价值。对于无法由代码确定质量的成功非空结果,模型没有被要求给出最小、机器可读的信息增益判断:
```text
information_gain = GAINED | NO_GAIN
```
因此 Harness 无法区分“合理继续收集证据”和“换关键词重复尝试”。
### 3.4 Harness 只有资源预算,没有无进展契约
模型调用次数、Tool 调用次数、Token、字节和超时属于资源边界。它们是最后的安全保护,不应承担正常停止策略。
Harness 当前缺少以下可观察状态:
- 连续 `NO_GAIN` 次数;
- 重复或等价查询;
- 当前收集状态 `COLLECTING / SATURATED`;
- 模型对需要语义评价的结果给出的 `GAINED / NO_GAIN`。
## 4. 顶层设计原则
### 4.1 不要求模型必须找到根因
诊断任务的首要目标是避免无依据结论,而不是每次都输出根因。模型只输出 `DiagnosisDraft`,不产生额外的诊断生命周期状态:
```text
有受证据支持的 conclusion
-> ReleaseOutcome.SUCCESS
conclusion=null,且形成了诚实、有界的结果
-> ReleaseOutcome.FALLBACK
不可恢复的 Tool、模型或基础设施故障
-> ReleaseOutcome.FAILED
```
证据不足和缺少查询条件是 `FALLBACK` 的不同原因,不是与 `SUCCESS / FALLBACK / FAILED / CANCELLED` 平行的第二套状态机。现有 `SafeFallback.type` 负责表达 `INSUFFICIENT_EVIDENCE / MISSING_REQUIRED_CONTEXT` 等发布原因。
### 4.2 Tool 提供事实,模型判断价值
| 组件 | 职责 |
|---|---|
| Tool / Adapter | 返回客观执行状态、查询范围、数量、来源和有界结果 |
| Diagnosis Agent | 判断结果对当前假设的语义价值 |
| Harness | 记录进展、识别重复和不一致、执行确定性停止 |
| EvidenceGuard / SemanticGuard | 校验最终引用真实性和结论支持度 |
| Release | 把正常停止与技术失败发布为不同用户结果 |
不应让 Tool 声称业务证据价值,也不应让 Harness 通过规则替代模型完成诊断推理。
### 4.3 模型主动停止,Harness 确定性兜底
模型应优先根据 Prompt 和显式进展状态主动选择结束;模型未能收敛时,Harness 必须根据可验证规则停止后续 Tool 调用。
只加强 Prompt 不足以形成系统保证;只增加硬预算则会继续产生高成本、低信息量的失败 Run。
## 5. 目标交互契约
### 5.1 已确认的 Tool 侧决策
1. Tool 由服务端通过模型原生 Tool Calling 通道动态注册;Prompt 不写死 Tool 名称或 Schema,也不重复插入 Tool Schema 占位符。
2. Tool 和 Projector 只产生客观结果,不判断业务根因和语义信息增益。
3. `NO_EVIDENCE` 由 Projector 根据结果集合为空确定,不是 LLM 推导。
4. `REFERENCE` 由 RAG 后处理规则根据归一化相似度及查询提示命中情况确定,不是 LLM 推导。
5. `RagResultProjector` 必须兼容读取上游 `relevanceLevel` 或 `relevance_level`,并统一保留为 `relevance_level`。
6. `EVIDENCE_FOUND` 暂时保留,但只表示存在候选内容,不表示存在能够支持诊断结论的证据。
7. 第一版不引入 `new_count`,不建立跨 Tool 通用内容指纹。
8. 不引入显式 `next_action`。模型发起 Tool Call 表示继续,输出 Draft 表示结束。
### 5.2 Tool 结果双视图
Tool 原始结果经过标准化后产生内部 Canonical Tool Result,再分别提供:
| 视图 | 消费者 | 内容 |
|---|---|---|
| Harness Control View | Harness / Canonical Store | 执行状态、数量、规范化 scope、相关度、重复指纹、预算与饱和控制信息 |
| Model Observation | Diagnosis Agent | `tool_call_id`、实际 scope、有界 evidence、`evidence_status`、粗粒度 `relevance_level`、`truncated` |
原始相似度、检索轨迹、重复指纹、低收益计数、阈值、预算和原始 Tool Response 不进入模型上下文。只有 Harness 必须改变模型行为时,才注入有界 `STOP_REQUIRED` 指令和已检查范围摘要。
详细 Tool 架构图和调用流程图见架构文档的“Tool 结果与上下文边界”章节。
### 5.3 信息增益与停止
信息增益只保留:
```text
information_gain = GAINED | NO_GAIN
```
赋值规则:
```text
FAILED
-> 技术故障流程,不产生 information_gain
evidence_status=NO_EVIDENCE
或重复的规范化 tool + scope
-> Harness 直接赋 NO_GAIN
其他成功非空结果,包括 relevance_level=REFERENCE
-> Diagnosis Agent 判断 GAINED / NO_GAIN
```
Harness 使用连续 `NO_GAIN` 维护 `COLLECTING / SATURATED`。连续次数达到配置项 `stop-after-consecutive-no-gain` 后进入 `SATURATED`;默认值为 `2`,只对新 Run 生效,且不进入模型上下文。
硬调用上限不等于信息饱和。Harness 分别记录:
```text
连续 NO_GAIN 达到阈值 -> stop_reason=INFORMATION_SATURATED
达到 Tool 或 Run 硬预算 -> stop_reason=BUDGET_LIMIT_REACHED
```
`SATURATED` 只表示信息饱和;预算限制属于独立的资源保护终止原因。
模型不显式声明行动状态。Harness 从模型实际发起 Tool Call 或输出 Draft 推导其行为。
### 5.4 Tool Call Envelope 协议
模型通过下一次 Tool Call 的通用 Envelope 回传上一轮语义评价:
```json
{
"previous_observation": {
"tool_call_id": "call-123",
"information_gain": "NO_GAIN"
},
"input": {
"query": "下一次查询参数"
}
}
```
协议约束:
- `previous_observation` 只在上一轮存在待模型评价的 Tool Observation 时必填;首次调用以及上一轮已由 Harness 客观赋值时省略。
- Harness 在执行新 Tool 前校验 `tool_call_id`,应用 `information_gain` 并更新 `COLLECTING / SATURATED`。
- 如果应用评价后进入 `SATURATED`,Harness 拒绝本次 Tool 调用并返回 `STOP_REQUIRED`。
- Harness 消费并剥离 `previous_observation`;业务 Tool 只接收 `input` 中原有的业务参数。
- 模型选择直接输出 DiagnosisDraft 时,不存在需要放行的下一次 Tool Call,因此无需额外回传最后一轮评价;Harness 将其记录为模型主动结束。
- 上一个待评价结果未完成评价时,Harness 不接受新的 Tool 调用。
这是模型侧 Tool 调用协议变更,但不改变业务 Tool 的入参协议,也不增加独立 Progress Judge 调用,不把 Harness 内部状态暴露给模型,不把 `information_gain` 混入最终用户可见的 DiagnosisDraft。
### 5.5 已确认的 Prompt 原则
Prompt 使用中文,并保持最小职责,不解释 Projector、计数器、阈值、预算或 Harness 状态机。Tool 名称和 Schema 不写死在正文中,也不拼接进 Prompt,由服务端通过模型原生 Tool Calling 通道动态注册。
Prompt 必须明确:
- 模型不必须得出诊断结论或根因,但必须完成一个诚实、有界的 `DiagnosisDraft`;
- `conclusion=null` 的证据不足结果是合法完成,不是失败;
- Tool 调用次数可以为零;缺少有效查询所需的企业、时间、服务或错误信息时,应直接在 `limitations.missing_info` 中列出缺口;
- 不得为了表现“已经排查”而执行没有明确范围、预期不会产生新诊断信息的 Tool 调用;
- Tool 返回内容事实正确、表述完整或结果非空,不代表它对当前诊断有信息增益;
- 只有新增事实确认、排除或缩小当前诊断假设时,才属于 `GAINED`;
- 通用知识、背景说明、重复内容或不能改变当前判断的内容属于 `NO_GAIN`;
- `NO_GAIN` 后不得通过改写相似关键词或重复相同 scope 继续尝试;
- 如果仍存在范围明确、并可能产生新诊断信息的不同查询,可以继续;否则应主动停止并完成证据不足结果;
- 收到服务端 `STOP_REQUIRED` 后必须停止调用 Tool。
Prompt 不要求模型输出 `next_action`。模型发起 Tool Call 或输出 Draft 即表达其实际选择。
## 6. 实施阶段(已完成)
### 阶段 1:终态与 Prompt 契约
- 保持 `conclusion=null` 作为合法的证据不足 Draft,并明确“有依据地停止”属于成功完成。
- 按 5.5 的最小原则重写中文 Prompt,Tool 定义只通过模型原生 Tool Calling 通道动态注册。
- 允许零次 Tool 调用;查询条件不足时使用现有 `limitations.missing_info`,不新增 `required_context`。
- 明确正确但不能推进当前诊断的 Tool 内容属于 `NO_GAIN`,不得触发等价重试。
- 保持 Tool 预算作为最终安全保护。
### 阶段 2:Tool 状态与模型评价解耦
- 标准化 Canonical Tool Result,并拆分 Harness Control View 与 Model Observation。
- `RagResultProjector` 兼容并保留 `relevance_level`。
- 不把检索命中、候选文档或 `REFERENCE` 自动等同于诊断证据。
- 通过白名单控制进入模型上下文的 Tool 字段。
### 阶段 3:Run 级最小进展状态
- 在 RunContext 中维护已检查 `tool + scope`、连续 `NO_GAIN` 和 `COLLECTING / SATURATED`。
- 只对 `tool_name + normalized_scope` 做确定性参数去重;第一版不做自然语言语义去重。
- 使用配置项 `stop-after-consecutive-no-gain` 控制连续低收益阈值,默认值为 `2`。
- 实现通用 Tool Call Envelope,由下一次 Tool Call 回传上一轮 `information_gain`,并在进入业务 Tool 前剥离控制字段。
- 不把 Prompt、内部 thought 或原始 Tool 载荷混入普通 Trace 和用户结果。
### 阶段 4:Harness 确定性停止
- 拒绝相同 `tool_name + normalized_scope` 的重复查询。
- 达到无进展条件时阻止新的 Tool 调用。
- 分别产生 `INFORMATION_SATURATED / BUDGET_LIMIT_REACHED`,不把硬预算终止伪装成信息饱和。
### 阶段 5:安全发布与 E2E
- 每次 Tool 完成后只保存 Canonical Tool Result;Tool Loop 结束时一次性投影有界 `ProgressSnapshot`。
- `DiagnosisReleaseUseCase` 同时接受正常 Draft 和 `stop_reason + ProgressSnapshot`,统一产生 `SUCCESS / FALLBACK`;`ChatApplicationUseCase` 不再单独决定预算 Fallback 的业务内容。
- 复用现有 `SafeFallback.observed_facts / limitations / next_steps` 和前端渲染协议,并为 `FallbackType` 增加 `INSUFFICIENT_EVIDENCE / MISSING_REQUIRED_CONTEXT`。
- `conclusion=null` 不进入 EvidenceRepair;有 Tool 引用时只验证引用真实性,并从 `ProgressSnapshot` 生成 Fallback。只有存在结论时才执行完整 EvidenceGuard、EvidenceRepair 和 SemanticGuard 链路。
- 不依赖预算耗尽后的额外模型调用来生成 Fallback。
- 前端明确区分证据不足、需要补充信息和技术故障。
## 7. 非目标
- 不通过简单增加 Tool/Token 预算解决问题。
- 不把“降低最大 Tool 调用次数”当作信息增益策略。
- 不引入 `new_count`、多级质量评分或显式 `next_action`。
- 不增加独立 Progress Judge 模型调用。
- 不依赖 Prompt 作为唯一控制手段。
- 不让 Tool 或规则代码判断业务根因。
- 不重新引入已由 ISS-014 删除的 Planner/Executor/Verifier/Composer 旧业务 Graph。
- 不在本 Issue 中解决 Reasoning 原文审计治理;该内容仍由 ISS-015 跟踪。
## 8. 验收标准
- [x] 不引入 `CONFIRMED / INSUFFICIENT_EVIDENCE / NEED_MORE_INFO` 第二套诊断生命周期状态;最终状态只使用 `SUCCESS / FALLBACK / FAILED / CANCELLED`。
- [x] Tool 客观命中状态与模型诊断价值评价在契约和 Trace 中明确分离。
- [x] Tool 结果拆分为 Harness Control View 和白名单 Model Observation,Harness 内部计数、阈值和预算不进入模型上下文。
- [x] `RagResultProjector` 保留 `relevance_level`,且模型与 Harness 都能获得各自所需的有界视图。
- [x] `NO_EVIDENCE` 和重复的规范化 `tool + scope` 被 Harness 确定性标记为 `NO_GAIN`;RAG `REFERENCE` 由模型判断 `GAINED / NO_GAIN`。
- [x] 模型继续调用 Tool 前,对上一轮待评价结果返回 `GAINED / NO_GAIN`,不返回 `next_action`。
- [x] 下一次 Tool Call Envelope 能回传上一轮语义评价;Harness 应用评价后才决定是否放行,并在调用业务 Tool 前剥离控制字段。
- [x] Prompt 明确不要求模型必须得出根因,`conclusion=null` 是合法完成结果。
- [x] Prompt 允许零次 Tool 调用;缺少必要查询条件时使用 `limitations.missing_info`,不强制执行无明确目标的查询。
- [x] Prompt 明确正确但不能推进诊断的 Tool 内容属于 `NO_GAIN`,并禁止相似关键词或相同 scope 的等价重试。
- [x] `REFERENCE`、候选文档或非空结果不会自动成为支持根因的证据。
- [x] 相同 `tool_name + normalized_scope` 的重复查询能够被 Harness 识别并阻止;第一版不承诺自然语言语义去重。
- [x] `stop-after-consecutive-no-gain` 可配置且默认值为 `2`;`GAINED` 清零连续计数。
- [x] `INFORMATION_SATURATED` 与 `BUDGET_LIMIT_REACHED` 在 Trace 和 Release 输入中保持区分。
- [x] 连续无新增信息时,Run 在资源预算耗尽前收敛为正常业务结果。
- [x] 模型主动停止和 Harness 强制停止都能输出已检查来源、查询范围、客观结果、信息缺口和下一步。
- [x] 无证据不被解释为故障不存在,通用参考资料不被解释为当前故障事实。
- [x] 真正取得新证据的多轮诊断不会被无进展策略过早终止。
- [x] 原始复现 Query 不再以 `INTERNAL_FAILURE` 结束,也不再通过大量改写查询运行到 `BUDGET_EXHAUSTED`。
- [x] Tool Loop 结束时由 Canonical Tool Result 一次性投影 `ProgressSnapshot`,并映射到现有 `SafeFallback.observed_facts`,无需新增前端协议。
- [x] `DiagnosisReleaseUseCase` 统一处理正常 Draft、信息饱和和预算终止;只有不可形成安全业务结果的技术故障发布为 `FAILED`。
- [x] `conclusion=null` 不触发 EvidenceRepair;有结论时才执行完整 EvidenceGuard、EvidenceRepair 和 SemanticGuard 链路。
- [x] 最终 Draft 合同失败时丢弃非法内容;仅在当前 Run 有已验真 ProgressSnapshot 时发布 `INSUFFICIENT_EVIDENCE`,否则保持 `FAILED`。
- [x] 原始复现 Query 在缺少企业、时间和错误信息时返回 `FALLBACK + MISSING_REQUIRED_CONTEXT`,或在有限排查后返回 `FALLBACK + INSUFFICIENT_EVIDENCE`,并展示已经完成的检查。
- [x] exact `sessionId + runId` Trace 能解释每轮继续或停止的原因,但不泄露 Prompt、内部 thought、原始 Tool 载荷或凭据。
- [x] 每个 Provider Usage 可按模型组件和组件轮次审计,Diagnosis Agent Usage 回填 AgentStep,Run 结束显式记录 Token 是否对账。
- [x] Harness 拒绝的 Tool 请求与实际 `TOOL_INVOCATION` 分开记录,拒绝 Trace 不包含 Tool 参数或原始响应。
- [x] 连续 `INVALID_PROGRESS_PROTOCOL` 拒绝在硬模型/Token 预算前确定性停止:未达阈值返回可修正 observation,达到阈值交付一次 `STOP_REQUIRED/PROGRESS_PROTOCOL_VIOLATED`,继续请求 Tool 走受控停止。
## 9. 已确认的首版边界
1. 连续 `NO_GAIN` 阈值由 `stop-after-consecutive-no-gain` 配置,默认值为 `2`,后续通过固定 E2E 和评测集校准。
2. 重复检测只比较 `tool_name + normalized_scope`,不做自然语言语义去重。
3. 模型可以在首次 Tool 调用前以 `conclusion=null + limitations.missing_info` 合法结束。
4. Harness 产生内部 `stop_reason`;Release 产生 `SafeFallback.type` 和最终 `release_outcome`。
5. Tool Schema 只通过模型原生 Tool Calling 通道注册,不拼接进 Prompt。
## 10. 相关文件
- `mvp/issues/active/ISS-015-diagnosis-runtime-quality-and-reasoning-audit.md`
- `mvp/issues/archived/ISS-002-executor-unconstrained-lookup.md`
- `mvp/issues/archived/ISS-012-executor-token-budget-and-context-growth.md`
- `mvp/issues/archived/ISS-009-negative-observation-no-evidence-reference.md`
- `mvp/architecture/agent-orchestration.md`
- `mvp/architecture/harness-quality-gates.md`
- `mvp/architecture/diagnosis-information-gain-stop-architecture.md`