Files
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

183 lines
16 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.
## Context
当前 Diagnosis ReAct loop 只有模型、Tool、Token、字节和时间预算,没有“查询是否仍在产生信息”的状态。`HarnessToolInterceptor` 将完整 canonical `agent_result` 直接放入模型上下文;三个 Agent-facing Tool 使用裸业务 request 生成 Schema;`DiagnosisAgentUseCase` 只返回非空 `DiagnosisDraft`;`DiagnosisReleaseUseCase` 要求 Draft 非空并对所有 Draft 运行 EvidenceGuard/Repair/SemanticGuard。预算耗尽的临时 Fallback 位于 `ChatApplicationUseCase`,导致业务 Release 决策分散。
本变更是 L3 模型协作接口演进。公开 HTTP/SSE、业务 Tool backend、数据库表和 `SafeFallback` JSON 结构保持兼容。当前环境没有 semantic retrieval/LSP,调用链通过源码、测试和 `rg` 引用核查完成。
## Goals / Non-Goals
**Goals:**
- 在硬预算耗尽前确定性停止连续无增益 Tool 调用。
- 让模型在看到成功非空结果后,以 `GAINED / NO_GAIN` 表达该结果是否推进当前诊断。
- 允许模型零次调用 Tool 或以 `conclusion=null` 合法结束。
- 信息饱和、预算终止和主动无结论均由 Diagnosis Release 发布为有过程的安全 Fallback。
- 有结论 Draft 继续经过完整 EvidenceGuard、单次 EvidenceRepair 和 SemanticGuard。
- 控制数据、raw payload、内部 thought 和预算不进入模型上下文或用户结果。
**Non-Goals:**
- 不引入 Judge、`UNKNOWN`、分数、`new_count`、`next_action` 或第二套诊断生命周期。
- 不做自然语言语义去重或跨 Run 进展继承。
- 不改变 Tool backend 参数、公开 SSE 事件或前端字段协议。
- 不把候选文档、`REFERENCE` 或非空结果自动认定为诊断证据。
## Decisions
### 1. RunContext 持有最小线程安全 ProgressTracker
新增 `DiagnosisProgressTracker` handle,并在 `DiagnosisHarnessCore.startRun` 时用固定阈值创建。tracker 维护:连续 `NO_GAIN`、`COLLECTING / SATURATED`、可选 `stop_reason`、最后一个待模型评价的成功 Tool Call、已成功检查的规范化 scope,以及已完成 canonical key/Tool Call ID 的有序索引。
tracker 不保存 raw response、完整 Agent result 或用户可见摘要。Canonical Store 仍是 Tool 真相源;结束投影器按 tracker 的 key 索引逐条读取 canonical 记录。
替代方案是给 `CanonicalInvocationStore` 增加按 Run 枚举。拒绝,因为停止状态是短生命周期控制数据,且该方案会扩大 Redis 接口及所有 fake store,实现另一种事实索引。
### 2. 使用三个强类型 Envelope,共享上一轮评价类型
Agent-facing Schema 使用三个具体输入类型:`RagToolCall`、`QueryLogsToolCall`、`MysqlToolCall`。每个类型包含可选 `previous_observation` 和必填 `input`;`input` 继续使用现有业务 request,`previous_observation` 使用共享的 `PreviousObservation(tool_call_id, information_gain)`。
不使用泛型 `ToolCallEnvelope<T>` 直接生成 Schema,因为运行时类型擦除可能使嵌套 `input` 丢失具体字段;不使用 `JsonNode`,因为它无法给模型提供强 Schema。interceptor 严格解析对应 Envelope,先消费控制字段,再把 `input` 序列化为原业务 JSON 交给 adapter。
首个 Tool Call 以及上一结果已由 Harness 确定性评价时允许省略 `previous_observation`。存在待评价调用时,新 Tool Call 必须携带完全匹配的上一 Tool Call ID 和二值评价;缺失、错序或跨 Run 引用返回有界协议错误且不执行 Tool。
协议错误返回可修正 Model Observation,而不是只返回稳定错误码。Observation 可包含 `repair_required=true`、`violation_type`、`missing_field`、`expected_previous_tool_call_id` 和允许的 `information_gain` 值;这些字段均来自 Harness 控制状态和安全 Tool Call ID,不包含业务参数、scope、预算、Prompt、raw response 或内部异常。
### 3. 先应用上一轮评价,再决定本轮是否放行
interceptor 的固定顺序为:
1. 严格解析 Envelope 并校验 `previous_observation`。
2. 将上一轮 `GAINED` 清零计数,或将 `NO_GAIN` 加一。
3. 若达到阈值,拒绝当前业务 Tool,返回一次 `STOP_REQUIRED/INFORMATION_SATURATED`。
4. 规范化本次业务 request 的 scope;若与同 Tool 的成功历史 scope 重复,则不执行 Tool、记一次确定性 `NO_GAIN`,并返回有界重复提示或 STOP_REQUIRED。
5. 其余请求进入现有 adapter/ToolBoundary。
6. 成功后记录 canonical key;`NO_EVIDENCE` 由 Harness 立即记为 `NO_GAIN`,`EVIDENCE_FOUND` 标记为待模型评价。
Tool `ERROR` 不产生 information gain,也不把失败 scope 写入成功去重集合;它沿用技术故障和现有预算/重试边界。
替代方案是让 Tool 自行报告质量。拒绝,因为 Tool 只知道客观返回,无法判断对当前诊断假设的价值。
### 4. 只做确定性 scope 规范化
每个 Tool 提供一个无副作用 scope projector:RAG 使用规范化 query;日志使用 topic、query 和实际 lookback;MySQL 使用 logical datasource、规范化 SQL 文本和参数。仅规范化空白、大小写明确不敏感的枚举/标识、确定性默认值和结构化集合,不判断自然语言改写是否语义等价。
重复只比较 `tool_name + normalized_scope`,且只基于已成功执行的历史 scope。被拒绝的重复调用不进入 ToolBoundary、canonical store 或 Tool 调用预算,但会记录安全 Trace 并推进无增益计数。
### 5. Canonical 结果产生控制视图和模型白名单视图
ToolBoundary 继续保存完整 request、raw response 和 bounded `agent_result`。Tool 完成后:
- Harness Control View 读取 execution/evidence status、returned count、normalized scope、RAG relevance、truncated 和 canonical identity。
- Model Observation 只包含 Tool Call ID、实际 scope、有界 evidence/rows/events、`evidence_status`、可选 `relevance_level` 和 `truncated`。
`HarnessToolInterceptor` 不再直接返回完整 `agent_result`,而是通过按 Tool 类型的 `AgentObservationProjector` 白名单序列化。RAG projector 兼容读取 `relevanceLevel` 和 `relevance_level`,在 canonical RAG result 中统一为 `relevance_level`。`REFERENCE` 仍交给模型判断信息增益。
### 6. 饱和后只有一次正常收尾机会
当 Tool 结果使 tracker 直接饱和时,当前 Model Observation 同时携带 `stop_required=true` 和 `reason=INFORMATION_SATURATED`。当模型在下一次 Tool Call 中回传 `NO_GAIN` 后达到阈值时,该本轮 Tool 被拒绝并返回同样的 STOP_REQUIRED observation。
tracker 记录控制指令已交付。下一轮模型仍发起 Tool Call时,interceptor 抛出可识别的 `DiagnosisCollectionStoppedException`;`DiagnosisAgentUseCase` 不把它包装为内部故障,而是返回“无 Draft + stop reason”的执行结果。这样模型获得一次生成合法 Draft 的机会,同时无法靠重复 Tool Call继续空转。
替代方案是无限返回 STOP_REQUIRED。拒绝,因为模型无视指令时仍会消耗模型预算并重现原问题。
### 6.1 连续协议错误也进入受控停止
`INVALID_PROGRESS_PROTOCOL` 表示模型没有遵守 Tool Call Envelope 进展协议,不表示 Tool 内容无信息增益。因此它不累计 `NO_GAIN`,而是单独维护连续协议错误计数。首次或未达阈值的协议错误返回可修正 observation,给模型一次按字段修复的机会;达到阈值后 tracker 进入 `SATURATED`,`stop_reason=PROGRESS_PROTOCOL_VIOLATED`,当前请求收到一次 `STOP_REQUIRED`。
如果模型在 `STOP_REQUIRED` 后继续请求 Tool,沿用既有 `DiagnosisCollectionStoppedException` 受控停止路径。Release 仅在 ProgressSnapshot 有当前 Run 已验真 observed facts 时发布 `INSUFFICIENT_EVIDENCE`;没有安全进展时继续 fail closed,不伪造用户可见事实。
### 7. Agent 执行返回 Draft 与停止投影,而不是只返回 Draft
`DiagnosisAgentUseCase` 返回内部 `DiagnosisAgentExecution`:可选 Draft、`ProgressSnapshot` 和可选 `DiagnosisStopReason`。正常 Draft、强制饱和停止和预算异常都在 Diagnosis executor 边界形成 Release 输入。
`DiagnosisProgressProjector` 在 Tool loop 结束时读取 tracker 索引和 canonical store,一次性生成有界 snapshot;缺失、过期、ERROR 或不属于当前 Run 的记录不会成为 verified fact,并产生稳定 limitation/trace。snapshot 不进入模型上下文。
### 8. DiagnosisReleaseUseCase 统一业务发布决策
Release 接收 query、可选 Draft、ProgressSnapshot 和可选 stop reason:
- `conclusion != null`:执行现有完整 EvidenceGuard、一次 Repair、重验和 SemanticGuard。
- `conclusion == null`:不执行 Repair/SemanticGuard;若 Draft 有 Tool 引用,仅验证当前 Run canonical 引用真实性和负向语义,不要求正常结论结构。
- 无 Draft且 `INFORMATION_SATURATED` 或 `BUDGET_LIMIT_REACHED`:从 snapshot 确定性生成 Fallback,不调用额外模型。
- 零 Tool 且 Draft 的 `limitations.missing_info` 非空:发布 `MISSING_REQUIRED_CONTEXT`。
- 有有限排查但无可支持结论:发布 `INSUFFICIENT_EVIDENCE`。
- 真正 Tool/模型/基础设施故障且无法形成安全过程:继续 `FAILED`。
最终模型文本为空或不能严格解析为 `DiagnosisDraft` 时,非法内容本身始终被丢弃,不做 Markdown/自然语言 JSON 抽取,也不调用额外模型修复。Agent 输出异常携带一次有界 `ProgressSnapshot`;Executor 仅在 snapshot 含当前 Run 已验真的 observed facts 时交给 Release 生成 `INSUFFICIENT_EVIDENCE`,否则保持 `FAILED`。Trace 只记录固定失败类别、输出字节数和是否存在可发布进展,不记录模型原文、字段值或解析异常文本。
`ChatApplicationUseCase` 删除业务内容级 `recoverBudgetExhaustion`。Diagnosis executor 捕获可识别预算终止并调用 Release;Application 只允许该已处理 Diagnosis Fallback 跳过 `core.checkActive/completeSuccess`,持久化 `release_outcome=FALLBACK`。Run 内部仍保留 `BUDGET_EXHAUSTED`,数据库公开运行状态继续按既有规则记录为成功发布的 Fallback。
### 9. Prompt 只约束模型职责,不复制 Tool Schema
中文 Prompt 明确:无需强行得出根因;`conclusion=null` 是合法完成;缺少企业、时间、服务或错误信息时允许零 Tool 并填写 `limitations.missing_info`;只有新增可验证事实确认、排除或缩小假设才是 `GAINED`;正确但无用、通用或重复内容是 `NO_GAIN`;没有明确不同且可能产生新信息的 scope 时停止;收到 STOP_REQUIRED 后不得继续调用 Tool。
Prompt 不写 Tool 名、Schema、阈值、计数器、Projector、预算或 `next_action`。
### 10. Trace 记录决策,不泄露推理
Trace 增加有界事件或字段,记录 Tool Call ID、Tool name、scope 摘要、information gain 的生产者(Harness/Model)、连续计数变化、collection state 和 stop reason。不得记录 Prompt、模型 thought、raw Tool response、完整 SQL 参数、预算余量或模型评价理由。
协议拒绝 Trace 在稳定 `error_code` 之外记录安全字段:`violation_type`、`repair_prompt_delivered`、`consecutive_protocol_violations` 和达到阈值时的 `stop_reason`。Trace 不记录缺失字段对应的业务值、上一轮观察正文、Tool 参数或异常文本。
### 11. Run 总账与模型调用明细使用同一份 Provider Usage
`RunContext` 增加最小线程安全模型调用账本,只维护组件轮次、已审计调用数、Usage 不可用调用数和 Token 合计。每次实际模型调用在预算放行后取得组件轮次;`HarnessModelInterceptor` 负责 Diagnosis Agent,`GuardModelCall` 负责 Router、System Chat、Knowledge Answer、Evidence Repair 和 Semantic Guard。两条入口都从 Spring AI `Usage` 读取同一组 input/output Token,先登记调用明细,再交给现有 `RunBudget` 累加总账。
每个模型调用 Trace 只包含 `component`、`component_round`、`usage_available`,并在 Usage 可用时包含 `input_tokens`、`output_tokens` 和 `total_tokens`;Usage 不可用时不写 Token 字段。Diagnosis Agent 的对应 `AgentStep.token_count` 回填 total Token;其他组件不伪装成 AgentStep。Run 结束事件同时写入预算总账、审计明细合计、Usage 不可用数量和 `tokens_reconciled`,从而显式暴露缺口而不是把未知 Token 当作零消耗。
进入 `HarnessToolInterceptor` 的 Tool 请求若因进展协议、重复 scope、信息饱和或观察合同失败而未进入/未成功交付业务边界,记录 `TOOL_REQUEST_REJECTED`。事件只保留安全 Tool Call ID、Tool name 和稳定 `error_code`;业务参数、原始响应、内部异常和预算余量均不进入 Trace。
不新增模型审计表:`diagnosis_trace_event` 是调用明细账,`RunBudget`/`diagnosis_run.total_token_count` 是 Run 总账,`AgentStep.token_count` 是 Diagnosis Agent 轮次摘要。这样避免三套可独立漂移的 Token 真相源。
## Module Map
```text
ChatApplicationUseCase
-> DiagnosisChatExecutor
-> DiagnosisAgentUseCase
-> ReactAgent
-> HarnessModelInterceptor
-> HarnessToolInterceptor
-> DiagnosisProgressTracker (RunContext handle)
-> HarnessEvidenceTools -> ToolBoundary -> CanonicalInvocationStore
-> AgentObservationProjector
-> DiagnosisProgressProjector -> CanonicalInvocationStore
-> DiagnosisReleaseUseCase
-> no-conclusion reference validation / SafeFallbackFactory
-> EvidenceGuard -> EvidenceRepair -> SemanticGuard (conclusion only)
-> ChatRunStore -> named SSE content
```
## Interface Impact
- 级别:L3 协作接口。
- Agent-facing input 从裸业务 request 改为 `{previous_observation?, input}`。
- 业务 adapter、backend、公开 HTTP/SSE、数据库和前端消费字段保持兼容。
- 所有 Tool loop scripted tests 必须使用新 Envelope;KnowledgeQueryExecutor 若直接调用 registry bridge,继续走业务 request,不使用 Agent-facing Envelope。
- 不提供旧/新 Schema 双轨;回滚以整个 change 为单位。
## Risks / Trade-offs
- [框架不能按预期生成嵌套强类型 Schema] -> 使用三个具体 Envelope record,并增加真实 callback schema 测试。
- [STOP_REQUIRED 后异常被框架包装] -> 使用 cause-chain 分类测试,只有专用受控停止异常可转换为 Release 输入。
- [预算终止与 Application active check 冲突] -> DiagnosisExecutionResult 显式标记已处理终止 Fallback,Application 仅对此窄分支跳过 success transition。
- [canonical TTL 到期导致过程不完整] -> Run timeout 小于 canonical TTL;投影缺失 fail closed 为 limitation,不伪造事实。
- [scope 规范化误判不同查询为重复] -> 首版只规范确定性字段,测试每个 Tool 的相同/不同 scope。
- [模型伪造上一轮评价 ID] -> tracker 只接受当前 Run 最后一个待评价 ID,错序/重复消费均拒绝。
- [无结论 Draft 绕过安全检查] -> 只跳过结论 Repair/SemanticGuard;引用真实性、当前 Run 所有权和负向语义仍确定性验证。
- [模型完成排查后输出非法 Draft 导致过程丢失] -> 丢弃非法 Draft;仅当 ProgressSnapshot 含已验真 observed facts 时由 Release 确定性降级,无进展仍 fail closed。
- [脏工作区行为丢失] -> 迁移预算 Fallback 的测试意图,实施前后用 scoped diff 核对,不覆盖无关修改。
## Migration Plan
1. 先加入状态/Envelope/scope/projector 类型和 focused contract tests,不切换 Release。
2. 接入 interceptor、tracker、STOP_REQUIRED 和执行结果,固定真实框架 loop 行为。
3. 接入 ProgressSnapshot 与统一 Release,迁移 Application 预算 Fallback。
4. 更新中文 Prompt、Trace、配置和文档。
5. 运行 focused、Harness 回归和全量测试;再用 Maven 启动项目执行原始未知 Query 的 SSE、日志、数据库 exact-run E2E。
6. 回滚时整体回滚本 change;不单独恢复旧 Tool Schema 或 Application 预算分支。
## Open Questions
无。阈值、状态、Prompt、协议、去重边界、Release 所有权和兼容范围均已确认。