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

16 KiB
Raw Permalink Blame History

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

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 所有权和兼容范围均已确认。