diff --git a/mvp/engineering/README.md b/mvp/engineering/README.md
index 26913c8..ac52cc4 100644
--- a/mvp/engineering/README.md
+++ b/mvp/engineering/README.md
@@ -40,11 +40,28 @@
---
+## Harness
+
+| 文档 | 内容 |
+|---|---|
+| [harness/README.md](harness/README.md) | **从这里开始**:用一次诊断请求理解 Harness,不要求先掌握组件和状态 |
+| [harness/components/README.md](harness/components/README.md) | **组件渐进式导读**:沿一次请求分四步理解运行控制、Agent 收敛、Tool 事实边界和验证发布 |
+| [harness/CONTEXT.md](harness/CONTEXT.md) | Harness 统一术语、命名规则、状态维度与已知命名债务 |
+| [harness/Harness生命周期与状态.md](harness/Harness生命周期与状态.md) | Run、Tool、Progress、Guard、Release、SSE 和持久化生命周期及状态映射 |
+| [harness/Harness设计-非确定性Agent的确定性控制边界.md](harness/Harness设计-非确定性Agent的确定性控制边界.md) | Harness 根问题、设计不变量、方案取舍、关键决策、代价与真实问题反推 |
+| [harness/Harness组件全景-职责-设计原因与边界.md](harness/Harness组件全景-职责-设计原因与边界.md) | 当前 Harness 10 个职责域、全部生产类型、设计原因、作用与边界 |
+| [harness/Harness-Tool双视图-从原始结果到可验证证据.md](harness/Harness-Tool双视图-从原始结果到可验证证据.md) | canonical truth、Control View、Agent Observation 与 metadata audit 的数据边界 |
+| [harness/Harness证据安全链-从引用真实到结论可发布.md](harness/Harness证据安全链-从引用真实到结论可发布.md) | EvidenceGuard、EvidenceRepair、SemanticGuard 和唯一 Release Policy |
+| [harness/Harness信息增益停止-让无证据诊断正常收敛.md](harness/Harness信息增益停止-让无证据诊断正常收敛.md) | GAINED/NO_GAIN、重复检测、协议停止、ProgressSnapshot 与过程型 Fallback |
+
+---
+
## 诊断 / E2E
| 文档 | 内容 |
|---|---|
| [diagnosis/一次诊断全流程-E2E导读.md](diagnosis/一次诊断全流程-E2E导读.md) | SUCCESS 全流程:阶段、token、timeline、字段 |
+| [diagnosis/Session-Run-Trace隔离-从串线到可回放.md](diagnosis/Session-Run-Trace隔离-从串线到可回放.md) | 多轮串线问题、身份拆分、协议迁移与 exact-run E2E |
架构对照:
diff --git a/mvp/engineering/diagnosis/Session-Run-Trace隔离-从串线到可回放.md b/mvp/engineering/diagnosis/Session-Run-Trace隔离-从串线到可回放.md
new file mode 100644
index 0000000..bf0683a
--- /dev/null
+++ b/mvp/engineering/diagnosis/Session-Run-Trace隔离-从串线到可回放.md
@@ -0,0 +1,315 @@
+# Session、Run、Trace 隔离:一次身份建模错误的修复
+
+**更新日期**:2026-07-29
+**性质**:MVP 工程问题与架构决策复盘
+**结论状态**:Session / Run 身份模型沿用至当前架构
+
+---
+
+## 1. 问题到底是什么
+
+一句话定义:**系统用同一个 `sessionId`,同时标识“多轮对话”和“一次诊断执行”,导致一次 Trace 不再对应一次真实执行。**
+
+问题由一个两轮 E2E 暴露。用户在同一会话中连续发起两次请求:
+
+```text
+Round 1:诊断支付接口超时
+Round 2:基于上一轮结论,列出还缺哪些证据
+```
+
+两轮复用 `sessionId` 是正确的,因为第二轮需要上一轮上下文。但当时持久化和 Trace 查询也只使用 `sessionId`:
+
+- `diagnosis_session` 是覆盖写,第二轮把第一轮的 query、answer、status 覆盖掉;
+- `agent_step` 和 `tool_invocation` 是追加写,两轮明细累积在同一个 `sessionId` 下;
+- Trace API 再按 `sessionId` 聚合主表和明细。
+
+结果不是简单的“重复数据”,而是一个系统中从未真实发生过的混合执行:
+
+```mermaid
+flowchart LR
+ R1["Round 1
query A / steps A / tools A"] --> SID["同一个 sessionId"]
+ R2["Round 2
query B / steps B / tools B"] --> SID
+
+ SID --> Main["diagnosis_session
只剩 query B / answer B"]
+ SID --> Steps["agent_step
steps A + steps B"]
+ SID --> Tools["tool_invocation
tools A + tools B"]
+
+ Main --> Trace["混合 Trace"]
+ Steps --> Trace
+ Tools --> Trace
+
+ Trace --> Error["无法回答:
哪组证据支持了哪次回答?"]
+```
+
+这个错误会沿数据链继续放大:
+
+| 消费方 | 错误结果 |
+|---|---|
+| Trace 回放 | 一条 Trace 混合两轮步骤和 Tool |
+| Verifier / Gatekeeper | 当前轮可能读到上一轮证据 |
+| Evaluation | 评分对象和证据集合不再属于同一次执行 |
+| Feedback | 无法确定用户评价的是哪一轮回答 |
+| CaseLibrary | 可能把反馈沉淀到错误的 query/answer 上 |
+
+因此,核心问题不是某个 Repository 的更新方式,而是**会话边界被错误地当成了证据与审计边界**。
+
+---
+
+## 2. 设计必须守住什么
+
+修复前先定义四条不变量。后续方案不是凭表结构偏好选择,而是看能否同时满足这些约束。
+
+### 不变量 1:一次执行只有一个稳定身份
+
+从请求被接受到最终 `SUCCESS / FALLBACK / FAILED / CANCELLED`,必须有一个不可变 ID。模型步骤、Tool 调用、预算、最终答案和反馈都属于它。
+
+### 不变量 2:一条 Trace 只描述一次执行
+
+Trace 中的 Run、AgentStep、ToolInvocation 和生命周期事件必须使用同一个 exact ID 聚合,不能依赖时间邻近或“最新一条”猜测归属。
+
+### 不变量 3:上下文连续不等于执行合并
+
+同一个 Session 可以包含多个 Run。后一个 Run 可以读取前一轮安全发布结果,但不能继承前一轮的 Tool、Trace、评分或失败状态。
+
+### 不变量 4:兼容不能伪造精度
+
+旧客户端可以有迁移期 fallback,旧数据也可以保留;但系统必须让歧义可观察,不能把无法恢复的历史混合数据伪装成精确多轮记录。
+
+这四条不变量共同导出一个结论:系统需要两个身份,而不是给 `sessionId` 增加更多解释。
+
+---
+
+## 3. 为什么不是在旧表上继续修
+
+设计阶段考虑的不是“拆表还是不拆表”这一个问题,而是如何建立稳定的执行边界。
+
+| 候选方案 | 能解决什么 | 为什么没有选择 |
+|---|---|---|
+| 继续复用 `sessionId`,修正覆盖逻辑 | 避免主表被覆盖 | 明细仍无法区分轮次,根因未解决 |
+| 增加 `round_no` | 可以表示第几轮 | 并发请求、重试和多个执行入口下顺序不稳定;外部引用仍需复合身份 |
+| 按时间窗口拆分历史 Step/Tool | 无需改协议 | 时间不能证明归属,会制造看似精确的错误 Trace |
+| 每次请求插入一条新的 `diagnosis_session` | 形成一行一次执行 | 实际上已经引入 Run 概念,但名称和 Session 生命周期仍混淆,也缺少会话主实体 |
+| 拆分 `chat_session` 与 `diagnosis_run` | 显式表达一对多生命周期 | 需要 Schema、API 和客户端迁移,但能满足全部不变量 |
+
+最终选择最后一种。它的判断依据不是“范式更规范”,而是只有它能让对话连续性和执行可审计性同时成立。
+
+---
+
+## 4. 核心设计
+
+目标模型是一个清晰的一对多关系:
+
+```mermaid
+flowchart TB
+ Client["Client"] --> Session["chat_session
sessionId:多轮对话目录"]
+ Session --> Run1["diagnosis_run
runId 1:一次执行"]
+ Session --> Run2["diagnosis_run
runId 2:另一次执行"]
+
+ Run2 --> Step["agent_step
模型步骤 metadata"]
+ Run2 --> Tool["tool_invocation
Tool 审计 metadata"]
+ Run2 --> Event["diagnosis_trace_event
生命周期 Timeline"]
+ Run2 --> Reasoning["agent_reasoning_audit
受限原文"]
+
+ Run2 --> Trace["普通 Trace API"]
+ Step --> Trace
+ Tool --> Trace
+ Event --> Trace
+ Reasoning --> Audit["独立 Reasoning API"]
+```
+
+这里有三个不同的职责:
+
+- `chat_session` 回答“哪些 Run 属于同一段对话”;
+- `diagnosis_run` 回答“这次请求的输入、状态、结果和资源消耗是什么”;
+- Trace 回答“这个 Run 具体经历了什么”,它是按 Run 聚合的读模型。
+
+---
+
+## 5. 六项关键决策
+
+### 决策 1:引入正式的 `runId`
+
+**选择**:每次有效 Chat 执行创建新的 `runId`,并通过 SSE metadata 返回 `sessionId + runId`。
+
+**理由**:Run 必须能被 API、数据库、日志、Feedback 和评测独立引用。数据库自增 ID 不适合作为外部协议;轮次编号又不能稳定处理并发和重试。
+
+**代价**:客户端必须保存并向后续 Trace/Feedback 请求传递 `runId`。
+
+**边界**:`runId` 是不透明标识。早期实现使用 `run-` + UUID,后续格式发生过演进,客户端不得解析其前缀或长度。
+
+### 决策 2:拆分会话态与运行态
+
+**选择**:新增 `chat_session` 和 `diagnosis_run`,旧 `diagnosis_session` 停止承载新的运行写入。
+
+**理由**:两者生命周期不同。
+
+| 对象 | 保存内容 | 更新特点 |
+|---|---|---|
+| `chat_session` | 会话状态、轮次数、最近活动时间等目录信息 | 跨多轮持续更新 |
+| `diagnosis_run` | 单次 query、answer、终态、intent、release outcome、预算 | 一次执行内从 RUNNING 走向唯一终态 |
+
+完整多轮正文没有因为拆表就复制到 MySQL。身份拆分解决的是审计归属,不应顺带扩大长期数据保存范围。
+
+### 决策 3:Trace 是 Run 的聚合视图,不另造身份
+
+**选择**:第一阶段复用 `agent_step` 和 `tool_invocation`,增加 `run_id`;不为了修复隔离问题再创建一个独立 `traceId`。
+
+**理由**:隔离所缺的是执行外键,不是第三套身份。引入 `traceId` 只会产生 `sessionId / runId / traceId` 的映射问题。
+
+后续单 Agent + Harness 重构新增 `diagnosis_trace_event`,用于表达统一生命周期 Timeline。这是 Run 下的新明细,不是新的聚合根,也没有改变 `runId` 的边界。
+
+### 决策 4:exact Run 是目标协议,latest Run 只是迁移桥梁
+
+**选择**:目标查询使用:
+
+```http
+GET /api/diagnosis/{sessionId}/trace?runId={runId}
+```
+
+服务端同时验证 Run 存在且属于 path 中的 Session,防止跨 Session 串读。
+
+旧客户端暂时只传 `sessionId` 时,可以解析 latest Run;但这是显式兼容路径,不是新的业务语义。如果必须计算 latest,应按:
+
+```text
+created_at DESC, id DESC
+```
+
+而不是 `updated_at`。旧 Run 可能因 Feedback 或异步处理再次更新,最近修改不等于最近执行。
+
+### 决策 5:所有下游语义绑定 Run
+
+**选择**:Trace、Feedback、Evaluation、Tool evidence 和新 Case provenance 都以 `runId` 为执行边界。
+
+**理由**:这些对象评价或引用的是一次回答,不是整段会话。
+
+Feedback 在迁移期缺少 `runId` 时可以绑定 latest Run,但响应必须暴露 `fallbackToLatestRun=true`。兼容如果不可观察,就会从临时措施变成永久歧义。
+
+`case_library.diagnosis_id` 因复用旧列,在过渡期存在历史 `session_id` 和新 `run_id` 两种语义。这是明确接受的迁移成本,而不是应被隐藏的数据一致性。
+
+### 决策 6:历史混合数据不做推测性拆分
+
+**选择**:每条旧 `diagnosis_session` 最多映射为一个 compatibility Run,不根据时间或 Agent 名称猜测真实轮次。
+
+**理由**:旧数据没有记录边界,任何自动拆分都只能产生无法证明的归属。审计系统宁可明确“不知道”,也不能制造虚假的精确回放。
+
+---
+
+## 6. 协议和影响范围
+
+这是一次有意的行为与协议变化,不是纯内部重构。
+
+| 范围 | 变化 | 受影响方 |
+|---|---|---|
+| Chat / SSE | metadata 增加 `runId` | 前端、脚本、调用方 |
+| Trace API | 支持 exact `runId` 查询 | Trace UI、排障工具、评测 |
+| Run API | 提供 Session 下的 Run 列表 | 多轮历史浏览 |
+| Feedback | request/response 增加 Run 绑定和 fallback 标志 | 前端、CaseLibrary |
+| 数据库 | 新增两张主表,明细增加 `run_id` | 持久化、迁移、查询脚本 |
+| 其它入口 | 当时的 AIOps 同步采用 Run 边界 | SSE 消费方、Trace |
+
+之所以把当时的 AIOps 一并迁移,是因为它同样会产生可回放执行;只修 Chat 会留下第二条具有同类缺陷的数据链。后续 ISS-014 删除了旧 AIOps 双入口,但这不改变当时“所有执行入口必须共享 Run 边界”的设计判断。
+
+---
+
+## 7. 风险如何处理
+
+### 风险 1:兼容路径继续产生歧义
+
+控制方式是让 fallback 可观察,并把 exact `runId` 定义为目标协议。兼容是迁移机制,不能反向成为领域模型。
+
+### 风险 2:异步链路丢失或串用身份
+
+`sessionId` 与 `runId` 必须作为同一执行上下文传播。当前架构将二者放入显式 `RunContext`,ToolBoundary、审计 Hook 和持久化都校验当前 Run,避免只依赖线程隐式状态。
+
+### 风险 3:历史和新 provenance 共用旧列
+
+保留旧列降低了迁移破坏性,但查询和文档必须承认双语义,不能把旧 `session_id` 当作非法 `run_id` 清理。
+
+### 风险 4:旧混合 Trace 永远无法恢复
+
+这是明确接受的事实。系统保留 compatibility 访问和回滚能力,但不承诺不存在的历史精度。
+
+---
+
+## 8. 如何证明设计成立
+
+验收问题不是“接口里有没有 `runId`”,而是下面五个条件是否同时成立:
+
+```text
+同一 Session 连续执行两轮
+ + 两轮获得不同 runId
+ + 每个 exact Trace 只返回本 Run 明细
+ + 数据库不存在跨 Run 混合行
+ + 第二轮仍能使用会话上下文
+```
+
+真实 E2E 使用:
+
+```text
+sessionId = e2e-phase6-chat-codex-20260710-2120
+run1 = run-e2a97696-4398-4abc-90e4-28f45c838f92
+run2 = run-76ce6a6e-92ab-40c9-800a-eca0c1bb5172
+```
+
+结果:
+
+- 两轮 Chat 成功并复用同一个 `sessionId`;
+- 两轮返回不同 `runId`;
+- run1 exact Trace 只返回 run1,run2 exact Trace 只返回 run2;
+- `diagnosis_run` 中存在两条独立运行记录;
+- AgentStep:run1 为 10 行,run2 为 9 行;
+- ToolInvocation:run1 为 14 行,run2 为 8 行;
+- mixed row check 为 0;
+- `chat_session.message_pair_count = 2`,上下文连续性没有因隔离而丢失;
+- focused tests、baseline diff、日志和数据库核验通过,未观察到 baseline drift。
+
+这组证据同时验证了“该分开的确实分开”和“该连续的仍然连续”。
+
+---
+
+## 9. 后续演进验证了什么
+
+系统后来从多角色 Agent 编排重构为单 Diagnosis ReAct Agent + Harness。执行结构发生了大变化,但 Session / Run 模型没有被替换,反而成为新架构的基础:
+
+- `ChatApplicationUseCase` 创建和结束 Run;
+- `RunContext` 显式携带 `sessionId + runId`;
+- ToolBoundary 校验 Tool 请求属于当前 Run;
+- Redis canonical invocation 使用 `runId + toolCallId` 定位;
+- EvidenceGuard 只接受当前 Run 的 READY invocation;
+- AgentStep、ToolInvocation、TraceEvent 和 ReasoningAudit 都绑定 Run。
+
+这说明当时解决的不是某一版代码的局部 bug,而是找到了稳定的领域边界。Agent 编排可以替换,Session 与 Run 的生命周期差异不会消失。
+
+---
+
+## 10. 可复用的设计判断
+
+这次问题可以归纳为四条通用经验:
+
+1. **生命周期不同的对象,不应共享同一个聚合身份。**
+2. **上下文复用不代表证据、状态和审计记录也可以复用。**
+3. **兼容 fallback 必须可观察、可退出,不能静默猜测。**
+4. **无法恢复的历史边界应明确降级,不能伪造精确性。**
+
+判断类似系统是否存在同类问题,可以直接问:
+
+- 一次请求是否有独立于 Session 的执行 ID?
+- 所有 Step、Tool、Event、Feedback 是否都能精确归属一次执行?
+- “查询最新”是否被误当成“查询指定执行”?
+- 主表覆盖写、明细追加写是否使用了同一个过宽的关联键?
+- 历史迁移是在保留不确定性,还是通过猜测制造精确性?
+
+如果这些问题没有明确答案,那么 Trace 即使看起来完整,也未必能作为可信审计证据。
+
+---
+
+## 11. 资料索引
+
+- [ISS-010:同 session 多轮诊断 Trace 隔离](../../issues/archived/ISS-010-session-run-trace-isolation.md)
+- [Session、Run 与 Trace 生命周期](../../architecture/session-trace-lifecycle.md)
+- [当前 MVP 架构](../../architecture/current-mvp-architecture.md)
+- [数据表索引](../../tables/README.md)
+- [devflow brief](../../../devflow/projects/2026-07-10-session-run-trace-isolation/brief.md)
+- [devflow decisions](../../../devflow/projects/2026-07-10-session-run-trace-isolation/decisions.md)
+- [devflow acceptance](../../../devflow/projects/2026-07-10-session-run-trace-isolation/acceptance.md)
+- [devflow evidence](../../../devflow/projects/2026-07-10-session-run-trace-isolation/evidence.md)
diff --git a/mvp/engineering/harness/CONTEXT.md b/mvp/engineering/harness/CONTEXT.md
new file mode 100644
index 0000000..769b68a
--- /dev/null
+++ b/mvp/engineering/harness/CONTEXT.md
@@ -0,0 +1,378 @@
+# Harness Context:统一术语与命名边界
+
+**更新日期**:2026-07-29
+**状态**:当前实现口径
+**适用范围**:`com.superbiz.agent.harness`、Chat SSE、Diagnosis Run 持久化与相关工程文档
+
+> 本文是术语词典,不建议第一次接触 Harness 时顺序阅读。入门请从 [README.md](README.md) 开始,遇到名词歧义时再回到本文查询。
+
+## 1. 为什么需要这份 Context
+
+当前系统同时存在 Run 状态、发布结果、Tool 状态、证据状态、收集状态、停止原因和 Fallback 原因。它们都使用了 `SUCCESS`、`ERROR`、`FAILED`、`READY` 等相近词汇,但回答的是不同问题。
+
+如果把这些词排成一条“大状态机”,会产生错误理解,例如:
+
+- `NO_EVIDENCE` 被理解为 Tool 调用失败;
+- `FALLBACK` 被理解为 Run 执行失败;
+- `SATURATED` 被理解为预算耗尽;
+- `READY` 被理解为证据足以支持根因;
+- 数据库 `status=SUCCESS` 被理解为已经找到根因。
+
+本文件是 Harness 工程文档的术语入口。阅读其他文章前,先以这里的定义区分身份、数据、状态和组件责任。代码与现行架构文档仍是最终事实来源;本文件不创建新的运行协议。
+
+## 2. 一句话定义 Harness
+
+**Harness** 是包围非确定性模型执行的确定性控制边界:Agent 负责业务推理和 Draft,Harness 负责 Run 身份、生命周期、预算、取消、Tool 门禁、证据验真、停止控制、发布和审计。
+
+Harness 不是:
+
+- 业务工作流引擎;
+- Planner / Executor / Verifier / Composer 编排图;
+- 框架 ReAct loop 的第二份实现;
+- 判断业务根因的规则引擎;
+- 用于存放所有 Agent 相关代码的泛化名称。
+
+## 3. 三层范围:不要把 Harness、Core 和 Application 当成同义词
+
+| 名称 | 定义 | 包含 | 不包含 |
+|---|---|---|---|
+| Chat Application | 一次 Chat 请求的应用用例 | Session/Run 创建、路由、分支执行、持久化、公开结果 | HTTP/SSE 连接本身、业务推理细节 |
+| Diagnosis Harness | Diagnosis Agent 外部的确定性控制系统 | Core、Interceptor、ToolBoundary、Progress、Guard、Release、Audit | 根因推理和 ReAct 规划 |
+| Harness Core | 最小运行控制内核 | RunContext、deadline、budget、cancel、lifecycle、retry policy | 路由、Tool backend、Guard、持久化、SSE |
+
+代码包 `com.superbiz.agent.harness` 同时包含 Chat Application 和 Diagnosis Harness 的实现,这是代码组织范围,不代表所有类都属于 Harness Core。
+
+`DiagnosisHarnessCore` 目前也被 System Chat、Knowledge Query 和 Router 复用预算与生命周期能力。类名前缀保留了演进历史,概念上应理解为当前 Chat Run 的 Harness Core。
+
+## 4. 身份术语
+
+```mermaid
+flowchart TB
+ S["Chat Session
sessionId,多轮容器"] -->|"1:N"| R1["Run
runId,一次请求"]
+ S -->|"1:N"| R2["Run
下一次请求"]
+
+ R1 -->|"1:N"| AS["Agent Step
一次模型步骤"]
+ R1 -->|"1:N"| TC["Tool Call / Invocation
framework tool_call_id"]
+ R1 -->|"1:N"| TE["Trace Event
sequence_no"]
+
+ TC --> CI["Canonical Invocation
Run 内短期 Tool 真相"]
+ AS --> TR["Diagnosis Trace
按 exact Run 聚合"]
+ TC --> TR
+ TE --> TR
+
+ R1 -.->|"SUCCESS diagnosis only"| PT["PublishedResult
可投影为下一轮 PreviousTurn"]
+```
+
+图中的所有明细都必须绑定 exact runId。Session 只负责组织多轮,不能替代 Run 归属;Trace 是聚合视图,不能反过来成为执行身份。
+
+### 4.1 Chat Session
+
+一次多轮对话容器,由 `sessionId` 标识。同一个 Session 可以包含多次 Run。
+
+Session 用于:
+
+- 组织多轮请求;
+- 查找安全的 PreviousTurn;
+- 查询历史 Run。
+
+Session 不代表一次执行,不拥有 Tool Call 或模型步骤的终态。
+
+### 4.2 Run
+
+一次独立的 Chat Application 执行,由 `runId` 标识。每次请求创建新 Run,无论 intent 是 System Chat、Knowledge Query 还是 Diagnosis。
+
+代码和表名中仍大量使用 `DiagnosisRun` / `diagnosis_run`,但当前 Chat Application 会为三种 intent 都创建该记录。因此文档中优先使用 **Run**;只有引用 Java 实体或数据库表时才写 `DiagnosisRun`。
+
+### 4.3 Agent Step
+
+Diagnosis ReAct Agent 的一次模型步骤。多个 Agent Step 属于一个 Run,用于记录每轮模型调用的有界元数据和 Token。
+
+Agent Step 不是 Run,也不是 retry attempt。ReAct 的下一轮模型调用是业务循环;retry 是同一技术操作的再次 attempt。
+
+### 4.4 Tool Call / Tool Invocation
+
+- **Tool Call**:模型通过框架产生的调用请求,由 framework `tool_call_id` 标识。
+- **Tool Invocation**:该请求进入 ToolBoundary 后的一次实际执行记录。
+- **Tool Request Rejection**:在 progress、重复或停止门禁处被拒绝,backend 没有执行,不算 Tool Invocation。
+
+Harness 不生成第二套 Tool Call ID。
+
+### 4.5 Trace
+
+按 exact `sessionId + runId` 聚合的可回放观察视图,包含 Run、Agent Step、Tool metadata 和统一 Timeline。
+
+Trace 是观察结果,不是新的执行上下文或状态所有者。`TraceEventStatus` 只描述单个事件,不能替代 RunState 或 ReleaseOutcome。
+
+## 5. 核心执行术语
+
+### 5.1 RunContext
+
+一次 Run 的显式执行上下文。结构不可变地携带:
+
+```text
+sessionId
+runId
+deadline
+RunCancellation
+RunBudget
+ModelCallLedger
+HarnessRetryPolicies
+RunLifecycle
+DiagnosisProgressTracker
+```
+
+“结构不可变”表示 record 字段引用不变化;预算、取消、生命周期和进展通过各自线程安全句柄在 Run 内变化。
+
+### 5.2 Run Lifecycle
+
+Run 的内存执行终态,由 `RunLifecycle` 所有,状态类型是 `RunState`。它采用 first-terminal-wins,后到达的成功、失败或取消不能覆盖第一个终态。
+
+### 5.3 Run Cancellation
+
+请求停止 Run 的协作机制,由 `RunCancellationReason` 记录第一个原因。取消会阻止后续边界和迟到发布,但不承诺一定能立即物理中断已发送给 Provider 的同步请求。
+
+### 5.4 Run Budget
+
+单 Run 的资源账本和门禁,包括模型调用、Tool 调用、单 Tool 次数、输入/输出/总 Token 和 Run bytes。
+
+Budget 只回答“还能不能消耗资源”,不回答“继续诊断是否有价值”。后者属于 Information Gain 和 Collection State。
+
+### 5.5 Retry Attempt
+
+同一个技术操作因允许的技术失败而再次执行。当前 Router 和 SemanticGuard 最多 2 次 attempt,Diagnosis Agent、Tool 和 EvidenceRepair 只有 1 次。
+
+以下不是 retry:
+
+- ReAct Agent 的下一轮思考;
+- 改用另一个 Tool;
+- `NO_EVIDENCE` 后继续查询;
+- 用户发起下一次 Run。
+
+## 6. Agent 与输出术语
+
+### 6.1 Diagnosis Agent
+
+唯一拥有业务 ReAct Tool loop 的 Agent,负责提出假设、选择 Tool、评价非空结果的信息增益并生成 `DiagnosisDraft`。
+
+它不负责 Run 生命周期、Tool 授权、证据物理验真、SemanticGuard 或最终发布。
+
+### 6.2 DiagnosisDraft
+
+Diagnosis Agent 的结构化草稿,是发布链输入,不是已经发布的报告。Draft 可以有结论,也可以 `conclusion=null`。
+
+Draft 中的 Tool 引用和结论必须经过 Release Pipeline 后才能成为公开内容。
+
+### 6.3 Agent Result
+
+当前 Tool 代码中的 `agent_result` 指 Tool-specific Projector 生成、存入 canonical invocation 的标准化有界结果。
+
+它不是:
+
+- Diagnosis Agent 的最终 Draft;
+- Chat Application 的最终结果;
+- 直接进入模型上下文的完整内容。
+
+更准确的理解是 **Canonical Projected Tool Result**。当前字段名因协议兼容保留。
+
+### 6.4 Model Observation
+
+从 canonical `agent_result` 再次白名单投影后,真正作为 Tool Response 进入 Diagnosis Agent 上下文的内容。
+
+预算、阈值、重复指纹、raw response 和完整 Harness 控制状态不进入 Model Observation。
+
+### 6.5 PreviousTurn 与 PublishedResult
+
+- `PublishedResult`:只有 `DIAGNOSIS + ReleaseOutcome.SUCCESS` 才能持久化的安全诊断结果。
+- `PreviousTurn`:从 PublishedResult 生成的有界下一轮上下文。
+
+Fallback、失败、取消、raw evidence 和完整历史不会进入 PreviousTurn。
+
+`PublishedResult` 不等于当前请求直接返回的 `ChatApplicationResult`。
+
+## 7. Tool 与证据术语
+
+### 7.1 ToolBoundary
+
+所有业务 Tool 的统一执行边界,负责 Run/ID/授权/只读校验、预算、bytes、canonical 状态迁移和 metadata audit。
+
+ToolBoundary 不判断信息增益或业务根因。
+
+### 7.2 Canonical Invocation
+
+Redis TTL 内的完整 Tool 调用真相,包含 request、raw response、标准化 `agent_result`、调用状态和证据状态。
+
+它用于当前 Run 的 EvidenceGuard 和 ProgressSnapshot,不是长期审计记录。
+
+### 7.3 Durable Audit
+
+长期保存的有界元数据:Run/Tool identity、状态、耗时、bytes、模型步骤和 Token。它不保存 Prompt、完整 Tool 参数、raw response 或 canonical record。
+
+### 7.4 Evidence
+
+Tool 在特定 scope 下返回、经过 Projector 标准化并能由当前 Run canonical record 验真的事实或负向观察。
+
+`EVIDENCE_FOUND` 只代表存在候选内容,不代表它支持根因。
+
+### 7.5 Negative Observation
+
+`READY + NO_EVIDENCE` 形成的限定范围事实,例如“在时间窗 T、服务 S、查询 Q 下没有匹配日志”。
+
+它不能被解释为“故障不存在”或“系统健康”。Draft 中使用 `AnalysisKind.NEGATIVE_OBSERVATION` 表达这种分析。
+
+### 7.6 VerifiedEvidenceSnapshot
+
+EvidenceGuard 从 canonical invocation 中投影出的已验真、最小证据集合,供 SemanticGuard 使用。它不包含 raw response。
+
+### 7.7 ProgressSnapshot
+
+Tool loop 结束后,根据已完成 Tool Call identity 回读 canonical records 生成的有界过程视图,用于受控停止或无结论 Fallback。
+
+VerifiedEvidenceSnapshot 面向“有结论报告的语义审查”;ProgressSnapshot 面向“没有可发布结论时说明已经检查了什么”,两者用途不同。
+
+## 8. Guard 与发布术语
+
+### 8.1 EvidenceGuard
+
+确定性引用验真器。检查 Draft 结构、analysis 引用闭包、当前 Run 所有权、READY 状态、EvidenceStatus 和 typed projection 自洽性。
+
+不调用模型,不判断结论是否被证据支持。
+
+### 8.2 EvidenceRepair
+
+一次性的模型修复步骤,只修结构和引用。修复前后 `SemanticDraftView` 必须保持用户可见语义一致,之后重新执行 EvidenceGuard。
+
+它不是新的报告作者,也不是 retry Agent。
+
+### 8.3 SemanticGuard
+
+隔离的单轮语义审查器,只判断 verified evidence 是否支持 Draft,输出 `SUPPORTED / UNSUPPORTED`。
+
+它使用模型,但无 Tool、无记忆、无 ReAct loop、无报告改写权,因此文档中不要称它为第二个业务 Agent。
+
+### 8.4 Release Pipeline
+
+从 DiagnosisDraft 或受控停止输入,到 `DiagnosisReleaseResult` 的安全决策链:EvidenceGuard、可选 Repair/Recheck、SemanticGuard 和 SafeFallback。
+
+### 8.5 ReleaseOutcome
+
+应用层最终处理结果:
+
+- `SUCCESS`:发布安全正常内容;
+- `FALLBACK`:请求已被安全处理,但没有发布正常诊断结论;
+- `FAILED`:无法形成安全业务结果;
+- `CANCELLED`:Run 被取消。
+
+ReleaseOutcome 不等于 RunState。尤其 `FALLBACK` 不是 RunState。
+
+### 8.6 SafeFallback 与 FallbackType
+
+SafeFallback 是确定性公开内容;FallbackType 解释为什么没有发布正常诊断结论,例如:
+
+- `EVIDENCE_VALIDATION_FAILED`;
+- `SEMANTIC_UNSUPPORTED`;
+- `SEMANTIC_UNAVAILABLE`;
+- `INSUFFICIENT_EVIDENCE`;
+- `MISSING_REQUIRED_CONTEXT`。
+
+FallbackType 是 `ReleaseOutcome.FALLBACK` 的原因,不是新的生命周期状态。
+
+## 9. 状态维度速查
+
+| 类型 | 所有者 | 回答的问题 | 不能回答的问题 |
+|---|---|---|---|
+| `RunState` | RunLifecycle | Run 的内存执行是否终止、如何终止 | 发布了正常内容还是 Fallback |
+| `RunCancellationReason` | RunCancellation | 谁首先请求取消、为什么 | 最终公开结果是什么 |
+| `ChatApplicationStatus` | Application Observer | 当前向用户展示哪个处理阶段 | Run 是否已经终止 |
+| `InvocationStatus` | Canonical Invocation | Tool 调用记录是否完成 | 是否找到候选证据 |
+| `EvidenceStatus` | Tool Projector | 当前 scope 是否有候选证据 | 是否支持根因 |
+| `InformationGain` | Harness/Diagnosis Agent | 结果是否推进当前诊断 | Tool 是否技术成功 |
+| `DiagnosisCollectionState` | ProgressTracker | 是否允许继续调用证据 Tool | Run 是否终止 |
+| `DiagnosisStopReason` | ProgressTracker | 为什么停止继续收集 | 对外发布什么内容 |
+| `SemanticVerdict` | SemanticGuard | 已验真证据是否支持 Draft | Run 是否成功执行 |
+| `ReleaseOutcome` | Application/Release | 对外处理结果属于成功、降级、失败还是取消 | Tool 或收集过程的内部状态 |
+| `FallbackType` | SafeFallbackFactory | FALLBACK 的业务/安全原因 | 整个 Run 的执行终态 |
+| `TraceEventStatus` | 单个 Trace Event | 某条事件的局部结果 | 全局生命周期 |
+| SSE Session `State` | ChatSseSession | 连接能否继续发送事件 | Harness Run 的业务结果 |
+
+```mermaid
+flowchart LR
+ subgraph Execution["执行控制维度"]
+ RS["RunState
RUNNING -> terminal"]
+ CS["CollectionState
COLLECTING / SATURATED"]
+ SR["StopReason
停止收集原因"]
+ end
+
+ subgraph Tool["单次 Tool 维度"]
+ IS["InvocationStatus
PROJECTING / READY / ERROR"]
+ ES["EvidenceStatus
FOUND / NO_EVIDENCE / ERROR"]
+ IG["InformationGain
GAINED / NO_GAIN"]
+ end
+
+ subgraph Validation["验证维度"]
+ EV["EvidenceGuardResult
valid / violations"]
+ SV["SemanticVerdict
SUPPORTED / UNSUPPORTED"]
+ end
+
+ subgraph Publication["发布与观察维度"]
+ RO["ReleaseOutcome
SUCCESS / FALLBACK / FAILED / CANCELLED"]
+ FT["FallbackType
FALLBACK 原因"]
+ SSE["SSE Session State
连接发送状态"]
+ DB["diagnosis_run.status
持久化通用状态"]
+ end
+
+ IS --> ES
+ ES --> IG --> CS
+ CS --> SR
+ ES --> EV --> SV
+ SR --> RO
+ SV --> RO
+ RS --> RO
+ RO --> FT
+ RO --> SSE
+ RO --> DB
+```
+
+箭头表示信息参与后续决策,不表示枚举之间一一转换。例如 `READY + EVIDENCE_FOUND` 仍可能得到 `NO_GAIN`,`RunState.SUCCESS` 也可能对应 `ReleaseOutcome.FALLBACK`。
+
+完整状态转换和跨层映射见[Harness生命周期与状态.md](Harness生命周期与状态.md)。
+
+## 10. 命名规则
+
+后续代码和文档遵守以下用词:
+
+1. 说“Run 成功/失败/取消/超时/预算耗尽”时,明确写 `RunState`。
+2. 说“发布正常内容/Fallback/失败/取消”时,明确写 `ReleaseOutcome`。
+3. 不单独写“Tool 成功”,改写为 `InvocationStatus=READY`,并同时说明 EvidenceStatus。
+4. 不写“找到有效证据”,除非已经说明是候选内容、已验真事实还是足以支持结论。
+5. `NO_EVIDENCE` 必须附带 scope,不得写成全局否定。
+6. `SATURATED` 只用于 Collection State;预算耗尽使用 `BUDGET_LIMIT_REACHED` 或 `RunState.BUDGET_EXHAUSTED`。
+7. `Fallback` 只指安全发布降级,不用来泛指异常兜底代码。
+8. `Agent` 默认指 Diagnosis Agent;SemanticGuard 和 EvidenceRepair 分别称“语义审查器”和“引用修复步骤”。
+9. `agent_result` 引用字段名时保留原名,概念说明使用“canonical projected Tool result”。
+10. `status` 单独出现没有意义,必须注明所属类型或存储字段。
+
+## 11. 已知命名债务
+
+### 11.1 `DiagnosisRun` 名称大于实际诊断范围
+
+当前 Chat Application 对三种 intent 都写入 `diagnosis_run`。文档统一称 Run;是否重命名实体和表属于单独的协议/迁移决策,本次不修改代码。
+
+### 11.2 `SseOutcome` 当前未被运行时使用
+
+`SseOutcome` 枚举存在,但当前 `ChatSseEvent.Done` 直接携带 `ReleaseOutcome`,并禁止公开 `CANCELLED`。因此当前 SSE 真理源是 `ReleaseOutcome`,不要再基于 `SseOutcome` 推导协议。
+
+### 11.3 `FallbackType.BUDGET_EXHAUSTED` 不是当前诊断发布主路径
+
+枚举值仍存在,但当前 Diagnosis Release 对预算受控停止的行为是:有已验真进展时发布 `INSUFFICIENT_EVIDENCE`,无安全进展时保持失败。文档不能仅因枚举存在就声称系统会发布 `BUDGET_EXHAUSTED` Fallback。
+
+### 11.4 数据库 `status=SUCCESS` 不等于找到根因
+
+`JpaChatRunStore` 将 `ReleaseOutcome.SUCCESS` 和 `FALLBACK` 都映射为数据库 `status=SUCCESS`,表示请求被正常处理。是否发布根因必须结合 `release_outcome`、`content_type` 和 FallbackType 判断。
+
+### 11.5 RunState 没有直接作为独立字段持久化
+
+当前持久化主记录保存通用 `status` 和 `release_outcome`,Trace 保存阶段事件;内存 `RunTermination.state/reason` 不是独立数据库字段。排查时不能只靠数据库 `status` 反推 TIMED_OUT 或 BUDGET_EXHAUSTED 的精确内部终态。
+
+## 12. 如何使用本文
+
+本文不是必读的第一章,而是遇到名词歧义时使用的词典。第一次接触 Harness,请先读 [README.md](README.md) 建立最小心智模型;需要理解某个状态或概念时,再回到本文对应章节查询。
diff --git a/mvp/engineering/harness/Harness-Tool双视图-从原始结果到可验证证据.md b/mvp/engineering/harness/Harness-Tool双视图-从原始结果到可验证证据.md
new file mode 100644
index 0000000..c29f6fd
--- /dev/null
+++ b/mvp/engineering/harness/Harness-Tool双视图-从原始结果到可验证证据.md
@@ -0,0 +1,280 @@
+# Harness Tool 双视图:从原始结果到可验证证据
+
+**更新日期**:2026-07-29
+**主题**:ToolBoundary、Canonical Invocation、Harness Control View 与 Agent Observation
+**术语与状态**:[CONTEXT.md](CONTEXT.md) · [Harness生命周期与状态.md](Harness生命周期与状态.md)
+**上位设计**:[Harness设计-非确定性Agent的确定性控制边界.md](Harness设计-非确定性Agent的确定性控制边界.md)
+
+## 1. 要解决的不是 Tool 调用,而是 Tool 结果的所有权
+
+Agent 调用 Tool 后,最直接的实现是把 backend 返回的 JSON 原样放进模型上下文。这在 Demo 中可以工作,但进入可验证的诊断系统后会出现一个根本矛盾:同一份结果需要同时服务推理、控制、验真和审计,而这些消费者需要的数据范围完全不同。
+
+以日志查询为例:
+
+- Agent 需要少量匹配事件、模式、实际时间范围和是否截断;
+- Harness 需要返回数量、规范化 scope、重复身份和客观证据状态;
+- EvidenceGuard 需要证明这份结果确实来自当前 Run 的某次 READY 调用;
+- 审计需要 Tool 名、调用 ID、状态、耗时和字节数;
+- 安全边界又要求密码、Token、主机、IP、SQL 字面量和无限日志正文不能进入模型或长期审计。
+
+如果只保留一份 JSON,只能在两个错误方向中选择:要么信息过多导致泄露和上下文膨胀,要么信息过少导致后续无法验真。
+
+因此这项设计的核心不是“增加一个 Redis Store”,而是重新定义 Tool 数据所有权:
+
+> backend raw 属于 Harness;模型只能获得为推理目的生成的有界观察;长期审计只保留允许运营保存的元数据。
+
+## 2. 三个消费者,三种数据责任
+
+虽然实现中常称为“Tool 双视图”,完整的数据分层实际上包含三种用途:
+
+| 数据形态 | 消费者 | 解决的问题 | 生命周期 |
+|---|---|---|---|
+| Canonical Invocation | ToolBoundary、EvidenceGuard、ProgressProjector | 这次 Tool 真实执行了什么、属于哪个 Run、能否引用 | Redis 短 TTL |
+| Harness Control View / Agent Observation | Harness / Diagnosis Agent | 是否重复、是否为空、模型下一步需要看到什么 | 当前 Run / 模型上下文 |
+| Durable Audit | Trace、运营排障 | 何时调用、状态、耗时、大小、关联 Step | MySQL 长期 metadata |
+
+所谓“双视图”,特指 canonical 标准结果被进一步分成:
+
+1. Harness 使用的 Control View;
+2. 模型使用的 Agent Observation。
+
+Durable Audit 是第三个持久化面,但它不是 Tool 内容视图,不参与推理或证据验真。
+
+```mermaid
+flowchart LR
+ A["Typed Tool Request"] --> B["ToolBoundary"]
+ B --> C["Backend Raw Response"]
+ C --> D["Tool-specific Projector"]
+ D --> E["Canonical agent_result"]
+
+ B --> F["Redis Canonical Invocation
request + raw + agent_result"]
+ E --> F
+
+ E --> G["Harness Control View
count / status / scope"]
+ E --> H["Agent Observation
白名单、有界、脱敏"]
+
+ B --> I["Durable Audit
identity / status / latency / bytes"]
+ G --> J["重复检测、NO_GAIN、停止"]
+ H --> K["Diagnosis Agent Context"]
+ F --> L["EvidenceGuard / ProgressSnapshot"]
+```
+
+## 3. 为什么不能让 Agent Observation 充当真相源
+
+Agent Observation 是为了控制上下文而生成的投影,它可能:
+
+- 只保留前 N 条结果;
+- 截断单条文本;
+- 对敏感列和日志字段做脱敏;
+- 把大量事件聚合成模式;
+- 只暴露粗粒度相关度,不暴露检索轨迹和原始分数。
+
+这意味着它适合帮助模型推理,却不适合作为“后台真实返回”的完整证明。如果 EvidenceGuard 反过来验证 Agent 自己收到的 observation,就相当于用被审查对象提供的摘要证明其自身真实性。
+
+Canonical Invocation 解决的正是这个独立性问题。它以 exact `runId + tool_call_id` 建立记录,并保存:
+
+```text
+tool_call_id
+run_id
+tool_name
+request
+raw_response
+agent_result
+invocation status
+evidence status
+error code
+started_at / completed_at
+```
+
+EvidenceGuard 不信任 Draft 中的引用,也不从模型历史反推 Tool 结果,而是重新按当前 Run 构造 key,读取 canonical record 并验证状态和投影内容。
+
+## 4. ToolBoundary 为什么必须是统一入口
+
+RAG、日志和 MySQL 的业务执行方式不同,但以下控制规则完全相同:
+
+- Run 必须仍然 active;
+- envelope 的 runId 必须等于当前 Run;
+- `tool_call_id` 必须合法且不能重复;
+- Tool 必须已授权并声明只读;
+- 调用前必须预占 Tool 预算和 request bytes;
+- raw response 和 agent result 必须分别检查容量;
+- canonical 状态只能按合法路径迁移;
+- 对 Agent 只返回稳定错误码;
+- durable audit 失败不能改变已经得到的 Tool 结果。
+
+如果把这些逻辑复制到三个 Adapter,任何新增 Tool 都可能漏掉其中一项。因此统一由 `ToolBoundary` 编排一次调用,具体 Adapter 只负责 typed request、backend 和 projector 的连接。
+
+```mermaid
+sequenceDiagram
+ participant I as Tool Interceptor
+ participant B as ToolBoundary
+ participant C as Harness Core
+ participant S as Canonical Store
+ participant T as Backend
+ participant P as Projector
+ participant A as Audit Sink
+
+ I->>B: RunContext + ToolCallRequestEnvelope
+ B->>B: run / id / authorization / readonly / JSON preflight
+ B->>C: reserve Tool call + request bytes
+ B->>S: begin PROJECTING
+ B->>T: execute typed request
+ T-->>B: raw response
+ B->>C: reserve raw bytes
+ B->>P: project raw response
+ P-->>B: bounded agent_result + evidence_status
+ B->>C: reserve projection bytes
+ B->>S: mark READY
+ B-->>I: ToolBoundaryResult
+ B-->>A: best-effort metadata audit
+```
+
+错误发生时,已经创建的 canonical record 会尽力迁移到 ERROR;如果连 Store 都不可用,则向上只返回 `STORE_ERROR` 等稳定码,不把 Redis 或 backend 异常正文交给模型。
+
+## 5. 两套状态为什么不能合并
+
+Tool 调用同时有两个正交维度:
+
+| 维度 | 状态 | 回答的问题 |
+|---|---|---|
+| Invocation lifecycle | `PROJECTING / READY / ERROR` | 这次调用是否完成并形成了可引用记录 |
+| Evidence semantics | `EVIDENCE_FOUND / NO_EVIDENCE / ERROR` | 客观结果中是否存在候选证据 |
+
+例如日志查询成功返回 0 条:
+
+```text
+InvocationStatus = READY
+EvidenceStatus = NO_EVIDENCE
+```
+
+它不是 `ERROR`。这条负向观察可以支持“在指定时间、服务和查询条件下没有匹配日志”,但不能支持“故障不存在”。
+
+合法组合被类型约束为:
+
+| InvocationStatus | EvidenceStatus | 是否允许引用 |
+|---|---|---:|
+| `PROJECTING` | `null` | 否 |
+| `READY` | `EVIDENCE_FOUND` | 是 |
+| `READY` | `NO_EVIDENCE` | 是,但只能作为限定范围负向观察 |
+| `ERROR` | `ERROR` | 否 |
+
+把两者合成一个 `SUCCESS / FAILED` 会丢失最重要的信息:技术成功但业务范围内没有证据。
+
+## 6. 三类 Tool 如何投影
+
+### 6.1 RAG
+
+`RagResultProjector` 从 backend 结果中提取有界 evidence block,稳定文档身份,限制 excerpt 数量和长度,并兼容 `relevanceLevel / relevance_level` 后统一为 `relevance_level`。
+
+这里有一个刻意保留的区分:
+
+```text
+evidence 非空 -> EVIDENCE_FOUND
+relevance_level=REFERENCE -> 相关度一般
+```
+
+`EVIDENCE_FOUND` 只说明存在候选内容,`REFERENCE` 也不自动表示 `NO_GAIN`。内容是否推进当前诊断假设,需要模型结合上下文判断。
+
+### 6.2 Logs
+
+`QueryLogsResultProjector` 不只是截取前几条日志,它还会:
+
+- 脱敏 password、token、secret、API key;
+- 脱敏 pod、host、PID、IP 和 SQL literal;
+- 去掉堆栈尾部噪声;
+- 对数字做模式归一化并聚合重复事件;
+- 对事件做跨范围采样,而不是只保留开头;
+- 保存实际时间、topic、query scope 和截断标记。
+
+如果 backend 执行成功且日志数组为空,投影结果是 READY + NO_EVIDENCE。backend 明确返回失败,才是 Tool 执行或投影错误。
+
+### 6.3 MySQL
+
+MySQL 在投影之前还有独立的只读安全链:`MysqlSqlValidator` 根据逻辑数据源、schema、table 和 column allowlist 生成 `MysqlQueryPlan`,`JdbcMysqlReadOnlyExecutor` 只执行该 Plan。
+
+`MysqlResultProjector` 再负责:
+
+- 限制最大行数、单元格长度和总 bytes;
+- 保持 number、boolean 和 null 类型;
+- 对 password、token、secret、credential 等敏感列强制脱敏;
+- 结果缩减时标记 `truncated=true`。
+
+Validator 负责“能不能执行”,Projector 负责“模型能看到什么”,两者不能合并。
+
+## 7. Control View 与 Model Observation 的字段边界
+
+| 字段 | Harness | Agent | 原因 |
+|---|---:|---:|---|
+| `tool_call_id` | 是 | 是 | 引用和上一轮评价都需要 |
+| 实际 scope | 是 | 是 | Harness 去重;模型理解负向观察边界 |
+| 有界 evidence/events/rows | 是 | 是 | 模型推理所需事实 |
+| `evidence_status` | 是 | 是 | 区分候选证据、空结果和错误 |
+| `relevance_level` | 是 | 是 | 给模型粗粒度检索语义 |
+| `truncated` | 是 | 是 | 防止模型误以为结果完整 |
+| `returned_count` | 是 | 否 | Harness 统计,不必消耗模型上下文 |
+| normalized scope / duplicate identity | 是 | 否 | 内部控制实现 |
+| 连续 NO_GAIN、阈值和剩余预算 | 是 | 否 | 防止模型围绕限制博弈 |
+| raw response / 检索轨迹 / 原始分数 | 是 | 否 | 敏感且体积不可控 |
+
+只有 Harness 必须改变模型行为时,才注入 `STOP_REQUIRED`、错误码或已检查范围等有限控制信息。
+
+## 8. 存储决策:为什么是 Redis canonical + MySQL metadata
+
+### 8.1 不把完整 raw 长期写 MySQL
+
+完整 Tool 结果可能包含日志、SQL 查询结果和内部知识内容。长期保存会扩大泄露半径,也会让 JPA audit 成为第二个事实源。MySQL 只保存运营和对账需要的字段,可以长时间保留而不复制正文。
+
+### 8.2 Canonical 读取不续期
+
+Redis record 在创建时设置 TTL,读取或状态更新不恢复初始 TTL。原因是:如果一次历史查询就能续期,敏感 raw 可能因审计访问而永久存在。
+
+状态更新保留当前剩余 TTL,代价是 read-TTL-write 存在小的并发窗口,但它比无界续期更符合数据治理目标。
+
+### 8.3 raw 超限为什么不截断
+
+raw 是内部事实。如果静默截断后仍标记 READY,系统无法区分“backend 只返回这些内容”和“Harness 丢掉了内容”。因此 raw 或完整 record 超限时调用进入 ERROR。
+
+Agent projection 可以截断,因为它本来就是面向消费的摘要,但必须通过 `truncated=true` 明示不完整。
+
+## 9. 真实问题如何改变设计
+
+| 真实问题 | 暴露的错误假设 | 最终修正 |
+|---|---|---|
+| Tool raw 直接进入 Agent | backend 输出天然适合模型消费 | 增加 Tool-specific projector 和白名单 observation |
+| Trace、raw、ContextPack、重复正文同时存在 | 多保存几份可以提高可观测性 | canonical、model view、metadata audit 明确分层 |
+| Mock 日志 0 命中返回 `success=false` | 没数据等于调用失败 | 技术执行状态与 NO_EVIDENCE 分离 |
+| RAG 的 `REFERENCE` 在投影中丢失 | 只要 evidence 非空就够了 | 保留 relevance_level,但不提升为根因证据 |
+| 生产 ObjectMapper 未注册 Java Time 模块,Redis 全部 STORE_ERROR | 单元测试序列化环境等同生产 | 使用生产装配验证 canonical record,并增加 live E2E |
+| backend 日志打印 raw/rewritten query | 排障信息可以直接进入普通日志 | 普通日志和 durable audit 只保留安全摘要 |
+
+## 10. 代价与边界
+
+这项设计不是免费的:
+
+1. 每增加一种 Tool,都要同时定义 typed request/result、Adapter、Projector 和 EvidenceGuard 读取规则。
+2. Redis 在 TTL 内成为证据校验依赖;不可用时系统应 fail closed,而不是相信 Draft。
+3. Agent 看到的是有损投影,Projector 设计不当可能丢掉模型真正需要的诊断信号。
+4. durable audit 不能替代 canonical replay;TTL 到期后只能解释调用元数据,无法恢复完整正文。
+
+但这些成本换来了明确的责任:Backend 决定原始事实,Projector 决定模型可见范围,Canonical Store 决定短期验真事实,Audit 决定长期允许保存什么。
+
+## 11. 如何验证
+
+| 验证内容 | 代表性测试或证据 |
+|---|---|
+| Run mismatch、未授权、非只读、重复 ID、预算和超限 | `ToolBoundaryTest` |
+| PROJECTING / READY / ERROR 和 TTL 行为 | `CanonicalInvocationStoreTest` |
+| RAG evidence identity、relevance 和 bytes | `RagResultProjectorTest` |
+| 日志脱敏、模式聚合、空结果和采样 | `QueryLogsResultProjectorTest` |
+| MySQL 行列/敏感字段/容量边界 | `MysqlResultProjectorTest` |
+| typed Tool contract 不漂移 | 三类 `*ToolContractTest` |
+| 生产 Redis serializer 与 ObjectMapper | single-react cleanup live E2E |
+
+单元测试只能证明数据变换和状态机;生产 Redis serializer、实际 Tool Calling ID 和 backend 返回格式仍必须通过 live E2E 验证。
+
+## 12. 与后续机制的关系
+
+Tool 双视图只解决“什么是可验证的 Tool 事实、模型允许看到什么”,并不解决:
+
+- 这些事实是否支持最终结论:见[Harness证据安全链-从引用真实到结论可发布.md](Harness证据安全链-从引用真实到结论可发布.md);
+- Agent 是否应该继续查询:见[Harness信息增益停止-让无证据诊断正常收敛.md](Harness信息增益停止-让无证据诊断正常收敛.md)。
diff --git a/mvp/engineering/harness/Harness信息增益停止-让无证据诊断正常收敛.md b/mvp/engineering/harness/Harness信息增益停止-让无证据诊断正常收敛.md
new file mode 100644
index 0000000..299a379
--- /dev/null
+++ b/mvp/engineering/harness/Harness信息增益停止-让无证据诊断正常收敛.md
@@ -0,0 +1,302 @@
+# Harness 信息增益停止:让无证据诊断正常收敛
+
+**更新日期**:2026-07-29
+**主题**:Information Gain、Progress Tracker、STOP_REQUIRED 与过程型 Fallback
+**术语与状态**:[CONTEXT.md](CONTEXT.md) · [Harness生命周期与状态.md](Harness生命周期与状态.md)
+**上位设计**:[Harness设计-非确定性Agent的确定性控制边界.md](Harness设计-非确定性Agent的确定性控制边界.md)
+
+## 1. 预算能限制损失,不能判断何时完成
+
+在知识库只有通用说明、日志持续为空或查询条件不足时,ReAct Agent 容易出现一种看似合理的行为:不断修改关键词、时间范围或查询表达,再调用一次 Tool。
+
+每一次调用单独看都可能合法,但整个 Run 没有获得新信息。旧系统只能等到模型次数、Tool 次数、Token 或 deadline 耗尽,再以 `BUDGET_EXHAUSTED` 或 `INTERNAL_FAILURE` 结束。
+
+这暴露了两个被混淆的问题:
+
+```text
+资源预算:这次 Run 最多允许消耗多少?
+业务收敛:继续查询是否仍可能推进当前诊断?
+```
+
+预算是最后一道资源保护,不能承担正常停止策略。否则“当前证据不足”会被错误表达成“系统执行失败”。
+
+信息增益停止契约的目标是:在硬预算之前识别连续无进展,使 Agent 停止调用 Tool,并把已经完成的有限检查发布为诚实、可验证的业务结果。
+
+## 2. 先划清三方判断权
+
+设计停止机制时最危险的做法,是让某一层承担它无法可靠完成的判断。
+
+| 参与者 | 能可靠判断什么 | 不能判断什么 |
+|---|---|---|
+| Tool / Projector | 是否执行成功、结果是否为空、返回数量、实际 scope、是否截断 | 内容是否支持当前诊断假设 |
+| Diagnosis Agent | 非空内容是否确认、排除或缩小当前假设 | 是否还能绕过预算、重复和饱和门禁 |
+| Harness | scope 是否重复、连续 NO_GAIN、协议是否合规、是否允许继续 | 业务根因是什么 |
+| Release | 已有进展可以发布成哪种安全结果 | 是否应该重新调用 Tool |
+
+最终原则是:
+
+> Tool 提供客观结果,模型判断语义价值,Harness 拥有最终停止权。
+
+模型可以选择主动结束,但不能通过继续发 Tool Call 绕过 Harness 已经确定的饱和或预算状态。
+
+## 3. 为什么信息增益只有两个值
+
+当前契约只保留:
+
+```text
+information_gain = GAINED | NO_GAIN
+```
+
+没有 `HIGH / MEDIUM / LOW`,也没有置信分数。停止控制只需要知道结果是否推进了当前诊断;引入更多等级会带来阈值解释、跨 Tool 标定和模型输出不稳定,却不一定改变最终动作。
+
+判定标准是:
+
+| 结果 | Information Gain | 生产者 |
+|---|---|---|
+| Tool 执行失败 | 不产生 | 进入技术失败流程 |
+| `evidence_status=NO_EVIDENCE` | `NO_GAIN` | Harness |
+| 相同 `tool_name + normalized_scope` | `NO_GAIN` | Harness,并拒绝重复执行 |
+| 成功非空,确认/排除/缩小假设 | `GAINED` | Diagnosis Agent |
+| 成功非空,但只是通用知识、重复内容或无关事实 | `NO_GAIN` | Diagnosis Agent |
+
+RAG 的 `REFERENCE` 不能自动映射为 `NO_GAIN`。它只表示检索相关度一般;一段一般相关的资料可能仍排除一个假设,也可能完全无用,需要模型结合诊断上下文判断。
+
+## 4. 为什么模型在下一次 Tool Call 中评价上一轮
+
+模型只有在读到 Tool Observation 后,才能判断它是否有语义增益。但如果要求模型单独输出一条 progress 消息,就必须增加新的协议轮次或 Progress Judge。
+
+当前设计利用模型已经要做的下一步行为:
+
+- 输出 DiagnosisDraft,表示主动结束;
+- 发起下一次 Tool Call,表示希望继续。
+
+当模型选择继续时,下一次 Tool Call Envelope 必须携带对上一轮的评价:
+
+```json
+{
+ "previous_observation": {
+ "tool_call_id": "call-123",
+ "information_gain": "NO_GAIN"
+ },
+ "input": {
+ "query": "新的业务查询参数"
+ }
+}
+```
+
+Interceptor 在调用业务 Tool 之前完成三件事:
+
+1. 校验 `previous_observation.tool_call_id` 是否正好是当前 pending 调用;
+2. 应用 `GAINED / NO_GAIN`,更新连续计数和收集状态;
+3. 剥离 `previous_observation`,只把 `input` 传给原业务 Tool。
+
+这是 Agent-facing Tool Schema 的协议变化,但 RAG、日志和 MySQL 的业务 request 并没有被控制字段污染。
+
+```mermaid
+sequenceDiagram
+ participant M as Diagnosis Agent
+ participant I as Tool Interceptor
+ participant P as Progress Tracker
+ participant T as Business Tool
+
+ M->>I: Tool Call #1 + input
+ I->>P: no pending observation
+ I->>T: business input
+ T-->>I: successful non-empty observation
+ I->>P: mark call #1 pending evaluation
+ I-->>M: bounded observation
+
+ M->>I: Tool Call #2 + previous_observation(#1, NO_GAIN)
+ I->>P: validate ID and apply NO_GAIN
+ alt 仍为 COLLECTING
+ I->>T: strip control fields, execute input #2
+ else 达到 SATURATED
+ I-->>M: STOP_REQUIRED
+ end
+```
+
+如果模型读完 observation 后直接输出 Draft,就不需要额外评价最后一轮。因为它已经通过实际行为表达“停止”,Harness 也不需要为收集一个统计字段强迫模型再调用 Tool。
+
+## 5. ProgressTracker 保存什么,不保存什么
+
+`DiagnosisProgressTracker` 是 `RunContext` 中的线程安全状态句柄,保存:
+
+- 已完成的 `tool_name + normalized_scope`;
+- 已完成 Tool Call identity;
+- 当前等待模型评价的 `pendingToolCallId`;
+- 连续 `NO_GAIN` 次数;
+- 连续 progress protocol violation 次数;
+- `COLLECTING / SATURATED` 状态;
+- stop reason;
+- STOP_REQUIRED 是否已经交付。
+
+它不保存 request、raw response、agent result 或模型 thought。完整 Tool 事实仍属于 Canonical Store。Tracker 只保存作出停止决策需要的最小 identity 和计数,避免出现第二份 Tool 真相。
+
+结束时,`DiagnosisProgressProjector` 根据 completed identity 回读 canonical READY records,投影成有界 `ProgressSnapshot`。无法验证、不可读取或格式非法的记录不会被发布,只会形成安全 limitation。
+
+## 6. 重复检测为什么只做参数级
+
+Harness 使用 `ToolScopeNormalizer` 将业务输入转换成稳定 scope,再比较:
+
+```text
+tool_name + normalized_scope
+```
+
+这可以识别字段顺序、格式差异下的完全相同查询,并在调用 backend 前拒绝重复执行。
+
+首版没有做自然语言语义去重,例如以下两条 query 可能语义相同,但不会被代码证明为同一 scope:
+
+```text
+“查询支付超时日志”
+“查找支付请求 timeout 记录”
+```
+
+原因是语义去重需要 embedding、模型判断或跨 Tool 指纹,会引入新的不确定性和误杀风险。当前边界选择了可以确定性证明的参数重复;语义近似由模型的 `NO_GAIN` 义务约束。
+
+## 7. 收集状态机
+
+连续无增益阈值由配置控制,当前默认值为 2。`GAINED` 会清零连续计数,避免一次早期空查使后续有效取证被过早停止。
+
+```mermaid
+stateDiagram-v2
+ [*] --> COLLECTING
+ COLLECTING --> COLLECTING: GAINED / consecutiveNoGain=0
+ COLLECTING --> COLLECTING: NO_GAIN / 未达阈值
+ COLLECTING --> SATURATED: NO_GAIN / 达到阈值
+ SATURATED --> STOP_CHANCE: 交付一次 STOP_REQUIRED
+ STOP_CHANCE --> RELEASE: 模型输出 Draft
+ STOP_CHANCE --> TERMINATED: 模型再次请求 Tool
+```
+
+当某次 READY 结果是 NO_EVIDENCE 时,Harness 可以立即累计 `NO_GAIN`。当成功非空结果需要模型评价时,Tracker 设置 pending ID;上一轮未评价前,新的 Tool Call 不会被执行。
+
+达到 `SATURATED` 后,Harness 给模型一次合法完成机会,而不是在 Tool response 中立刻抛出通用错误。STOP_REQUIRED 只交付一次;模型仍继续请求 Tool 时,`DiagnosisCollectionStoppedException` 将受控停止穿出框架 ReAct loop。
+
+## 8. 协议错误为什么不能计为 NO_GAIN
+
+真实 E2E 曾出现 9 次 `INVALID_PROGRESS_PROTOCOL` Tool 请求拒绝和 13 轮 Diagnosis Agent 模型调用。模型没有正确回传上一轮评价,但这些拒绝也没有增加 NO_GAIN,最终持续空转。
+
+一种看似简单的修复是把协议错误也计为 NO_GAIN。但这会混淆两个事实:
+
+- `NO_GAIN` 表示 Tool 结果没有推进诊断;
+- 协议错误表示模型没有遵守控制契约,Tool 根本没有执行。
+
+因此引入独立的协议错误计数和 `PROGRESS_PROTOCOL_VIOLATED`:
+
+1. 第一次错误返回可修正 observation,包含 violation type、缺失字段、期望的上一轮 ID 和允许值;
+2. 连续错误达到配置阈值后进入 SATURATED;
+3. 交付一次 `STOP_REQUIRED / PROGRESS_PROTOCOL_VIOLATED`;
+4. 再次请求 Tool 时受控停止。
+
+协议错误 Trace 使用 `TOOL_REQUEST_REJECTED`,不能记录成 `TOOL_INVOCATION`,因为 backend 从未被调用,Tool 预算和实际执行数也不应被污染。
+
+## 9. 三种停止原因必须分开
+
+| Stop Reason | 含义 | 是否等于技术失败 |
+|---|---|---:|
+| `INFORMATION_SATURATED` | 连续结果没有推进诊断 | 否 |
+| `BUDGET_LIMIT_REACHED` | 达到模型、Tool、Token 或 bytes 预算边界 | 不一定;有安全进展时可发布过程 Fallback |
+| `PROGRESS_PROTOCOL_VIOLATED` | 模型连续违反 progress Envelope | 协议失败;有安全进展时仍可保留过程价值 |
+
+信息饱和不能伪装成预算耗尽,否则无法判断阈值是否合理;预算耗尽也不能伪装成信息饱和,因为可能是在持续获得有效证据时资源不足。
+
+这些 stop reason 是 Harness 内部控制语义,不直接作为第二套公开生命周期。最终仍由 Release 映射为 `SUCCESS / FALLBACK / FAILED / CANCELLED`。
+
+## 10. 停止以后如何形成用户结果
+
+停止本身不是答案。系统需要把已经完成的检查转换成可发布内容,又不能依赖预算耗尽后额外调用模型。
+
+`DiagnosisProgressProjector` 从 canonical READY results 生成:
+
+- verified sources;
+- observed facts,包括限定范围的空结果;
+- 实际查询 scope;
+- 投影失败、截断或不可读取形成的 limitations;
+- stop reason。
+
+Release 根据现有进展决定:
+
+```mermaid
+flowchart TD
+ S["Agent 主动无结论
或 Harness 受控停止"] --> P["ProgressSnapshot"]
+ P --> Q{"存在已验真 observed facts?"}
+ Q -->|"是"| I["INSUFFICIENT_EVIDENCE
展示已检查内容和下一步"]
+ Q -->|"否"| M{"Draft 声明 missing_info?"}
+ M -->|"是"| C["MISSING_REQUIRED_CONTEXT"]
+ M -->|"否"| F["FAILED / fail closed"]
+```
+
+`conclusion=null` 是合法 Draft。它允许 Agent 在缺少企业、时间、服务或错误信息时零次调用 Tool,直接报告 `missing_info`,避免为了表现“已经排查”而执行无明确范围的查询。
+
+## 11. 为什么没有引入更多控制字段
+
+### 11.1 不使用 `new_count`
+
+“新记录数量”需要跨 RAG 文档、日志事件和数据库行建立稳定指纹,而且新数据不等于对当前假设有用。它增加了复杂度,却不能替代语义增益判断。
+
+### 11.2 不使用 `next_action`
+
+模型发起 Tool Call已经表示继续,输出 Draft 已经表示结束。再要求 `CONTINUE / STOP` 只会形成一套可能与实际行为冲突的声明状态。
+
+### 11.3 不增加 Progress Judge
+
+独立 Judge 会为每轮 Tool 结果增加模型调用、延迟和失败面。空结果和完全重复 scope 本可由代码判断;其他内容由正在做诊断的 Agent 评价即可。
+
+### 11.4 不向模型公开剩余预算和阈值
+
+模型只需要知道是否必须停止,不需要围绕“还剩几次”规划消耗。阈值、计数和预算属于 Harness Control View,只有 STOP_REQUIRED 等必要指令进入模型上下文。
+
+## 12. Prompt 与硬门禁如何分工
+
+Prompt 仍然需要告诉模型:
+
+- 不必须得出根因;
+- `conclusion=null` 是合法完成;
+- 缺少必要上下文时可以零 Tool 结束;
+- 正确但不能推进假设的内容也是 NO_GAIN;
+- 不要通过改写相似关键词重复查询;
+- 收到 STOP_REQUIRED 后必须停止。
+
+但 Prompt 只是帮助模型做出正确选择,不构成系统保证。重复 scope、pending evaluation、饱和状态、预算和一次性 STOP_REQUIRED 都由代码门禁执行。
+
+## 13. 真实问题如何改变设计
+
+| 真实现象 | 被证伪的假设 | 设计修正 |
+|---|---|---|
+| 空日志、通用知识仍不断改写查询 | 模型会自然意识到没有进展 | 明确信息增益义务和 Harness 饱和状态 |
+| 最终以 BUDGET_EXHAUSTED 结束 | 硬预算可以充当正常停止 | 预算与信息饱和分离 |
+| `REFERENCE` 非空结果持续触发查询 | 非空候选就是有价值证据 | 检索相关度与诊断增益分离 |
+| 相同 scope 被反复执行 | Prompt 足以禁止重复 | Harness 参数级去重并在 backend 前拒绝 |
+| 9 次协议拒绝仍消耗 13 轮模型 | 错误 observation 会让模型自修复 | 可修正反馈 + 独立协议阈值 + STOP_REQUIRED |
+| 非法 Draft 使已完成检查丢失 | 只有合法最终 Draft 才有用户价值 | 已验真 ProgressSnapshot 可形成过程型 Fallback |
+| 缺少企业/时间仍被迫调用 Tool | Tool 调用次数大于零才算诊断 | 允许零 Tool、missing context 合法结束 |
+
+## 14. 代价与当前边界
+
+1. 模型侧 Tool Schema 增加了 `previous_observation + input`,这是明确的 Agent-facing 协议变化。
+2. 首版只能确定性识别参数相同的重复 scope,不能阻止所有自然语言近义改写。
+3. 默认连续 NO_GAIN 阈值 2 是工程起点,需要依靠固定评测集校准;太小会过早停止,太大会增加空转。
+4. 最后一轮非空 Tool 结果如果模型直接输出 Draft,Tracker 不强制收集其 information gain;这是减少无意义协议轮次的主动取舍。
+5. ProgressSnapshot 依赖 canonical record 仍在 TTL 内且可解析,无法验真的进展不会被发布。
+6. 受控预算停止只有在已有安全进展时才能转为 Fallback;没有可验证内容仍然 fail closed。
+
+## 15. 如何验证
+
+| 需要证明 | 代表性测试或 E2E |
+|---|---|
+| NO_EVIDENCE 自动累计 NO_GAIN,GAINED 清零 | `DiagnosisProgressTrackerTest` |
+| 重复 scope 在 backend 前被拒绝 | `HarnessToolInterceptorTest`、`ToolScopeNormalizerTest` |
+| pending evaluation 的缺失、乱序和意外回传被拒绝 | Interceptor protocol focused cases |
+| 连续协议错误达到阈值并只交付一次 STOP_REQUIRED | Tracker + Interceptor tests |
+| 模型主动停止、饱和停止和预算停止都能进入 Release | `DiagnosisAgentUseCaseTest`、`DiagnosisReleaseUseCaseTest` |
+| 非法 Draft 只有在存在安全进展时降级 | `DiagnosisChatExecutorTest` |
+| Tool 拒绝与实际 Tool 执行分开审计 | exact-run Trace |
+| 未知 Query 不再以通用内部错误结束 | ISS-016 named SSE E2E |
+
+其中一条 live E2E 曾准确暴露“协议拒绝不累计 NO_GAIN”的盲区。这说明停止机制不能只验证最终 SSE,还要核对模型轮次、Tool 实际执行数、Tool 拒绝数、Token 和 Timeline 序列。
+
+## 16. 与另外两项设计的关系
+
+信息增益依赖 [Tool 双视图](Harness-Tool双视图-从原始结果到可验证证据.md)提供客观 evidence status、scope 和有界 observation;停止后的 ProgressSnapshot 和最终 Fallback 依赖[证据安全链](Harness证据安全链-从引用真实到结论可发布.md)确保只发布当前 Run 可验证的事实。
+
+三者组合后,Harness 才能完整回答:模型看到了什么、为什么继续或停止、最终哪些内容可以发布。
diff --git a/mvp/engineering/harness/Harness生命周期与状态.md b/mvp/engineering/harness/Harness生命周期与状态.md
new file mode 100644
index 0000000..751ce92
--- /dev/null
+++ b/mvp/engineering/harness/Harness生命周期与状态.md
@@ -0,0 +1,440 @@
+# Harness 生命周期与状态
+
+**更新日期**:2026-07-29
+**状态**:当前实现口径
+**术语前置**:[CONTEXT.md](CONTEXT.md)
+
+## 1. 先澄清:系统不存在一条包含所有状态的总状态机
+
+当前 Harness 有多组正交状态:
+
+- RunState 控制内存执行终态;
+- ChatApplicationStatus 表示用户可见处理阶段;
+- InvocationStatus 表示单次 Tool invocation 生命周期;
+- EvidenceStatus 表示 Tool 客观结果;
+- CollectionState 表示能否继续收集证据;
+- StopReason 表示停止收集的内部原因;
+- SemanticVerdict 表示结论支持度;
+- ReleaseOutcome 表示最终发布结果;
+- SSE Session State 表示连接能否继续发送。
+
+它们在一次请求中并行演进,只在少数边界发生映射。生命周期文档的目标不是把它们合并,而是说明谁先发生、由谁拥有、在哪里汇合。
+
+## 2. 一次 Run 的主时间线
+
+```mermaid
+sequenceDiagram
+ participant C as Client / SSE
+ participant A as ChatApplicationUseCase
+ participant H as Harness Core
+ participant R as Intent Router
+ participant D as Diagnosis Runtime
+ participant L as Release Pipeline
+ participant P as Persistence / Trace
+
+ C->>A: query + optional sessionId
+ A->>A: 读取 routing history / PreviousTurn
+ A->>H: startRun(sessionId)
+ H-->>A: RUNNING RunContext
+ A->>P: diagnosis_run=RUNNING + RUN_STARTED
+ A-->>C: metadata(sessionId, runId)
+
+ A->>R: route(query, bounded history)
+ R-->>A: IntentType
+
+ alt SYSTEM_CHAT
+ A->>A: 单轮受控模型回答
+ else KNOWLEDGE_QUERY
+ A->>A: 一次 RAG + 单轮答案
+ else DIAGNOSIS
+ A->>D: Agent ReAct / Tool / Progress
+ D-->>A: Draft 或 Controlled Stop
+ A->>L: release(Draft/Progress/StopReason)
+ L-->>A: SUCCESS Draft 或 SafeFallback
+ end
+
+ alt 正常完成
+ A->>H: completeSuccess(预算 Fallback 特例除外)
+ A->>P: 持久化 release outcome / safe content / usage
+ A->>P: RUN_FINISHED
+ A-->>C: content + done
+ else 失败
+ A->>H: completeFailure 或读取既有终态
+ A->>P: 持久化 FAILED/CANCELLED
+ A->>P: RUN_FINISHED
+ A-->>C: failure + done(FAILED)
+ end
+```
+
+创建顺序很重要:Application 先读取会话上下文,再创建 RunContext、持久化 RUNNING、记录 RUN_STARTED,之后才进入路由和执行。SSE 的 metadata 在 `onStarted` 中发布 exact sessionId/runId,后续结果必须匹配这组身份。
+
+## 3. Run 生命周期
+
+### 3.1 状态
+
+```mermaid
+stateDiagram-v2
+ [*] --> RUNNING
+ RUNNING --> SUCCESS: 正常路径完成
+ RUNNING --> FAILED: 不可恢复内部失败
+ RUNNING --> CANCELLED: 客户端断开或用户取消
+ RUNNING --> TIMED_OUT: deadline 到达
+ RUNNING --> BUDGET_EXHAUSTED: 任一硬预算超限
+
+ SUCCESS --> [*]
+ FAILED --> [*]
+ CANCELLED --> [*]
+ TIMED_OUT --> [*]
+ BUDGET_EXHAUSTED --> [*]
+```
+
+`RunLifecycle.finish` 使用原子 compare-and-set:只有从“尚无 termination”到某个终态的第一次转换成功。所有终态都不可再次转换。
+
+### 3.2 状态含义
+
+| RunState | 含义 | 常见来源 |
+|---|---|---|
+| `RUNNING` | 尚未产生 RunTermination | `startRun` 后默认状态 |
+| `SUCCESS` | Application 已形成正常可持久化结果 | 正常内容,或非预算类 SafeFallback |
+| `FAILED` | 不可恢复执行失败 | 路由不可用、模型失败、无安全进展的非法 Draft 等 |
+| `CANCELLED` | 协作取消已成为第一个终态 | 客户端断开、用户请求 |
+| `TIMED_OUT` | `checkActive` 发现 deadline 已到 | 模型/Tool/Guard 边界前后检查 |
+| `BUDGET_EXHAUSTED` | 模型、Tool、Token 或 bytes 预算超限 | `DiagnosisHarnessCore.exhaustBudget` |
+
+### 3.3 RunState 与 ReleaseOutcome 不一一对应
+
+最重要的反例是 Fallback:
+
+- 证据不足、缺少上下文、语义不支持等正常降级,RunState 最终为 `SUCCESS`,ReleaseOutcome 为 `FALLBACK`;
+- 预算耗尽后已经有安全 ProgressSnapshot,RunState 保持 `BUDGET_EXHAUSTED`,但 ReleaseOutcome 可以为 `FALLBACK`;
+- 超时或预算耗尽且无法形成安全内容时,RunState 分别保持 `TIMED_OUT / BUDGET_EXHAUSTED`,ReleaseOutcome 映射为 `FAILED`。
+
+所以不能从 ReleaseOutcome 反推出精确 RunState,也不能把 `FALLBACK` 当作 RunState。
+
+## 4. 取消生命周期
+
+`RunCancellation` 使用 first-reason-wins。取消原因包括:
+
+| Reason | RunState 映射 |
+|---|---|
+| `CLIENT_DISCONNECTED` | `CANCELLED` |
+| `USER_REQUESTED` | `CANCELLED` |
+| `DEADLINE_EXCEEDED` | `TIMED_OUT` |
+| `BUDGET_EXHAUSTED` | `BUDGET_EXHAUSTED` |
+| `INTERNAL_FAILURE` | `FAILED` |
+
+生命周期和取消句柄互相配合:取消回调尝试完成 RunLifecycle;deadline 和预算路径也会先确定终态,再触发取消阻止后续工作。
+
+取消语义是协作式的:
+
+1. 后续模型、Tool、Guard 边界调用 `checkActive` 时立即失败;
+2. SSE 断开后不再发送 content;
+3. first-terminal-wins 阻止迟到成功覆盖 CANCELLED/TIMED_OUT;
+4. 已经进入 Provider 的同步调用是否物理停止,取决于底层客户端,Harness 不作虚假保证。
+
+## 5. Application 阶段不是生命周期状态
+
+`ChatApplicationStatus` 用于 SSE status 事件:
+
+```text
+ROUTING
+SYSTEM_RESPONDING
+KNOWLEDGE_SEARCHING
+KNOWLEDGE_ANSWERING
+DIAGNOSIS_RUNNING
+SAFETY_VALIDATING
+```
+
+这些值只是用户可见的处理阶段:
+
+- 不是严格完备的状态机;
+- 不表示终态;
+- 不持有取消或预算;
+- 不保证每个 Run 都经过全部阶段。
+
+例如 Diagnosis Run 通常经过 `ROUTING -> DIAGNOSIS_RUNNING -> SAFETY_VALIDATING`,System Chat 则经过 `ROUTING -> SYSTEM_RESPONDING`。
+
+## 6. Diagnosis Agent 执行生命周期
+
+`DiagnosisAgentExecution` 只有两种合法形态,不额外定义一套枚举:
+
+| 形态 | 字段 | 含义 |
+|---|---|---|
+| Completed | `draft != null, stopReason=null` | Agent 输出严格合法 DiagnosisDraft |
+| Controlled Stop | `draft=null, stopReason != null` | Tool loop 因信息饱和、预算或协议错误受控停止 |
+
+其他情况通过异常表达:
+
+- 空 Draft;
+- 非法 JSON;
+- Schema 不合法;
+- 模型调用失败;
+- RunAborted。
+
+Draft 合同失败不是 DiagnosisStopReason。非法 Draft 会被丢弃;只有异常携带的 ProgressSnapshot 已包含可验真 facts,Application 才允许 Release 生成过程型 Fallback。
+
+## 7. 单次 Tool Invocation 生命周期
+
+### 7.1 状态转换
+
+```mermaid
+stateDiagram-v2
+ [*] --> PREFLIGHT
+ PREFLIGHT --> REJECTED: invalid run/id/auth/readonly/request/budget/store
+ PREFLIGHT --> PROJECTING: canonical begin 成功
+ PROJECTING --> READY: backend + projection + store 成功
+ PROJECTING --> ERROR: execution/projection/size/budget/store error
+ READY --> [*]
+ ERROR --> [*]
+ REJECTED --> [*]
+```
+
+`PREFLIGHT` 和 `REJECTED` 是本文用于解释流程的阶段,不是 `InvocationStatus` 枚举值。canonical record 只有:
+
+- `PROJECTING`:已创建,尚未形成可引用结果;
+- `READY`:执行和投影完成,可以被当前 Run 引用;
+- `ERROR`:调用失败,不可引用。
+
+部分 preflight 失败发生在 canonical begin 之前,因此可能只有 `ToolBoundaryResult.ERROR` 和审计事件,没有 canonical ERROR record。
+
+### 7.2 EvidenceStatus 是另一条轴
+
+| InvocationStatus | EvidenceStatus | 语义 |
+|---|---|---|
+| `PROJECTING` | `null` | 未完成 |
+| `READY` | `EVIDENCE_FOUND` | 技术完成,存在候选内容 |
+| `READY` | `NO_EVIDENCE` | 技术完成,当前 scope 为空 |
+| `ERROR` | `ERROR` | 技术失败 |
+
+`READY + EVIDENCE_FOUND` 仍不表示证据足以支持结论;后续还要经过 InformationGain、EvidenceGuard 和 SemanticGuard。
+
+## 8. 信息收集生命周期
+
+### 8.1 Collection State
+
+```mermaid
+stateDiagram-v2
+ [*] --> COLLECTING
+ COLLECTING --> COLLECTING: GAINED / 清零 no-gain
+ COLLECTING --> COLLECTING: NO_GAIN / 未达阈值
+ COLLECTING --> SATURATED: 连续 NO_GAIN 达阈值
+ COLLECTING --> SATURATED: 连续 progress protocol violation 达阈值
+ SATURATED --> SATURATED: 终态,不再允许新 evidence Tool
+```
+
+`SATURATED` 只表示证据收集不再允许继续。它不是整个 Run 的终态,Agent 仍有一次机会输出 Draft,之后进入 Release。
+
+预算超限不会把 CollectionState 改成 SATURATED。它将 RunState 置为 `BUDGET_EXHAUSTED`,并把内部 DiagnosisStopReason 标记为 `BUDGET_LIMIT_REACHED`。
+
+### 8.2 InformationGain
+
+- `NO_EVIDENCE` 和完全重复的 `tool_name + normalized_scope` 由 Harness 产生 `NO_GAIN`;
+- 其他成功非空结果由 Diagnosis Agent 评价 `GAINED / NO_GAIN`;
+- `GAINED` 清零连续 NO_GAIN;
+- 技术失败和 progress 协议错误不产生 InformationGain。
+
+### 8.3 StopReason
+
+| DiagnosisStopReason | 触发条件 | 对 CollectionState 的影响 |
+|---|---|---|
+| `INFORMATION_SATURATED` | 连续 NO_GAIN 达阈值 | 进入 SATURATED |
+| `PROGRESS_PROTOCOL_VIOLATED` | 连续协议错误达阈值 | 进入 SATURATED |
+| `BUDGET_LIMIT_REACHED` | RunBudget 超限 | 不要求进入 SATURATED;Run 已终止 |
+
+STOP_REQUIRED 是否已经交付由独立 boolean 记录,它不是第四种 CollectionState。一次指令交付后仍请求 Tool,会抛出受控停止异常。
+
+## 9. Guard 与 Release 生命周期
+
+```mermaid
+flowchart TD
+ I["DiagnosisAgentExecution"] --> D{"有合法 Draft?"}
+ D -->|"否,受控停止"| PS["验证 ProgressSnapshot"]
+ D -->|"是,无 conclusion"| NC["验证已有 Tool references"]
+ D -->|"是,有 conclusion"| EG["EvidenceGuard initial"]
+
+ EG --> EV{"evidence valid?"}
+ EV -->|"否"| ER["EvidenceRepair once"]
+ ER --> RE["EvidenceGuard recheck"]
+ EV -->|"是"| SG["SemanticGuard"]
+ RE -->|"valid"| SG
+ RE -->|"invalid"| EF["EVIDENCE_VALIDATION_FAILED"]
+
+ SG -->|"SUPPORTED"| SU["ReleaseOutcome.SUCCESS"]
+ SG -->|"UNSUPPORTED"| SF["SEMANTIC_UNSUPPORTED"]
+ SG -->|"unavailable"| UF["SEMANTIC_UNAVAILABLE"]
+
+ NC -->|"有 progress"| IF["INSUFFICIENT_EVIDENCE"]
+ NC -->|"无 progress,有 missing_info"| MF["MISSING_REQUIRED_CONTEXT"]
+ NC -->|"两者都无"| FAIL["FAILED"]
+ PS -->|"有 verified facts"| IF
+ PS -->|"无 verified facts"| FAIL
+
+ EF --> FB["ReleaseOutcome.FALLBACK"]
+ SF --> FB
+ UF --> FB
+ IF --> FB
+ MF --> FB
+```
+
+`DiagnosisReleaseResult` 只表达 `SUCCESS` 或 `FALLBACK`。真正不可恢复的 `FAILED / CANCELLED` 由 Chat Application 异常路径映射。
+
+## 10. RunState、ReleaseOutcome 和 FallbackType 映射
+
+| 场景 | RunState | ReleaseOutcome | FallbackType / 内容 |
+|---|---|---|---|
+| 有结论且 Guards 通过 | `SUCCESS` | `SUCCESS` | Diagnosis report |
+| 缺少必要上下文,零 Tool 合法结束 | `SUCCESS` | `FALLBACK` | `MISSING_REQUIRED_CONTEXT` |
+| 有限检查后证据不足 | `SUCCESS` | `FALLBACK` | `INSUFFICIENT_EVIDENCE` |
+| 信息饱和后有安全进展 | `SUCCESS` | `FALLBACK` | `INSUFFICIENT_EVIDENCE` |
+| 协议停止后有安全进展 | `SUCCESS` | `FALLBACK` | `INSUFFICIENT_EVIDENCE` |
+| EvidenceGuard 最终失败 | `SUCCESS` | `FALLBACK` | `EVIDENCE_VALIDATION_FAILED` |
+| SemanticGuard 判定不支持 | `SUCCESS` | `FALLBACK` | `SEMANTIC_UNSUPPORTED` |
+| SemanticGuard 技术不可用 | `SUCCESS` | `FALLBACK` | `SEMANTIC_UNAVAILABLE` |
+| 预算耗尽但有安全进展 | `BUDGET_EXHAUSTED` | `FALLBACK` | 当前发布 `INSUFFICIENT_EVIDENCE` |
+| 超时,无法形成安全内容 | `TIMED_OUT` | `FAILED` | failure |
+| 预算耗尽且无安全进展 | `BUDGET_EXHAUSTED` | `FAILED` | failure |
+| 不可恢复内部失败 | `FAILED` | `FAILED` | failure |
+| 客户端断开 | `CANCELLED` | `CANCELLED` | 不公开 CANCELLED done |
+
+这张表解释了为什么不能只问“最后 status 是什么”。必须先确定是在问内存执行、发布结果还是 Fallback 原因。
+
+## 11. SSE 连接生命周期
+
+`ChatSseSession` 有自己的连接状态机:
+
+```mermaid
+stateDiagram-v2
+ [*] --> NEW
+ NEW --> OPEN: onStarted / metadata
+ NEW --> DISCONNECTED: 客户端提前断开
+ OPEN --> OPEN: status events
+ OPEN --> TERMINAL: content + done
+ OPEN --> TERMINAL: failure + done(FAILED)
+ OPEN --> DISCONNECTED: send failure / disconnect
+ TERMINAL --> [*]
+ DISCONNECTED --> [*]
+```
+
+公开事件顺序是:
+
+```text
+metadata -> status* -> content | failure -> done
+```
+
+边界规则:
+
+- content 最多发送一次;
+- result 的 sessionId/runId 必须匹配 metadata;
+- TERMINAL 或 DISCONNECTED 后拒绝迟到内容;
+- `ReleaseOutcome.CANCELLED` 不作为公开 done outcome;客户端已经断开时没有可靠发送目标;
+- 当前 `SseOutcome` 枚举未参与运行时协议,`ChatSseEvent.Done` 使用 `ReleaseOutcome`。
+
+## 12. 持久化生命周期
+
+### 12.1 创建
+
+RunContext 创建后,Application 写入:
+
+```text
+diagnosis_run.status = RUNNING
+session_id / run_id / query
+```
+
+之后记录 intent。
+
+### 12.2 完成映射
+
+`JpaChatRunStore` 根据 ReleaseOutcome 映射数据库通用 status:
+
+| ReleaseOutcome | diagnosis_run.status |
+|---|---|
+| `SUCCESS` | `SUCCESS` |
+| `FALLBACK` | `SUCCESS` |
+| `FAILED` | `FAILED` |
+| `CANCELLED` | `CANCELLED` |
+
+这里的 `status=SUCCESS` 表示请求已被正常处理并形成安全内容,不表示一定有诊断 conclusion。
+
+同时保存:
+
+- `release_outcome`;
+- safe answer JSON;
+- 从 safe content 提取的 conclusion;
+- duration、Token、Agent step 数和实际 Tool call 数;
+- 仅在 `DIAGNOSIS + SUCCESS` 时保存 `published_result`。
+
+Fallback 不进入下一轮 PreviousTurn。
+
+### 12.3 当前可观测缺口
+
+内存 `RunTermination.state/reason` 当前没有独立字段直接持久化。`RUN_FINISHED` 主要记录 ReleaseOutcome 和预算对账,精确的 TIMED_OUT/BUDGET_EXHAUSTED 原因需要结合异常路径和其他 Trace 事件判断。
+
+因此数据库 `status`、ReleaseOutcome 和 Trace 都是必要观察面,任何一个都不是完整替代品。
+
+## 13. Trace Timeline 如何对应生命周期
+
+典型 Diagnosis SUCCESS:
+
+```text
+RUN_STARTED
+ROUTING_ATTEMPT
+ROUTING_DECISION
+AGENT_MODEL_STEP
+TOOL_INVOCATION / TOOL_PROGRESS ...
+EVIDENCE_GUARD_INITIAL
+SEMANTIC_GUARD_ATTEMPT
+SEMANTIC_GUARD_DECISION
+RELEASE_DECISION SUCCESS
+RUN_FINISHED SUCCESS
+```
+
+典型信息不足 Fallback:
+
+```text
+RUN_STARTED
+ROUTING_ATTEMPT
+ROUTING_DECISION
+AGENT_MODEL_STEP
+TOOL_INVOCATION / TOOL_PROGRESS ...
+COLLECTION_STOP(可选)
+EVIDENCE_GUARD_INITIAL(无结论引用检查)
+RELEASE_DECISION FALLBACK
+RUN_FINISHED FALLBACK
+```
+
+`TracePhase` 和 `TraceEventStatus` 用来组织 Timeline。它们描述事件发生在哪一阶段、该事件结果如何,不构成新的 Run 生命周期。
+
+## 14. 并发和迟到结果规则
+
+一次 Run 的终止安全依赖三层协作:
+
+1. `RunLifecycle`:first-terminal-wins,终态不可覆盖;
+2. `DiagnosisHarnessCore.checkActive`:每个关键边界阻止终止后的新工作;
+3. `ChatSseSession`:TERMINAL/DISCONNECTED 后拒绝迟到 content。
+
+这能保证逻辑上的“取消后不再发布”。它不等于强制终止底层线程或 Provider 计算;底层调用返回后仍要经过 active 和 SSE state 检查,迟到结果才会被丢弃。
+
+## 15. 排障时应该先看哪个状态
+
+| 问题 | 首先看 | 然后看 |
+|---|---|---|
+| 用户为什么收到 Fallback | `release_outcome + FallbackType` | RELEASE/EVIDENCE/SEMANTIC Trace |
+| Agent 为什么停止调用 Tool | `DiagnosisStopReason` | TOOL_PROGRESS、TOOL_REQUEST_REJECTED、COLLECTION_STOP |
+| Tool 为什么没有证据 | `InvocationStatus + EvidenceStatus` | canonical record 和 Tool audit error code |
+| Run 是超时还是预算耗尽 | 内存 termination(运行中)或相关 Trace/异常 | budget usage、collection stop、失败码 |
+| 数据库为什么 status=SUCCESS 但没有结论 | `release_outcome` | answer 的 content type / FallbackType |
+| 为什么没有 SSE done | `ChatSseSession.State` | client disconnect/send failure 和 Run cancellation |
+| 为什么下一轮没有上一轮上下文 | 是否 `DIAGNOSIS + ReleaseOutcome.SUCCESS + published_result` | PublishedResultPolicy |
+
+## 16. 生命周期不变量
+
+1. 一个 Run 只能有一个 RunTermination。
+2. 一个公开 SSE 最多有一次 content/failure 和一次 done。
+3. Tool Invocation 只有 READY 才能被引用。
+4. READY 必须同时拥有 `EVIDENCE_FOUND` 或 `NO_EVIDENCE`。
+5. `NO_EVIDENCE` 不能被提升为全局否定。
+6. SATURATED 后不再执行新的 evidence Tool。
+7. StopReason 不直接决定公开内容,必须经过 Release。
+8. 有 conclusion 才执行完整 EvidenceGuard/Repair/SemanticGuard 链。
+9. FALLBACK 是正常发布结果,不等于 RunState.FAILED。
+10. 数据库 status、ReleaseOutcome 和 RunState 不能互相替代。
diff --git a/mvp/engineering/harness/Harness组件全景-职责-设计原因与边界.md b/mvp/engineering/harness/Harness组件全景-职责-设计原因与边界.md
new file mode 100644
index 0000000..b0ee54d
--- /dev/null
+++ b/mvp/engineering/harness/Harness组件全景-职责-设计原因与边界.md
@@ -0,0 +1,374 @@
+# Harness 组件全景:职责、设计原因与边界
+
+**更新日期**:2026-07-29
+**适用代码**:`src/main/java/com/superbiz/agent/harness`
+**术语与状态**:[CONTEXT.md](CONTEXT.md) · [Harness生命周期与状态.md](Harness生命周期与状态.md)
+**配套主文**:[Harness设计-非确定性Agent的确定性控制边界.md](Harness设计-非确定性Agent的确定性控制边界.md)
+
+> 本文是完整组件参考手册,不建议第一次接触 Harness 时顺序阅读。入门请先读 [Harness 阅读入口](README.md),需要逐组理解组件时使用[组件渐进式导读](components/README.md)。
+
+## 1. 这份文档怎样定义“全部组件”
+
+当前 `harness` 目录包含 10 个一级职责域、189 个 Java 源文件。它们并不都是独立运行的“服务”:
+
+- **执行组件**拥有行为,例如 Core、Interceptor、Boundary、Guard、Release、Executor;
+- **端口与适配器**隔离框架、Redis、JPA、JDBC 和业务 Tool;
+- **状态与契约类型**固定跨组件语言,防止字符串协议漂移;
+- **Limits、Prompt 和异常类型**把边界配置与失败语义显式化。
+
+因此本篇先解释 10 个职责域为什么存在,再列出每个生产类型。判断某个类应该放在哪里时,只问三个问题:它拥有什么状态、它能作出什么决定、它绝不能决定什么。
+
+## 2. 组件地图
+
+| 职责域 | 文件数 | 解决的问题 | 核心组件 |
+|---|---:|---|---|
+| `application` | 34 | 谁创建 Run、路由请求、持久化和映射公开结果 | `ChatApplicationUseCase`、三个 Executor、`ChatRunStore` |
+| `core` | 14 | deadline、取消、预算和唯一终态由谁拥有 | `DiagnosisHarnessCore`、`RunContext`、`RunBudget`、`RunLifecycle` |
+| `agent` | 17 | 如何把框架 ReAct 接入 Harness,而不复制 ReAct | `DiagnosisAgentUseCase`、Factory、Model/Tool Interceptor |
+| `progress` | 14 | 如何识别无增益、重复和协议空转 | `DiagnosisProgressTracker`、Projector、scope normalizer |
+| `tool` | 49 | Tool 如何安全执行、保存真相并只暴露必要内容 | `ToolBoundary`、Adapters、Projectors、Canonical Store、MySQL sandbox |
+| `guard` | 15 | 如何分开验证引用真实性和结论支持度 | `EvidenceGuard`、`SemanticGuard`、`GuardModelCall` |
+| `release` | 6 | 谁拥有最终 SUCCESS / FALLBACK 决策 | `DiagnosisReleaseUseCase`、`EvidenceRepair`、`SafeFallbackFactory` |
+| `retry` | 8 | 哪些失败允许重试、attempt 如何可见 | `HarnessRetryExecutor`、Policies、Failure taxonomy |
+| `audit` | 17 | 如何重放决策而不复制敏感正文 | Trace、Tool audit、Model ledger、Agent hook |
+| `contract` | 15 | 跨层公开语言如何保持类型化 | Draft、PublishedResult、Fallback、状态枚举 |
+
+```mermaid
+flowchart LR
+ APP["application
Run 与公开用例"] --> CORE["core
执行不变量"]
+ APP --> AGENT["agent
ReAct 接入"]
+ AGENT --> PROGRESS["progress
收敛控制"]
+ AGENT --> TOOL["tool
证据边界"]
+ APP --> RELEASE["release
唯一发布"]
+ RELEASE --> GUARD["guard
真实性与支持度"]
+ CORE --> RETRY["retry
显式 attempt"]
+ CORE -.-> AUDIT["audit
可观测账本"]
+ AGENT -.-> AUDIT
+ TOOL -.-> AUDIT
+ RELEASE -.-> AUDIT
+ CONTRACT["contract
类型化语言"] -.-> APP
+ CONTRACT -.-> AGENT
+ CONTRACT -.-> TOOL
+ CONTRACT -.-> GUARD
+ CONTRACT -.-> RELEASE
+```
+
+## 3. Application:Run 的应用所有者
+
+### 为什么需要
+
+Core 只知道一次 Run 是否活跃,并不知道 HTTP、SSE、意图路由、数据库持久化和上一轮上下文。若这些职责塞进 Core,Harness 会变成业务工作流引擎;若散落在 Controller,则每个入口都可能产生不同的终态和 Fallback。
+
+### 核心组件
+
+| 组件 | 为什么设计 | 作用 | 明确边界 |
+|---|---|---|---|
+| `ChatApplicationUseCase` | 为一次请求建立唯一应用事务边界 | 解析 session、读取历史、创建 Run、路由、执行分支、落终态和安全输出 | 不做诊断推理,不自行构造诊断 Fallback |
+| `IntentRouter` | 路由也会消耗模型、超时并返回非法 JSON | 在有界输入、timeout 和显式 retry 下输出唯一 `IntentType` | 不执行业务 Tool,不生成最终回答 |
+| `SystemChatExecutor` | 系统问答不需要 ReAct,但仍必须受模型预算控制 | 单轮回答产品能力和闲聊 | 不声称查询了实时数据 |
+| `KnowledgeQueryExecutor` | 知识问答需要一次 RAG 和一次受控生成,但不需要完整诊断链 | 调用知识 Tool、校验 Tool result、生成带来源答案 | 首版不进入 SemanticGuard,不执行多 Tool 诊断 |
+| `DiagnosisChatExecutor` | Agent 执行和安全发布需要一个明确接合点 | 执行 Agent、处理合法停止/非法 Draft、调用 Release、映射公开内容 | 不重复 Guard 或 Release 决策 |
+| `ChatRunStore` / `JpaChatRunStore` | 内存 Run 状态与长期数据库状态职责不同 | 保存 Run 开始、intent、结果、预算摘要;读取安全上一轮 | 不保存 canonical raw;只允许安全发布结果进入 PreviousTurn |
+| `PublishedResultPolicy` | 直接复用上一轮完整结果会让上下文无限增长并传播失败内容 | 生成可持久化 PublishedResult 和有界 PreviousTurn | Fallback、失败、raw evidence 不进入下一轮 |
+
+### 类型清单
+
+| 类型 | 分类与功能 |
+|---|---|
+| `ChatApplicationRequest`、`ChatApplicationResult` | 应用入口和出口 DTO;固定 session、run、intent、outcome 和内容类型 |
+| `ChatApplicationContent`、`ChatContentType` | 公开内容的 sealed/typed 边界,避免任意对象直接发给 SSE |
+| `DiagnosisContent`、`KnowledgeContent`、`SystemChatContent`、`FallbackContent` | 四种公开内容载体;分别包装安全诊断、知识答案、系统回答和 Fallback |
+| `ChatApplicationStatus`、`ChatApplicationObserver` | 向 SSE 报告有界阶段,不泄露模型内部步骤 |
+| `ChatRunControl` | 只向入口暴露 exact session/run 和客户端断开取消能力 |
+| `ChatApplicationException`、`ChatFailureCode` | 把内部异常映射为稳定、可公开的失败语义 |
+| `IntentRouting`、`SystemChatOperation`、`KnowledgeQueryOperation`、`DiagnosisOperation` | 四个应用端口;使主用例不依赖具体模型或执行器 |
+| `DiagnosisExecutionResult` | Diagnosis 分支的内部返回,携带 outcome、content、published result 和预算已处理标记 |
+| `IntentRouterInput`、`IntentRouterLimits`、`IntentRouterPrompt`、`IntentRoutingException` | 路由输入、边界、Prompt 和失败类型 |
+| `KnowledgeQueryLimits`、`SingleTurnExecutorLimits` | 知识与单轮模型路径的输入、输出、timeout 上限 |
+| `ChatRunStore`、`RoutingHistory` | 持久化端口及最小路由历史 |
+| `PreviousTurnLimits`、`PublishedResultPolicy` | 安全历史的字段/大小限制与投影策略 |
+
+## 4. Core:每个 Run 的执行不变量
+
+### 为什么需要
+
+模型、Tool、Guard 和 Application 都要检查取消、deadline 和预算。如果每层各自维护计数或终态,会出现多个真相源;如果依赖 ThreadLocal,则异步线程无法可靠继承。
+
+| 组件 | 为什么设计 | 作用 | 明确边界 |
+|---|---|---|---|
+| `DiagnosisHarnessCore` | 所有执行边界需要同一套 active / budget / terminal 规则 | 创建 RunContext,执行模型/Tool/Token/bytes 门禁,处理取消和终态 | 不持久化,不调用 Agent/Tool,不维护全局 Run Map |
+| `RunContext` | Run 身份和状态句柄必须一起显式传播 | 固定 sessionId、runId、deadline 及 per-run handles | record 结构不可变,不代表内部计数不变化 |
+| `RunLifecycle` | 成功、失败、取消可能竞态到达 | 原子 `compareAndSet` 实现 first-terminal-wins | 不映射公开 ReleaseOutcome |
+| `RunCancellation` | 取消原因和回调只能被第一个请求确定 | first-reason-wins,并通知 lifecycle/资源回调 | 不承诺强杀同步 Provider 请求 |
+| `RunBudget` | 多维预算必须原子地先检查再计数 | 模型、Tool、单 Tool、输入/输出/总 Token 和 bytes 计量 | 不判断信息是否有价值 |
+| `RunCapacityCounter` | bytes 可能由并发边界累计 | CAS 方式维护 Run 总容量 | 不负责字段级截断策略 |
+
+### 类型清单
+
+| 类型 | 分类与功能 |
+|---|---|
+| `RunBudgetLimits`、`RunBudgetUsage` | 预算配置和值快照;区分限制与已使用量 |
+| `BudgetKind`、`BudgetExceededException` | 明确指出耗尽的是模型、Tool、Token 还是 bytes |
+| `RunState`、`RunTermination` | 内存执行状态与不可变终止快照 |
+| `RunCancellationReason` | 客户端断开、用户请求、deadline、预算和内部失败的取消分类 |
+| `RunAbortedException` | 将已经确定的 RunTermination 穿过深层调用栈,不丢失终态 |
+
+## 5. Agent:框架 ReAct 与 Harness 的接合层
+
+### 为什么需要
+
+业务需要框架原生 Tool Calling 和 ReAct loop,但框架默认并不知道项目的 RunContext、预算、审计、Tool 双视图和停止协议。接合层的目标是“拦截边界”,不是重新实现 Agent 循环。
+
+| 组件 | 为什么设计 | 作用 | 明确边界 |
+|---|---|---|---|
+| `DiagnosisAgentFactory` | 每个 Run 的 interceptor 和 metadata 不同 | 为当前 Run 创建 ReactAgent,注册 Tool callback、Prompt、Hook 和 interceptor | 不缓存跨 Run Agent 状态 |
+| `DiagnosisAgentUseCase` | 框架输入输出是字符串,业务要求严格 Draft 和 bytes 边界 | 序列化输入、显式注入 Run metadata、调用 Agent、严格解析 Draft、映射受控停止 | 不执行证据和语义校验 |
+| `HarnessModelInterceptor` | 每一轮 ReAct 模型调用都必须进入预算和 Token 账本 | 调用前 reserve,调用后记录 Provider usage 并再次检查 active | 不重试模型 |
+| `HarnessToolInterceptor` | 模型 Tool Call 中混有 Harness 进展协议和业务参数 | 校验 Envelope、上一轮增益、重复/饱和、调用 Tool、投影 observation、交付 STOP_REQUIRED | 不执行 backend,不复制 ToolBoundary 预算 |
+| `HarnessEvidenceTools` | Tool schema 必须由服务端原生注册,且业务 Tool 可选启用 | 注册 RAG/log/MySQL callback,严格解析通用 Envelope,桥接 Adapter | Prompt 不写死 Tool schema;未配置 MySQL 时不暴露死 Tool |
+| `ToolResultViewProjector` | canonical agent_result 仍含 Harness 控制字段 | 生成 Control View 和白名单 Model Observation | 不读取 raw response,不判断根因 |
+
+### 类型清单
+
+| 类型 | 分类与功能 |
+|---|---|
+| `DiagnosisAgentInput`、`DiagnosisAgentExecution` | Agent 用例输入,以及 Draft/ProgressSnapshot/stop reason 的执行结果 |
+| `DiagnosisAgentLimits` | query、previous turn、总输入和 Draft 的 UTF-8 bytes 上限 |
+| `DiagnosisAgentPrompt`、`DiagnosisDraftOutputSchema` | 最小职责 Prompt 与严格结构化输出 Schema |
+| `EvidenceToolInvoker` | Agent 层到具体 Adapter 的函数端口 |
+| `ParsedAgentToolCall` | 解包后的 previous observation、typed business input 和 JSON 参数 |
+| `ToolControlView` | Harness 消费的 evidence status、count、scope 等控制视图 |
+| `DiagnosisAgentLimitException` | Agent 输入或输出越界 |
+| `DiagnosisAgentOutputException` | 空、非法 JSON、Schema 不合格 Draft,并可携带安全 ProgressSnapshot |
+| `DiagnosisCollectionStoppedException` | STOP_REQUIRED 后仍请求 Tool 时,把受控停止穿出框架 loop |
+
+## 6. Progress:从资源上限到正常收敛
+
+### 为什么需要
+
+预算只能阻止无限消耗,不能识别“连续查询没有推进诊断”。Progress 子系统只保存 Run 内最小控制状态,不复制完整证据。
+
+| 组件 | 为什么设计 | 作用 | 明确边界 |
+|---|---|---|---|
+| `DiagnosisProgressTracker` | 连续无增益、待评价调用和协议错误需要线程安全单一所有者 | 记录 completed scope、pending evaluation、NO_GAIN、协议错误、饱和和一次停止指令 | 不保存 raw/agent_result,不判断非空内容的业务价值 |
+| `ToolScopeNormalizer` | 字段顺序或无关格式不应绕过重复检测 | 将各 Tool typed input 规范化为稳定 scope | 首版不做自然语言语义去重 |
+| `DiagnosisProgressProjector` | 受控停止或非法 Draft 后仍需安全说明已检查内容 | 按 Tracker identity 回读 canonical READY 记录,生成有界事实、来源和限制 | 无法验真的记录直接排除,不输出 raw |
+| `DiagnosisProgressProjection` | Agent 用例不应依赖具体 Redis projector | 定义 Run 到安全快照的端口,并提供 empty 实现 | 不决定 Fallback 类型 |
+
+### 类型清单
+
+| 类型 | 分类与功能 |
+|---|---|
+| `InformationGain` | 仅 `GAINED / NO_GAIN`,避免引入含混质量等级 |
+| `DiagnosisCollectionState` | `COLLECTING / SATURATED`,只描述信息收集状态 |
+| `DiagnosisStopReason` | 区分信息饱和、预算限制和进展协议错误 |
+| `PreviousObservation` | 模型在下一次 Tool Call 回传上一轮 `tool_call_id + information_gain` |
+| `CompletedToolCall`、`ToolScopeIdentity` | 保存已完成调用的 identity 和规范化 scope,不保存 payload |
+| `DiagnosisProgressSnapshotState` | Tracker 的内部控制快照,含计数、pending ID 和停止指令状态 |
+| `DiagnosisProgressSnapshot` | Release 可消费的安全快照,含 verified sources、observed facts 和 limitations |
+| `ProgressProtocolViolationType`、`ProgressProtocolViolationException` | 缺字段、乱序评价、意外评价和非法 Envelope 的稳定分类 |
+
+## 7. Tool:执行、真相、投影与后端安全
+
+Tool 是文件最多的职责域,但可以按四层理解。
+
+### 7.1 Boundary:所有 Tool 共用的确定性入口
+
+| 组件 | 为什么设计 | 作用 | 明确边界 |
+|---|---|---|---|
+| `ToolBoundary` | 每个 Adapter 自己实现授权、预算、store 和 audit 会产生漂移 | 统一 preflight、active、read-only、Tool budget、bytes、canonical 状态迁移和 audit | 不理解 Tool 业务内容;不做信息增益判断 |
+| `ToolCallRequestEnvelope` | 调用必须同时证明 Run、ID、Tool、参数、授权和只读意图 | Boundary 的内部调用信封 | 不等同于模型侧 progress Envelope |
+| `ToolExecutor` | Boundary 不依赖具体 backend | raw 执行函数端口 | 不投影结果 |
+| `ToolResultProjector` | raw 到 canonical agent_result 的逻辑因 Tool 而异 | 标准化、限制、计算客观 evidence status | 不判断结论支持度 |
+| `ToolBoundaryResult` | 只允许 READY 或 ERROR 离开 Boundary | 向上返回 ID、状态、agent result、evidence status 或稳定错误码 | PROJECTING 不对外暴露 |
+| `ProjectedToolResult` | projector 同时返回有界 agent result 与客观状态 | Boundary/store 的中间值 | 不是最终 Model Observation |
+| `ToolBoundaryErrorCode` | 不能把内部异常正文交给 Agent | 固定非法 ID、Run mismatch、未授权、非只读、超限、执行/投影/store 等错误 | 不包含敏感原因 |
+
+### 7.2 Canonical Store:TTL 内的完整 Tool 真相
+
+| 组件 | 为什么设计 | 作用 | 明确边界 |
+|---|---|---|---|
+| `CanonicalInvocationStore` | Guard 需要独立于 Agent 上下文读取原始调用真相 | 定义 begin/find/markReady/markError 状态端口 | 不负责长期审计 |
+| `RedisCanonicalInvocationStore` | 完整 request/raw 有敏感性和容量,适合短期 TTL 存储 | 原子创建,保持剩余 TTL 的状态更新,读取不续期 | 只有该 Adapter 访问 Redis |
+| `CanonicalToolInvocation` | request、raw、agent_result 和两个状态必须形成合法组合 | 封装 PROJECTING -> READY/ERROR 转换及 Run 可引用判断 | ERROR/PROJECTING 不可被 EvidenceGuard 引用 |
+| `CanonicalInvocationLimits` | Redis record、raw 候选和 agent result 需要独立硬限制 | 统一 UTF-8 bytes 校验 | 不执行截断 |
+| `ToolCallKeyFactory` | Key 要隔离 Run 且保留框架 ID | 校验安全 segment,生成 prefix/runId/toolCallId | 不生成或改写 tool_call_id |
+| `DuplicateInvocationException`、`InvocationStateException`、`CanonicalStoreException`、`ResultTooLargeException` | Store 失败需要可分类而不是字符串猜测 | 区分重复、非法迁移、基础设施错误和容量错误 | 最终对 Agent 仍映射为安全错误码 |
+
+### 7.3 Adapter 与 Projector:隔离业务后端
+
+| 组件 | 为什么设计 | 作用 | 明确边界 |
+|---|---|---|---|
+| `RagToolAdapter` | 检索实现会演进,但 Agent schema 和 Boundary 不应随之变化 | 解析 RAG request,经 Boundary 调 backend 和 `RagResultProjector` | 不把检索轨迹直接给模型 |
+| `QueryLogsToolAdapter` | 现有日志 Tool 的返回和时间语义需要规范化 | 校验范围、桥接 backend、投影日志事件 | 0 命中是 `NO_EVIDENCE`,不是技术失败 |
+| `MysqlToolAdapter` | LLM 生成 SQL 必须经过授权和只读沙箱 | 解析 request、先校验 SQL,再经 Boundary 执行和投影 | 未配置数据源时 Tool 不注册 |
+| `RagResultProjector` | 上游 evidence、score、relevance 表达不稳定 | 输出有界证据,并保留粗粒度 `relevance_level` | 非空/REFERENCE 不等于支持根因 |
+| `QueryLogsResultProjector` | raw 日志不可直接进入上下文 | 生成有界 events、pattern、scope 和截断标记 | 不泄露无限日志正文 |
+| `MysqlResultProjector` | JDBC rows 和元数据需要稳定 Agent contract | 限制行列、单元格和 bytes,输出 rows/columns/scope | 不执行 SQL 安全判断 |
+| `ToolProjectionLimits`、`MysqlToolLimits` | 各投影边界必须集中、可测试 | 配置证据数、文本、行列和结果上限 | 不改变 Run 总 bytes 预算 |
+
+### 7.4 MySQL 只读沙箱
+
+| 组件 | 为什么设计 | 作用 | 明确边界 |
+|---|---|---|---|
+| `MysqlSqlValidator` | `readOnly=true` 声明不能证明 SQL 安全 | 解析并限制单条 SELECT、数据源、schema/table/column、limit 等 | 不执行查询 |
+| `MysqlDataSourceDefinition` | 连接存在不代表 Agent 可访问所有表列 | 保存逻辑数据源和 allowlist | 不携带密码 |
+| `MysqlQueryPlan` | 校验后的内容不应在 Executor 再解析原始请求 | 固定已批准数据源、SQL 和限制 | 只能由 Validator 产生 |
+| `MysqlReadOnlyExecutor` / `JdbcMysqlReadOnlyExecutor` | JDBC 细节与 Harness Boundary 解耦 | 在只读连接、timeout 和 row limit 下执行 plan | 不接收未经验证的 request |
+| `MysqlRawResult` | JDBC 原始但结构化的执行结果 | 携带 columns、rows、truncated、duration | 仍需 Projector 后才能进入 canonical agent_result |
+| `MysqlSecurityException` | 安全拒绝必须与基础设施错误区分 | 表达非法 SQL、未授权对象等 | 不向 Agent泄露详细策略 |
+
+### 7.5 Tool contract 完整清单
+
+| 类型 | 设计原因与功能 |
+|---|---|
+| `AgentToolContracts` | Tool 名、描述和服务端注册契约的唯一常量源,防止 Prompt/代码漂移 |
+| `RagToolCall`、`QueryLogsToolCall`、`MysqlToolCall` | 模型侧统一 Envelope:`previous_observation + input` |
+| `RagToolRequest`、`QueryLogsRequest`、`MysqlToolRequest` | 业务 Tool 的 typed input;Interceptor 解包后仍保持原业务协议 |
+| `RagToolResult`、`QueryLogsToolResult`、`MysqlToolResult` | canonical agent-facing 标准结果,不等同于 raw backend response |
+| `RagEvidence`、`SourceDocument` | 有界知识证据及文档身份 |
+| `RagRelevanceLevel` | `PRECISE / HIGHLY_RELEVANT / REFERENCE` 等客观检索相关度,不是诊断置信度 |
+| `LogQueryScope`、`LogEvent`、`LogPattern` | 日志查询实际范围、事件和聚合模式 |
+| `LogSourceKind`、`LogTopic` | 限制日志来源和主题的可选集合 |
+| `ToolContractCollections` | 对 contract 集合做 defensive copy 和空值规范化 |
+
+## 8. Guard:把两个验证命题分开
+
+### 8.1 Evidence Guard
+
+| 组件 | 为什么设计 | 作用 | 明确边界 |
+|---|---|---|---|
+| `EvidenceGuard` | 模型不能被信任去验证自己引用的 ID 和 Run 归属 | 严格验证 Draft、analysis ID、canonical READY、当前 Run 所有权、evidence status 和引用闭包 | 不调用模型,不判断结论语义是否成立 |
+| `EvidenceGuardResult` | 校验只能是 verified snapshot 或 violations | 阻止半有效结果继续发布 | 不包含原始 Draft 修复逻辑 |
+| `VerifiedEvidenceSnapshot` | SemanticGuard 只能看到已验真的证据投影 | 汇总 verified analyses 和 sources | 不包含 raw Tool payload |
+| `VerifiedAnalysisEvidence`、`VerifiedEvidence` | 保持 analysis 到证据的归属关系 | 提供来源、scope、excerpt 等安全证据 | 不提升为业务结论 |
+| `EvidenceViolation`、`EvidenceViolationCode` | Repair 和 Fallback 需要稳定失败原因 | 标识缺 analysis、未知调用、Run mismatch、非 READY、引用不闭合等 | 不保存内部异常栈 |
+
+### 8.2 Semantic Guard 与模型调用边界
+
+| 组件 | 为什么设计 | 作用 | 明确边界 |
+|---|---|---|---|
+| `SemanticGuard` | 引用真实仍可能无法支持结论 | 用隔离单轮调用输出 `SUPPORTED / UNSUPPORTED`,严格解析并有限 retry | 无 Tool、无记忆、不访问 Redis、不改写 Draft |
+| `GuardModelCall` | Router、单轮回答、Repair、Semantic 都需一致的模型预算、timeout 和 bytes 控制 | 在线程池中执行单轮 ChatModel,记录 usage,超时取消 future | 不决定业务 retry policy |
+| `SemanticDraftView` | Guard/Repair 只应比较用户可见语义 | 从 Draft 提取稳定语义视图,并判断修复前后是否一致 | 不包含引用实现细节 |
+| `SemanticGuardInput`、`SemanticGuardDecision` | 固定 Guard 输入和带 verdict 的输出 | 只传 query、Draft view 和 verified snapshot | 不传 Prompt 历史或 raw Tool response |
+| `SemanticGuardLimits`、`SemanticGuardPrompt` | 输入、输出、单次/总 timeout 和判定职责可测试 | 限定一次语义审查的成本与 Prompt | 不暴露给 Diagnosis Agent |
+| `GuardModelCallException` | 单轮模型失败要按 timeout、transport、parse、schema 分类 | 为 Harness retry 提供类型化信号 | 不直接映射用户内容 |
+
+## 9. Release:唯一公开决策点
+
+### 为什么需要
+
+如果 Agent、Guard、Application 都可以各自构造结果,同一个失败会出现不同用户语义,迟到内容也可能绕过安全校验。Release 必须集中回答一个问题:当前 Run 有哪些内容可以公开?
+
+| 组件 | 为什么设计 | 作用 | 明确边界 |
+|---|---|---|---|
+| `DiagnosisReleaseUseCase` | Draft、停止、Guard 和 Repair 的组合分支必须只有一个所有者 | 处理有结论、无结论、受控停止、非法 Draft;协调 Guard/Repair/Semantic;返回成功或 Fallback | 不持久化、不发送 SSE、不写新结论 |
+| `EvidenceRepair` | 引用或结构小错不应总是丢掉语义正确的 Draft | 单轮修复引用,严格解析,并用 `SemanticDraftView` 保证用户语义不变 | 只 attempt 一次;不新增事实、不改结论 |
+| `SafeFallbackFactory` | 失败文案若交给模型生成会再次引入幻觉 | 确定性构造 evidence failed、semantic unsupported/unavailable、insufficient evidence、missing context | 只使用已验真事实和有界问题码 |
+| `DiagnosisReleaseResult` | 下游不能同时收到 Draft 和 Fallback | 类型化承载 outcome、Draft、verified evidence 或 SafeFallback | 不等同于 RunState |
+| `EvidenceRepairLimits`、`EvidenceRepairPrompt` | Repair 的成本与职责必须比 Diagnosis Agent 更窄 | 限制输入/输出/timeout,固定只修引用的指令 | 不允许 Tool Calling |
+
+## 10. Retry:显式、类型化、可审计的 attempt
+
+### 为什么需要
+
+重试会改变成本、延迟和副作用,必须是调用者的有意识决策。统一 Executor 负责循环,但具体组件持有自己的 Policy。
+
+| 类型 | 设计原因与功能 |
+|---|---|
+| `HarnessRetryExecutor` | 在每个 attempt 前检查 Run active,统一记录成功/失败,并绝不吞掉取消和预算耗尽 |
+| `HarnessRetryPolicies` | 集中定义 Router/SemanticGuard 最多两次,其余一次的严格策略 |
+| `RetryPolicy` | `maxAttempts + retryable failures` 的不可变值,避免布尔 `retry=true` |
+| `RetryFailure` | timeout、transport、parse、schema、invalid output、cancel、budget 等稳定分类 |
+| `RetryAttempt` | 记录 attempt 序号、是否成功和失败类型,供 Trace 使用 |
+| `RetryOperation`、`RetryFailureClassifier` | 将执行和异常分类作为端口注入,Executor 不依赖具体模型组件 |
+| `RetryExecutionException` | 重试终止时保留最终 attempt 和失败分类 |
+
+## 11. Audit:记录控制事实,而不是复制业务正文
+
+### 为什么需要
+
+诊断系统需要回答“为什么停止、调用了什么、Token 是否对账、发布为何降级”,但普通观察面不应长期保存 Prompt、SQL、日志 query、raw response 或 reasoning。
+
+| 组件 | 为什么设计 | 作用 | 明确边界 |
+|---|---|---|---|
+| `DiagnosisTraceRecorder` / `JpaDiagnosisTraceRecorder` | 所有阶段需要同一 exact-run timeline | 按 Run 分配 sequence,追加安全事件;失败 best-effort | 不改变业务结果,不保存敏感正文 |
+| `TraceAuditEvents` | 各组件手写 details 容易字段漂移或泄露 | 集中构造 run/routing/model/tool/progress/guard/release 事件 | 只接受有界、安全字段 |
+| `DiagnosisTraceAuditEvent` | Recorder 与业务组件解耦 | 统一事件 identity、phase、type、status、details | 不是领域事件总线 |
+| `ToolInvocationAuditSink` / `JpaToolInvocationAuditSink` | canonical raw 不能长期保存,但调用元数据要留存 | 保存 exact Run、Tool ID、状态、耗时、bytes 和有界 enrichments | 不保存完整 request/raw/agent result |
+| `RagLookupAuditEnricher` | RAG 需要有限的质量诊断字段 | 从受限输入提取 step/query/relevance 等安全摘要 | 不打印完整 rewritten/raw query |
+| `HarnessAgentAuditHook` | 框架每轮 Agent 模型步骤需关联数据库 step 和 Provider reasoning 可用性 | 写 AgentStep metadata,并把 reasoning/assistant text 放独立受限存储 | reasoning 不进普通 Timeline、Guard 或下一轮上下文 |
+| `AgentStepAuditTracker` | Tool audit 需要关联当前 Agent step,但不能使用 ThreadLocal | 按 runId 显式绑定/查询/清理 stepId | 不保存 Step 实体 |
+| `ModelCallAuditor` / `ModelCallLedger` | 所有模型入口都要按组件、轮次对账 Token | begin call、记录 Provider usage、写 MODEL_TOKEN_USAGE、生成 Run reconciliation | Usage 缺失时标 unavailable,不估算 |
+| `RunConclusionExtractor` | Trace/DB 常需直接读取安全发布结论 | 从 public JSON 提取 conclusion | 不读取 Provider reasoning |
+
+### Audit 类型清单
+
+| 类型 | 分类与功能 |
+|---|---|
+| `ModelCallComponent` | Router、System、Knowledge、Diagnosis、Repair、Semantic 的统一组件枚举,并映射 Trace phase |
+| `ToolInvocationAuditEvent` | durable Tool metadata 事件 |
+| `TracePhase`、`TraceEventType`、`TraceEventStatus` | Timeline 的阶段、事件和结果词汇表 |
+
+## 12. Contract:跨组件唯一语言
+
+### 为什么需要
+
+Harness 横跨模型 JSON、Java 对象、Redis、JPA 和 SSE。若状态以自由字符串在各层重复定义,`SUCCESS`、`READY`、`EVIDENCE_FOUND` 很容易被混为一谈。Contract 包将不同维度保持正交。
+
+| 类型 | 为什么存在与表达什么 |
+|---|---|
+| `DiagnosisDraft` | Diagnosis Agent 唯一结构化输出;analysis 绑定 Tool Call 引用,允许 `conclusion=null` |
+| `KnowledgeAnswerDraft` | Knowledge Query 单轮模型输出,答案项绑定文档来源 |
+| `PublishedResult` | 只有成功、安全的诊断结果可持久化为下一轮候选 |
+| `PreviousTurn` | PublishedResult 的有界历史投影,不是完整会话记录 |
+| `SafeFallback` | 确定性公开降级结构,包含 verified sources、observed facts、limitations、next steps 和 validation issues |
+| `SourceDocument` | 知识回答中的文档身份与来源 |
+| `IntentType` | `SYSTEM_CHAT / KNOWLEDGE_QUERY / DIAGNOSIS` 路由结果 |
+| `InvocationStatus` | Tool invocation 生命周期:`PROJECTING / READY / ERROR` |
+| `EvidenceStatus` | Tool 客观结果:`EVIDENCE_FOUND / NO_EVIDENCE / ERROR` |
+| `SemanticVerdict` | 最终推论支持度:`SUPPORTED / UNSUPPORTED` |
+| `ReleaseOutcome` | Harness 发布结果:`SUCCESS / FALLBACK / FAILED / CANCELLED` |
+| `SseOutcome` | 当前未被运行时消费的遗留枚举;公开 `done` 实际使用 `ReleaseOutcome`,不要把它作为 SSE 协议真理源 |
+| `FallbackType` | evidence validation、semantic、insufficient evidence、missing context 等降级原因 |
+| `AnalysisKind` | 区分正向证据分析与限定范围的负向观察 |
+| `ContractCollections` | 对公共 contract 集合做 defensive copy、去空和不可变处理 |
+
+## 13. 配置装配:组件如何真正连起来
+
+`HarnessChatConfiguration` 不在 `harness` 包内,但它是运行时组件图的 Composition Root:
+
+- 构造两个有界线程池:Chat worker 与 Harness model executor;
+- 从 `ChatHarnessProperties` 创建 Run、预算、停止阈值和各模型调用 limits;
+- 装配 Redis canonical store、ToolBoundary、三类 Adapter/Projector;
+- 仅在存在有效逻辑数据源时注册 `query_mysql`;
+- 为每个 Run 创建带 Model/Tool interceptor 和 Audit Hook 的 Agent;
+- 装配 Guard、Repair、Release、Router、三个执行分支和顶层 Application UseCase。
+
+它存在的原因是让所有限制和替换点在一个地方可见。组件内部不应自行读取 Spring 配置或寻找全局 Bean,否则 focused test 很难证明其边界。
+
+## 14. 用调用链快速定位组件
+
+| 想回答的问题 | 首先阅读 | 接着阅读 |
+|---|---|---|
+| 一次请求如何创建并结束 Run | `ChatApplicationUseCase` | `DiagnosisHarnessCore`、`JpaChatRunStore` |
+| ReAct 每轮如何计预算 | `DiagnosisAgentFactory` | `HarnessModelInterceptor`、`ModelCallAuditor` |
+| Tool 为什么被拒绝 | `HarnessToolInterceptor` | `DiagnosisProgressTracker`、`ToolBoundary` |
+| Tool 结果为何没有原样给模型 | `ToolBoundary` | 具体 ResultProjector、`ToolResultViewProjector` |
+| 一条 evidence_ref 如何验真 | `EvidenceGuard` | `CanonicalToolInvocation`、具体 ToolResult contract |
+| 为什么最终是 FALLBACK | `DiagnosisReleaseUseCase` | `SafeFallbackFactory`、Trace 的 RELEASE 事件 |
+| 为什么诊断停止继续查 | `DiagnosisProgressTracker` | Tool progress / rejection / collection stop Trace |
+| Token 为什么对不上 | `ModelCallAuditor` | `ModelCallLedger`、`RUN_FINISHED` reconciliation |
+
+## 15. 组件边界自检
+
+未来新增能力时,可以用下面的问题判断放置位置:
+
+1. 它是在判断业务根因吗?应留在 Diagnosis Agent,而不是 Core/Tool/Guard。
+2. 它能由代码机械证明吗?应放在 Boundary、Tracker 或 EvidenceGuard。
+3. 它需要完整 Tool 真相吗?读取 canonical store,不要从 Agent observation 反推。
+4. 它要决定公开内容吗?只能进入 Release,不要在 Application 或 Guard 私自构造。
+5. 它是短期验真数据还是长期运营元数据?前者 canonical,后者 metadata audit。
+6. 它会再次调用模型吗?必须进入 Core budget、ModelCallAuditor、timeout 和显式 retry policy。
+7. 它引入了新的状态吗?先确认是否只是 error code、stop reason 或 fallback reason,避免再造全局生命周期。
diff --git a/mvp/engineering/harness/Harness设计-非确定性Agent的确定性控制边界.md b/mvp/engineering/harness/Harness设计-非确定性Agent的确定性控制边界.md
new file mode 100644
index 0000000..441eef1
--- /dev/null
+++ b/mvp/engineering/harness/Harness设计-非确定性Agent的确定性控制边界.md
@@ -0,0 +1,503 @@
+# Harness 设计:非确定性 Agent 的确定性控制边界
+
+**更新日期**:2026-07-29
+**适用范围**:当前 MVP Chat / Diagnosis Harness
+**代码基线**:`com.superbiz.agent.harness`
+**术语与状态**:[CONTEXT.md](CONTEXT.md) · [Harness生命周期与状态.md](Harness生命周期与状态.md)
+**组件索引**:[Harness组件全景-职责-设计原因与边界.md](Harness组件全景-职责-设计原因与边界.md)
+
+## 1. 结论先行
+
+这个系统真正需要解决的,不是“怎样让模型多调用几个 Tool”,而是:
+
+> 当业务推理由非确定性模型完成时,怎样保证每一次执行仍然有身份、有边界、有停止条件、有证据闭包,并且只发布系统能够负责的内容。
+
+当前 Harness 给出的答案是职责分治:
+
+- Diagnosis Agent 负责提出假设、选择 Tool、理解结果和撰写 Draft。
+- Harness 负责所有必须确定的事情:Run 生命周期、预算、取消、Tool 授权、结果隔离、停止、验真、发布和审计。
+- Tool 只报告客观执行结果,不宣称业务根因。
+- Guard 不重新做诊断,只回答受限的验证问题。
+- Release 是唯一对外发布决策点,只能发布原始安全 Draft 或确定性 SafeFallback。
+
+这不是一个新的工作流引擎。Harness 不复制 ReAct 循环,不维护 Planner / Executor / Composer 图,也不替模型判断根因。它包围 ReAct 的不确定部分,在所有外部副作用和最终发布点建立确定性门禁。
+
+## 2. 根问题:模型能推理,但系统必须能够负责
+
+### 2.1 旧架构把“推理角色”当成了“安全边界”
+
+旧方案使用 Planner、Executor、Verifier、Composer 等多个 Agent 串联。表面上每个角色各司其职,实际上每个角色都拥有一部分 Prompt、模型调用、Schema 转换、重试和 Fallback 逻辑。结果是:
+
+1. 一次诊断的控制权分散在多个模型角色和业务 Service 中。
+2. 同一种错误可能被不同层重试、改写或吞掉。
+3. Verifier 既检查引用,又判断语义,还可能重写报告,成为第二个诊断者。
+4. Tool 原始结果、Agent 上下文和长期审计没有明确的数据边界。
+5. Run 身份依赖 ThreadLocal 传播,跨线程后无法证明一次调用属于哪个 Run。
+
+```mermaid
+flowchart LR
+ U["用户请求"] --> P["Planner Agent"]
+ P --> E["Executor Agent"]
+ E --> T["Tool / raw result"]
+ E --> V["Verifier Agent"]
+ V --> C["Composer Agent"]
+ C --> O["公开结果"]
+
+ P -.-> X1["独立 Prompt / retry / schema"]
+ E -.-> X2["独立 Prompt / retry / fallback"]
+ V -.-> X3["验真、语义判断、改写混合"]
+ C -.-> X4["再次生成用户事实"]
+ T -.-> X5["raw、trace、正文边界不清"]
+
+ H["ThreadLocal context"] -.-> P
+ H -.-> E
+ H -.-> V
+
+ classDef risk fill:#fff1f0,stroke:#cf1322,color:#5c0011;
+ class X1,X2,X3,X4,X5,H risk;
+```
+
+问题并不在于“Agent 数量多”本身,而在于控制职责没有单一所有者。只要预算、重试、证据和发布仍分散,多 Agent 换成 Graph 也不会自然变得可靠。
+
+### 2.2 单 Agent 仍然不等于受控系统
+
+把多 Agent 合并成一个 ReAct Agent,只消除了重复推理链,并没有自动解决以下问题:
+
+- 模型可能无限改写相似查询;
+- SDK 可能在 Harness 之外自动重试;
+- Tool 可能返回过大、敏感或不可引用的原始数据;
+- 模型可以引用其他 Run 或不存在的 Tool Call;
+- 证据引用真实,不代表结论被证据支持;
+- 客户端断开后,迟到的模型结果仍可能覆盖终态;
+- “没有足够证据”可能被错误地发布为技术失败。
+
+所以重构的核心不是“单 Agent”,而是“单 Agent + 确定性 Harness”。
+
+## 3. 设计约束与不变量
+
+以下不变量是组件拆分的依据,不是实现后的总结。
+
+| 不变量 | 系统含义 | 由谁保证 |
+|---|---|---|
+| 一个 Run 只有一个终态 | 成功、失败、取消、超时、预算耗尽不能相互覆盖 | `RunLifecycle` 的 first-terminal-wins |
+| 所有边界显式携带 Run 身份 | 不依赖线程绑定的隐式上下文 | `RunContext` 和 framework `tool_call_id` |
+| 所有模型与 Tool 消耗可计量 | SDK 隐式 retry 不能绕过预算 | Core、Model/Tool interceptor、Harness retry |
+| Tool raw 不直接进入模型 | 内部数据、体积和控制字段不能污染上下文 | ToolBoundary、canonical store、双视图投影 |
+| 只有当前 Run 的 READY 调用可被引用 | 防止伪造、串 Run 和引用失败调用 | EvidenceGuard |
+| 引用真实性与结论支持度分开 | 确定性规则和语义判断不互相冒充 | EvidenceGuard、SemanticGuard |
+| Guard 不写报告 | 防止验证器成为第二个业务 Agent | Release Policy |
+| 停止权属于 Harness | Prompt 建议不能替代系统保证 | ProgressTracker、预算和 interceptor |
+| 没有根因是合法业务结果 | 证据不足不应伪装成内部故障 | `conclusion=null`、ProgressSnapshot、SafeFallback |
+| 普通审计不保存敏感正文 | 可观测性不能以泄露 Prompt、raw、reasoning 为代价 | metadata-only audit、reasoning 独立存储 |
+
+## 4. 当前总体架构
+
+```mermaid
+flowchart TB
+ subgraph Entry["应用入口与 Run 所有权"]
+ APP["ChatApplicationUseCase"]
+ ROUTER["IntentRouter"]
+ EXEC["DiagnosisChatExecutor"]
+ STORE["ChatRunStore"]
+ end
+
+ subgraph Harness["确定性 Harness 边界"]
+ CORE["Core
context / lifecycle / budget / cancel"]
+ MI["Model Interceptor
调用预算与 Token"]
+ TI["Tool Interceptor
协议、重复、停止"]
+ TB["ToolBoundary
授权、执行、canonical"]
+ PROGRESS["ProgressTracker
信息增益与饱和"]
+ EG["EvidenceGuard
引用真实性"]
+ SG["SemanticGuard
结论支持度"]
+ RELEASE["Release Policy
SUCCESS / FALLBACK"]
+ AUDIT["Audit / Trace
metadata-only"]
+ end
+
+ subgraph Nondeterministic["非确定性区域"]
+ AGENT["Diagnosis ReAct Agent"]
+ MODEL["Chat Model"]
+ end
+
+ subgraph Evidence["证据执行与存储"]
+ ADAPTER["RAG / Logs / MySQL Adapter"]
+ BACKEND["Evidence Backend"]
+ CANON["Redis Canonical Invocation"]
+ META["MySQL Durable Metadata"]
+ end
+
+ APP --> CORE
+ APP --> ROUTER
+ ROUTER --> MODEL
+ APP --> EXEC
+ EXEC --> AGENT
+ AGENT --> MI --> MODEL
+ AGENT --> TI
+ TI --> PROGRESS
+ TI --> TB --> ADAPTER --> BACKEND
+ TB --> CANON
+ TB --> META
+ TB --> TI --> AGENT
+ EXEC --> EG --> SG --> RELEASE
+ PROGRESS --> RELEASE
+ RELEASE --> APP --> STORE
+ CORE -.-> MI
+ CORE -.-> TI
+ CORE -.-> TB
+ CORE -.-> EG
+ CORE -.-> SG
+ APP -.-> AUDIT
+ MI -.-> AUDIT
+ TI -.-> AUDIT
+ TB -.-> AUDIT
+ EG -.-> AUDIT
+ SG -.-> AUDIT
+ RELEASE -.-> AUDIT
+```
+
+图中的关键边界有三条:
+
+1. **Agent 之前**:Application 创建 Run,Core 固定身份、预算和终止语义。
+2. **Tool 前后**:Interceptor 管控制协议,ToolBoundary 管执行与真相,Projector 管模型可见内容。
+3. **发布之前**:EvidenceGuard 验引用,SemanticGuard 验推论,Release 决定唯一公开内容。
+
+## 5. 关键决策及设计推导
+
+### 5.1 决策一:只保留一个拥有 Tool loop 的 Diagnosis Agent
+
+**问题**:多角色 Agent 把同一份业务上下文在多个模型之间传递,每一跳都可能增加信息损失、重试和幻觉面。
+
+**候选方案**:
+
+| 方案 | 优点 | 主要问题 |
+|---|---|---|
+| 保留 Planner / Executor / Verifier / Composer | 角色概念直观 | 控制权分散;多套 Prompt 和重试;Verifier/Composer 会产生新事实 |
+| 外层自建 ReAct / StateGraph | 流程显式 | 与框架 ReAct 重复;状态和异常路径翻倍 |
+| 单 ReAct Agent + Harness | 推理上下文连续;控制边界集中 | 对 Harness 的契约和门禁设计要求更高 |
+
+**决策**:Diagnosis Agent 是唯一业务推理者和 Draft 作者,使用框架 `ReactAgent` 自己完成 Thought / Action / Observation。项目不在外层复制循环。
+
+**代价**:单 Agent 并不能通过“角色互审”获得表面冗余,因此必须把可机械验证的安全要求下沉到 Guard,把语义审查收缩成隔离的单轮判断。
+
+### 5.2 决策二:RunContext 显式传播,结构不可变、状态句柄线程安全
+
+**问题**:旧 ThreadLocal 可以在同步调用中工作,但异步 Tool、线程池和取消回调跨线程后,调用方无法证明读到的是当前 Run 的上下文。
+
+**容易走错的方案**:把所有字段都做成不可变值对象。这样 deadline 和 identity 很干净,但预算、取消和终态不得不在外部另建全局 Map,反而形成第二真相源。
+
+**决策**:`RunContext` 本身是 record,固定 `sessionId / runId / deadline` 和各状态句柄引用;预算、取消、生命周期、模型账本和进展 Tracker 在各自线程安全对象内变化。
+
+```mermaid
+flowchart LR
+ RC["RunContext
结构不可变"] --> ID["sessionId / runId / deadline"]
+ RC --> C["RunCancellation
first-reason-wins"]
+ RC --> B["RunBudget
同步复合计数"]
+ RC --> L["RunLifecycle
first-terminal-wins"]
+ RC --> M["ModelCallLedger
组件/轮次账本"]
+ RC --> P["ProgressTracker
Run 内进展"]
+```
+
+**为什么不是全局 Run Registry**:Harness 只控制当前调用,不承担 Run 查询和持久化;持久化真相仍由 `diagnosis_run` 所有,避免 Core 演变成工作流引擎。
+
+### 5.3 决策三:关闭 SDK 隐式 retry,由 Harness 按失败类型拥有 retry
+
+**问题**:Spring AI 默认 `maxAttempts=10`。如果 SDK 在模型边界内部自动重试,Harness 看到的一次调用可能对应多个 Provider attempt,预算、延迟、Trace 和取消都失真。
+
+**决策**:底层 SDK retry 设为 1;仅在 Harness 中使用类型化策略:
+
+- IntentRouter:超时、传输、非法输出最多 2 次 attempt;
+- SemanticGuard:超时、传输、解析或 Schema 问题最多 2 次 attempt;
+- Diagnosis Agent、业务 Tool、EvidenceRepair:1 次,不自动重试;
+- 取消、预算耗尽、`NO_EVIDENCE`、业务拒绝:从不重试。
+
+**设计理由**:重试不是通用容错开关。只有调用者知道一次失败是否幂等、是否还在 deadline 内、是否应该再次消耗预算。
+
+**代价**:Provider 瞬时抖动更容易直接暴露,但真实 attempt 终于可计算、可审计,且不会把业务无证据误判为技术重试条件。
+
+### 5.4 决策四:Tool 使用 canonical truth 与 Agent projection 双视图
+
+**问题**:同一份 Tool 返回同时服务三个目标,而三个目标互相冲突:
+
+- 证据验真需要完整、稳定、按 Run 归属的记录;
+- Agent 只需要完成下一步推理的最小内容;
+- 长期审计应保留身份和耗时,但不能长期保存敏感 raw。
+
+**决策**:把 Tool 数据分成三层,而不是让一个 JSON 到处流转。
+
+```mermaid
+flowchart LR
+ REQ["framework tool_call_id
typed request"] --> B["ToolBoundary"]
+ B --> RAW["backend raw response"]
+ RAW --> CAN["Redis canonical invocation
request + raw + agent_result
短 TTL"]
+ RAW --> PRJ["Tool-specific projector"]
+ PRJ --> CTRL["Harness control view"]
+ PRJ --> OBS["Agent observation
白名单、有界"]
+ B --> META["Durable audit
identity / status / latency / bytes"]
+ CTRL --> STOP["重复、NO_GAIN、停止控制"]
+ OBS --> AGENT["Diagnosis Agent"]
+ CAN --> GUARD["EvidenceGuard"]
+```
+
+**关键取舍**:
+
+- 使用框架 `tool_call_id`,Harness 不生成第二套 ID;
+- `PROJECTING / READY / ERROR` 表达调用生命周期;
+- `EVIDENCE_FOUND / NO_EVIDENCE / ERROR` 表达客观结果语义;
+- raw 超限直接失败,不静默截断;Agent projection 可以有界截断,但必须标记 `truncated`;
+- Redis 是 TTL 内完整 Tool 真相源,MySQL 只做长期 metadata audit;
+- Redis 读取不续期,避免一次历史读取无限延长敏感 raw 生命周期。
+
+**代价**:同一次 Tool 调用需要 projector、store 和 audit 三套表达。但它们不是重复数据模型,而是分别回答“真实发生了什么”“模型允许看到什么”“长期允许保留什么”。
+
+### 5.5 决策五:EvidenceGuard、SemanticGuard 和 Release 三段分工
+
+**问题**:“引用是真实的”和“结论被引用支持”是两个不同命题。只做前者会放过牵强推论;都交给模型则无法确定性防止伪造 ID、跨 Run 引用或修改报告。
+
+**决策**:
+
+1. `EvidenceGuard` 使用纯代码检查 Draft 结构、analysis ID、Tool Call 当前 Run 所有权、READY 状态、evidence status 和引用闭包。
+2. 首次引用失败时,`EvidenceRepair` 只允许修复结构和引用;修复前后用户可见语义必须一致,然后重新执行 EvidenceGuard。
+3. `SemanticGuard` 只接收 query、完整 Draft 和 verified snapshot,进行无 Tool、无记忆的单轮 `SUPPORTED / UNSUPPORTED` 判断。
+4. `DiagnosisReleaseUseCase` 是唯一发布决策点。Guard 无权改写最终报告。
+
+```mermaid
+sequenceDiagram
+ participant A as Diagnosis Agent
+ participant R as Release UseCase
+ participant E as EvidenceGuard
+ participant C as Canonical Store
+ participant X as EvidenceRepair
+ participant S as SemanticGuard
+ participant P as Public Result
+
+ A->>R: DiagnosisDraft
+ R->>E: validate draft references
+ E->>C: read exact run/tool_call_id
+ C-->>E: READY agent_result
+ alt 引用或结构错误
+ E-->>R: violations
+ R->>X: 原 Draft + violations
+ X-->>R: 语义不变的修复 Draft
+ R->>E: revalidate once
+ end
+ alt 证据闭包有效
+ E-->>R: verified snapshot
+ R->>S: query + draft + snapshot
+ S-->>R: SUPPORTED / UNSUPPORTED
+ end
+ alt SUPPORTED
+ R-->>P: 原始安全 Draft
+ else 任一门禁失败
+ R-->>P: deterministic SafeFallback
+ end
+```
+
+**为什么 Repair 不能修正文义**:一旦 Repair 可以修改结论,它就成为新的报告作者;此时最终内容不再是 Diagnosis Agent 的 Draft,也无法证明修复只解决了引用问题。
+
+**为什么 SemanticGuard 不是第二个 Agent**:它没有 Tool、历史记忆或 ReAct loop,只回答一个受约束的二值审查问题,不能探索新事实或生成新结论。
+
+### 5.6 决策六:资源预算和信息增益是两套停止机制
+
+**问题**:预算只能回答“还能不能花资源”,不能回答“继续查询还有没有价值”。知识库只有通用资料、日志持续为空时,Agent 可以在预算范围内不断换关键词,最终以 `BUDGET_EXHAUSTED` 结束。用户得到的是技术失败,而系统实际已经知道“当前范围证据不足”。
+
+**决策**:
+
+- Tool 负责客观事实:是否执行成功、是否为空、实际 scope;
+- Harness 机械判定空结果和完全重复的 `tool_name + normalized_scope` 为 `NO_GAIN`;
+- 其他成功非空结果由模型在下一次 Tool Call Envelope 中声明 `GAINED / NO_GAIN`;
+- `DiagnosisProgressTracker` 维护连续 `NO_GAIN`,达到阈值后进入 `SATURATED`;
+- 连续 Envelope 协议错误使用独立计数和 `PROGRESS_PROTOCOL_VIOLATED`,不伪装成无信息增益;
+- STOP_REQUIRED 只交付一次,再次请求 Tool 直接受控终止;
+- `INFORMATION_SATURATED` 与 `BUDGET_LIMIT_REACHED` 始终分开记录。
+
+```mermaid
+stateDiagram-v2
+ [*] --> COLLECTING
+ COLLECTING --> COLLECTING: GAINED / 清零 NO_GAIN
+ COLLECTING --> COLLECTING: NO_GAIN / 未达阈值
+ COLLECTING --> SATURATED: 连续 NO_GAIN 达阈值
+ COLLECTING --> SATURATED: 连续协议错误达阈值
+ COLLECTING --> STOPPED: 硬预算到达
+ SATURATED --> DRAFT_CHANCE: 单次 STOP_REQUIRED
+ DRAFT_CHANCE --> STOPPED: 再次请求 Tool
+ DRAFT_CHANCE --> RELEASE: 输出合法 Draft
+ STOPPED --> RELEASE: ProgressSnapshot
+```
+
+**为什么不用 `new_count`**:跨 RAG、日志和数据库建立统一内容指纹成本高,而且“新记录”不等于“对假设有价值”。
+
+**为什么不用独立 Progress Judge**:它会增加模型成本和新的失败点,还会把简单的空结果、重复 scope 判断模型化。
+
+**首版边界**:重复检测只比较规范化参数,不承诺识别自然语言语义等价查询。
+
+### 5.7 决策七:`conclusion=null` 是合法完成,不是模型失败
+
+**问题**:如果成功的唯一含义是“必须给出根因”,Agent 在缺少企业、时间范围或错误信息时只能继续盲查,或者编造结论。
+
+**决策**:DiagnosisDraft 允许 `conclusion=null`:
+
+- 缺少开始查询所需信息:`MISSING_REQUIRED_CONTEXT`;
+- 已完成有限检查但证据不足:`INSUFFICIENT_EVIDENCE`;
+- 有结论:才进入完整 EvidenceGuard、Repair、SemanticGuard 链。
+
+最终公开生命周期仍只有 `SUCCESS / FALLBACK / FAILED / CANCELLED`。证据不足是 `FALLBACK` 的原因,不再引入一套与 Run 终态平行的诊断状态机。
+
+如果模型输出非法 Draft,系统会丢弃非法正文;只有当前 Run 已存在可验真的 ProgressSnapshot,才允许降级成过程型 Fallback,否则保持 fail closed。
+
+### 5.8 决策八:可观测性记录决策证据,不复制敏感上下文
+
+**问题**:为了调试 Agent,最直接的做法是保存 Prompt、模型正文、Tool arguments 和 raw response。但这会让普通 Trace 变成敏感数据仓库,也会造成多份事实副本。
+
+**决策**:
+
+- `diagnosis_trace_event` 只保存追加式、按 exact Run 排序的事件和有界 metadata;
+- `ToolInvocation` 长期保存 Tool 身份、状态、耗时和字节数,不保存完整请求/响应;
+- `AgentStep` 保存步骤元数据和 Token,不保存 Prompt、Tool payload;
+- Provider reasoning 与 assistant text 放入独立受限审计表和接口;
+- Trace 写入失败不得改变业务结果;
+- Model usage 按组件和轮次入账,Run 结束做 Token 对账;
+- Tool 请求被 Harness 拒绝时记录 `TOOL_REQUEST_REJECTED`,不能伪装成一次真实 `TOOL_INVOCATION`。
+
+**尚未完成的治理**:Reasoning 的访问控制、保留期限和加密仍由 ISS-015 跟踪。已有隔离不代表完整合规闭环。
+
+## 6. 一次诊断的完整控制流程
+
+```mermaid
+sequenceDiagram
+ participant U as Client
+ participant App as Chat Application
+ participant Core as Harness Core
+ participant Agent as Diagnosis Agent
+ participant TI as Tool Interceptor
+ participant TB as ToolBoundary
+ participant Store as Canonical Store
+ participant Guard as Guards
+ participant Release as Release
+
+ U->>App: query + optional sessionId
+ App->>Core: startRun(sessionId)
+ Core-->>App: RunContext(runId, deadline, handles)
+ App->>Agent: query + bounded safe previousTurn
+
+ loop ReAct 由框架拥有
+ Agent->>TI: Tool Call Envelope
+ TI->>TI: 应用上一轮信息增益、检查重复/饱和/协议
+ alt 门禁允许
+ TI->>TB: framework id + business input + RunContext
+ TB->>TB: active / auth / readonly / budget / size
+ TB->>Store: PROJECTING -> READY or ERROR
+ TB-->>TI: bounded canonical agent_result
+ TI-->>Agent: whitelist observation
+ else 信息饱和或协议停止
+ TI-->>Agent: one-shot STOP_REQUIRED
+ end
+ end
+
+ Agent-->>App: DiagnosisDraft 或受控停止
+ App->>Release: Draft + ProgressSnapshot + stop reason
+ alt 有结论
+ Release->>Guard: evidence truth + semantic support
+ Guard-->>Release: verified / unsupported
+ else 无结论或受控停止
+ Release->>Release: 构造确定性 Fallback
+ end
+ Release-->>App: original safe Draft or SafeFallback
+ App->>Core: first terminal wins
+ App-->>U: content/failure + done
+```
+
+## 7. 真实问题如何反向修正设计
+
+这些不是零散 Bug 清单。每个问题都暴露了一个原设计假设不成立,并促成了边界调整。
+
+| 现场问题 | 被证伪的假设 | 设计修正 | 固化位置 |
+|---|---|---|---|
+| Spring AI 默认 10 次 retry | 一次 Harness 模型调用等于一次 Provider attempt | SDK retry=1,retry 所有权上移 | Core / Retry / 配置测试 |
+| ThreadLocal 跨异步边界不稳定 | 同线程上下文足以表示 Run 所有权 | 显式 `RunContext` 贯穿调用链 | Core / framework metadata |
+| Tool raw 直接进入 Agent | Tool 返回可同时服务推理、验真和审计 | canonical / control / observation 三层拆分 | ToolBoundary / Projector / Store |
+| 0 条日志被表达为 `success=false` | 空结果等于技术失败 | 技术执行与 `NO_EVIDENCE` 分离 | Tool contract / Projector |
+| `REFERENCE` 被当作诊断证据 | 非空候选等于支持结论 | 保留相关度,语义增益交给模型 | RAG projector / Progress |
+| Redis canonical 全部 `STORE_ERROR` | 单测 ObjectMapper 与生产配置行为一致 | 使用生产 ObjectMapper 能力并增加 live E2E | Store wiring / E2E |
+| raw/rewritten query 出现在日志 | 可观测性可以直接打印检索输入 | 普通日志和 Trace 仅保存安全 metadata | Audit boundary |
+| 空查反复改写直到预算耗尽 | 硬预算可以承担正常收敛 | 引入信息增益和饱和停止 | ProgressTracker / Interceptor |
+| 9 次协议拒绝仍消耗 13 轮模型 | 协议错误会被模型自然修正 | 独立协议错误阈值和一次 STOP_REQUIRED | Progress protocol |
+| 非法 Draft 导致已完成检查全部丢失 | Draft 失败意味着整个 Run 没有安全价值 | 仅在已有验真进展时发布过程型 Fallback | ProgressSnapshot / Release |
+
+## 8. 关键决策总表
+
+| 决策 | 选择 | 放弃的方案 | 获得的能力 | 付出的代价 |
+|---|---|---|---|---|
+| 推理拓扑 | 单 Diagnosis ReAct Agent | 多 Agent Graph、外层 ReAct | 上下文连续、唯一 Draft 作者 | Harness 门禁必须完整 |
+| Run 上下文 | 显式 record + 状态句柄 | ThreadLocal、全局 Registry | 异步可证明、状态所有权清楚 | 参数需要显式传递 |
+| Tool ID | framework `tool_call_id` | Harness 二次生成 ID | 引用链唯一 | 依赖框架 ID 契约 |
+| Tool 真相 | Redis canonical,短 TTL | JPA 保存完整 raw | 可验真且限制敏感数据寿命 | Redis 可用性成为验证依赖 |
+| Agent 输入 | 白名单 projection | raw / 完整 canonical 直传 | 上下文有界、减少泄露 | 需为每类 Tool 维护 projector |
+| 长期审计 | metadata-only | 永久保存请求和响应 | 降低泄露与重复真相 | 深度回放受 TTL 限制 |
+| 证据安全 | 确定性 EvidenceGuard + 隔离 SemanticGuard | 单一 Verifier Agent | 分清真实性和支持度 | 两段门禁增加延迟 |
+| 修复 | 只修引用且语义必须不变 | Guard 重写报告 | 保持唯一作者 | 部分报告只能 Fallback |
+| 停止 | 预算 + 信息增益双机制 | 只靠 Prompt 或硬上限 | 正常无证据收敛 | 首版只能做参数级重复判断 |
+| 无结论 | 合法 Draft + SafeFallback | 强制根因 | 避免盲查和编造 | 调用方需理解 FALLBACK 是业务结果 |
+| 发布 | 唯一 Release Policy | 各层自行 fallback | 对外语义一致 | Release 成为关键集中组件 |
+
+## 9. Harness 明确不做什么
+
+边界是否清晰,既看它做什么,也看它拒绝做什么:
+
+- 不判断业务根因;
+- 不实现 Planner / Executor / Verifier / Composer 角色图;
+- 不在框架外复制 ReAct while-loop;
+- 不让 Tool 声称结果是否支持诊断结论;
+- 不把检索分数直接提升为结论可信度;
+- 不依赖 Prompt 作为唯一预算或停止机制;
+- 不自动重试 Diagnosis Agent 和业务 Tool;
+- 不允许 Guard 生成新事实或改写结论;
+- 不把 Redis canonical 变成永久审计库;
+- 不承诺同步 Provider 调用一定能被立即物理中断;
+- 不在首版做自然语言语义去重或跨 Tool 内容指纹。
+
+这些非目标是在防止 Harness 再次长成一套不可维护的业务编排系统。
+
+## 10. 如何验证设计成立
+
+验证重点不是某个类是否被调用,而是上述不变量能否在失败和竞态下保持:
+
+| 验证层 | 需要证明的事实 | 代表性测试/证据 |
+|---|---|---|
+| Core 单测 | deadline、取消、预算、first-terminal-wins、异步显式 context | `RunContextTest`、`RunBudgetTest`、`DiagnosisHarnessCoreTest` |
+| Tool 单测 | cross-run、重复 ID、只读、大小、状态迁移、投影 | `ToolBoundaryTest`、`CanonicalInvocationStoreTest`、各 Projector test |
+| Agent loop | framework ID、Envelope、STOP_REQUIRED、非法 Draft | `DiagnosisAgentUseCaseTest`、`HarnessToolInterceptorTest` |
+| Guard / Release | 引用闭包、Repair 语义不变、Unsupported Fallback | `EvidenceGuardTest`、`SemanticGuardTest`、`DiagnosisReleaseUseCaseTest` |
+| Audit | metadata 边界、Token 对账、拒绝与执行分离 | audit 包 focused tests、exact-run Trace |
+| Live E2E | 生产序列化、Redis、数据库、模型和 SSE 的组合契约 | `devflow/projects/2026-07-22-single-react-cleanup-e2e/`、ISS-016 evidence |
+
+单元测试能证明局部状态机,不能证明生产 `ObjectMapper`、Redis serializer、Provider Tool Calling 和 SSE 串联正确。因此 canonical store 的 Java Time 问题、零日志语义和协议空转都必须依靠 live E2E 反证,而不能只看 mock tests。
+
+## 11. 当前边界与后续治理
+
+当前设计已经建立可运行的确定性边界,但仍有明确限制:
+
+1. 重复检测是 `tool_name + normalized_scope` 的参数等价,不识别语义近似查询。
+2. Redis canonical 受 TTL 约束;TTL 过后只能依赖 metadata audit,不能重建完整证据正文。
+3. 同步模型请求的取消主要阻止后续边界和迟到发布,不承诺 Provider 已执行计算立即停止。
+4. SemanticGuard 仍是模型判断,只是被限制在无 Tool、无记忆、二值输出的最小范围内。
+5. Reasoning 已与普通 Trace 隔离,但访问控制、保留期限和加密仍需完成。
+6. 信息增益阈值默认为 2,仍需通过固定评测集持续校准,不能通过线上直觉随意调大。
+
+## 12. 可复用的设计原则
+
+这套 Harness 最值得复用的不是某个 Java 类,而是以下判断顺序:
+
+1. 先列出系统必须保证的确定性不变量,再决定组件。
+2. 将推理权留给模型,将授权、预算、归属、验真和发布权留给代码。
+3. 不让一个数据表示同时承担内部真相、模型上下文和长期审计。
+4. 不用资源耗尽代替业务收敛,也不用 Prompt 代替硬门禁。
+5. 不让验证器成为第二个作者;验证失败时降级,而不是偷偷改写。
+6. 把“没有足够证据”设计成一等业务结果,系统才不需要用幻觉换取成功率。
+
+## 13. 代码与文档入口
+
+- 完整组件说明:[Harness组件全景-职责-设计原因与边界.md](Harness组件全景-职责-设计原因与边界.md)
+- 当前质量门禁:[harness-quality-gates.md](../../architecture/harness-quality-gates.md)
+- Diagnosis Agent 架构:[agent-orchestration.md](../../architecture/agent-orchestration.md)
+- 信息增益停止:[diagnosis-information-gain-stop-architecture.md](../../architecture/diagnosis-information-gain-stop-architecture.md)
+- 核心重构 Issue:[ISS-014-single-react-agent-harness-aci-ptk-refactor.md](../../issues/archived/ISS-014-single-react-agent-harness-aci-ptk-refactor.md)
+- 信息增益 Issue:[ISS-016-diagnosis-information-gain-stop-contract.md](../../issues/active/ISS-016-diagnosis-information-gain-stop-contract.md)
diff --git a/mvp/engineering/harness/Harness证据安全链-从引用真实到结论可发布.md b/mvp/engineering/harness/Harness证据安全链-从引用真实到结论可发布.md
new file mode 100644
index 0000000..de57dd9
--- /dev/null
+++ b/mvp/engineering/harness/Harness证据安全链-从引用真实到结论可发布.md
@@ -0,0 +1,276 @@
+# Harness 证据安全链:从引用真实到结论可发布
+
+**更新日期**:2026-07-29
+**主题**:EvidenceGuard、EvidenceRepair、SemanticGuard 与 Diagnosis Release
+**术语与状态**:[CONTEXT.md](CONTEXT.md) · [Harness生命周期与状态.md](Harness生命周期与状态.md)
+**前置阅读**:[Harness-Tool双视图-从原始结果到可验证证据.md](Harness-Tool双视图-从原始结果到可验证证据.md)
+
+## 1. 证据存在,不代表结论成立
+
+诊断报告至少可能出现三类不同错误:
+
+1. **伪造引用**:Draft 引用了不存在、失败或属于其他 Run 的 Tool Call。
+2. **引用断裂**:analysis 有证据,但 conclusion、action plan 或 recommendation 没有绑定对应 analysis。
+3. **牵强推论**:引用和结构都真实,但证据并不足以支持所写根因。
+
+例如,系统确实查到了一条支付超时日志,但报告把根因写成“数据库连接池耗尽”。此时 Tool Call 真实、日志真实、引用格式也正确,结论仍然不成立。
+
+因此“报告是否安全”不能由一个笼统的 Verifier 回答。它至少包含两个不同命题:
+
+```text
+P1:引用的证据是否真实、属于当前 Run,并形成结构闭包?
+P2:这些真实证据是否足以支持报告中的结论?
+```
+
+P1 可以由代码确定性证明;P2 是语义判断。把两者都交给模型,会让本可机械验证的身份和状态也变成概率结果;全部交给规则,则规则代码会逐渐变成另一套业务诊断引擎。
+
+## 2. 设计目标:验证链不能产生新事实
+
+证据安全链遵守四个约束:
+
+- Diagnosis Agent 是唯一报告作者;
+- EvidenceGuard 只做确定性验真;
+- SemanticGuard 只做受限语义审查;
+- Release 是唯一公开决策点。
+
+任何 Guard 都不能调用业务 Tool、探索新证据或重写最终报告。否则验证链会变成第二条隐式 Agent 链,重新引入多 Agent 架构的问题。
+
+```mermaid
+flowchart LR
+ D["DiagnosisDraft
唯一业务 Draft"] --> R["DiagnosisReleaseUseCase
发布编排所有者"]
+ R --> E["EvidenceGuard
确定性引用验真"]
+ E -->|"invalid"| X["EvidenceRepair
只修结构/引用"]
+ X --> E2["EvidenceGuard Recheck"]
+ E -->|"valid"| V["VerifiedEvidenceSnapshot"]
+ E2 -->|"valid"| V
+ V --> S["SemanticGuard
SUPPORTED / UNSUPPORTED"]
+ S -->|"SUPPORTED"| OK["发布原始或等义修复 Draft"]
+ E -->|"repair 失败"| F["SafeFallback"]
+ E2 -->|"invalid"| F
+ S -->|"UNSUPPORTED / unavailable"| F
+```
+
+## 3. 第一层:EvidenceGuard 证明引用真实性
+
+`EvidenceGuard` 不调用模型。它从当前 `RunContext` 出发,对 Draft 进行两阶段检查。
+
+### 3.1 Draft 结构与引用闭包
+
+首先检查报告自身结构:
+
+- Draft、analysis、analysis ID、kind 和正文是否存在;
+- analysis ID 是否重复;
+- 每条有结论分析是否绑定 Tool Call;
+- conclusion、action plan 和 recommendation 是否绑定已存在的 analysis ID;
+- limitations 是否存在并声明报告范围。
+
+引用链的目标不是让 JSON 看起来完整,而是形成下面的闭包:
+
+```text
+Conclusion / Action / Recommendation
+ |
+ v
+ Analysis Item
+ |
+ v
+ framework tool_call_id
+ |
+ v
+ Canonical READY Invocation
+```
+
+只要其中一跳缺失,公开报告就不能证明其来源。
+
+### 3.2 Canonical Invocation 验真
+
+对每个 `tool_call_id`,Guard 使用当前 `runId` 重新生成 canonical key,然后检查:
+
+1. ID 格式是否合法;
+2. canonical record 是否存在;
+3. record 内 ID 是否与引用一致;
+4. record 是否属于当前 Run;
+5. invocation 是否 READY;
+6. analysis kind 是否接受该 evidence status;
+7. Tool 类型是否受支持;
+8. `agent_result` 是否能严格解析为对应 typed result;
+9. typed result 中的 ID、状态、count 和集合是否自洽。
+
+因此,即使模型猜中了另一个 Run 的 Tool Call ID,也无法通过当前 Run key 和 ownership 检查。
+
+### 3.3 正向证据和负向观察不能混用
+
+`AnalysisKind` 与 `EvidenceStatus` 的匹配是关键约束。
+
+一条 READY + NO_EVIDENCE 日志结果,只能支持如下陈述:
+
+> 在企业 A、时间窗 T、服务 S、查询条件 Q 下没有匹配事件。
+
+它不能支持:
+
+> 系统没有发生故障。
+
+EvidenceGuard 会把空结果投影为带 source 和 scope 的负向观察,而不是把它提升为支持任意根因的正向证据。
+
+## 4. VerifiedEvidenceSnapshot 为什么是必要中间产物
+
+EvidenceGuard 验证通过后不会把 canonical raw 直接交给 SemanticGuard,而是生成 `VerifiedEvidenceSnapshot`。它保存:
+
+- analysis ID、正文和 kind;
+- 已验证证据的 source type、source、scope、timestamp、excerpt;
+- Tool-specific 的有限结构化 values;
+- 去重后的 verified sources。
+
+Snapshot 的作用是形成一条新的最小信任边界:SemanticGuard 不需要访问 Redis,也不能看到 request、raw response 或内部错误。它只判断“这组已经验真的事实是否支持 Draft”。
+
+这也避免了语义审查阶段重新解释 backend 私有格式。
+
+## 5. 第二层:EvidenceRepair 只允许修引用
+
+### 5.1 为什么需要 Repair
+
+模型可能生成业务语义正确、但引用结构存在局部问题的 Draft,例如:
+
+- conclusion 漏写 `based_on_analysis_ids`;
+- analysis 引用了错误的 Tool Call ID;
+- 输出字段结构不符合严格 Schema。
+
+如果所有结构错误都直接 Fallback,会丢掉部分可修复结果。但让 Repair 自由重写又会产生更严重的问题:最终结论不再来自 Diagnosis Agent,Repair 事实上成为第二个报告作者。
+
+### 5.2 Repair 的不变量
+
+`EvidenceRepair` 只获得:
+
+- 原始 query;
+- 原始 Draft;
+- EvidenceGuard 给出的 violations。
+
+它没有 Tool,也不能获取新证据。修复输出必须再次严格解析,并满足:
+
+```text
+SemanticDraftView(original)
+ ==
+SemanticDraftView(repaired)
+```
+
+即用户可见的 analysis、conclusion、action plan、recommendations 和 limitations 语义不变,只允许修复引用和结构。之后必须重新执行 EvidenceGuard,不能因为“已经 Repair”就跳过验真。
+
+当前策略只给 EvidenceRepair 一次 attempt。Repair 失败、改变语义或复检仍不通过,直接进入 `EVIDENCE_VALIDATION_FAILED` Fallback。
+
+## 6. 第三层:SemanticGuard 判断结论支持度
+
+EvidenceGuard 证明的是“证据是真的”,SemanticGuard 判断的是“推论是否成立”。为了不让它变成第二个 Agent,系统主动削减了它的能力:
+
+| 约束 | 目的 |
+|---|---|
+| 无 Tool | 不能自行寻找新事实 |
+| 无会话记忆 | 不能引入当前输入之外的信息 |
+| 单轮调用 | 不形成新的 ReAct loop |
+| 输入固定 | 只接收 query、Draft 和 VerifiedEvidenceSnapshot |
+| 输出严格 | 只能返回 `verdict + reason` |
+| 二值 verdict | 只允许 `SUPPORTED / UNSUPPORTED` |
+| 无发布权限 | 不能改写或直接返回用户报告 |
+
+技术超时、传输失败、解析失败和 Schema 非法可以按完全相同输入进行一次显式重试。仍失败时不假设“可能支持”,而是 fail closed,发布 `SEMANTIC_UNAVAILABLE` Fallback。
+
+这里需要准确描述“确定性边界”:SemanticGuard 的语义判断仍然是概率性的;确定的是它的权限、输入、输出、成本、重试次数和失败后的发布行为。
+
+## 7. Release 为什么必须是唯一发布入口
+
+如果 Application、EvidenceGuard 和 SemanticGuard 都能构造公开结果,同一种失败会出现多个含义,甚至可能绕过前置校验。`DiagnosisReleaseUseCase` 集中处理以下分支:
+
+| 输入情况 | 验证路径 | 发布结果 |
+|---|---|---|
+| Draft 有 conclusion | EvidenceGuard -> 可选 Repair/Recheck -> SemanticGuard | 原 Draft/等义修复 Draft,或安全 Fallback |
+| Draft 无 conclusion,有已验真进展 | 只验证其中已有 Tool 引用 | `INSUFFICIENT_EVIDENCE` |
+| Draft 无 conclusion,无进展但声明缺失信息 | 检查引用边界 | `MISSING_REQUIRED_CONTEXT` |
+| Harness 受控停止,有已验真进展 | ProgressSnapshot | `INSUFFICIENT_EVIDENCE` |
+| Draft 非法,但有已验真进展 | 丢弃非法 Draft,使用 ProgressSnapshot | `INSUFFICIENT_EVIDENCE` |
+| Draft 非法且没有安全进展 | 无可发布事实 | FAILED,保持 fail closed |
+
+有结论时,只有 `SUPPORTED` 可以发布 Draft。发布的是 Diagnosis Agent 原始 Draft,或者经证明用户可见语义相同的 Repair Draft,不让 SemanticGuard生成“更好的答案”。
+
+## 8. `conclusion=null` 为什么不进入完整语义审查
+
+当模型明确表示无法确认根因时,不存在需要验证的根因结论。继续调用 EvidenceRepair 或 SemanticGuard,不仅浪费 Token,还可能让验证模型反向补出一个原本不存在的结论。
+
+因此无结论路径只做必要的引用真实性检查,然后根据安全进展决定:
+
+```text
+有 verified progress
+ -> INSUFFICIENT_EVIDENCE
+
+没有 progress,但有 limitations.missing_info
+ -> MISSING_REQUIRED_CONTEXT
+
+两者都没有
+ -> 不是合法业务结果,fail closed
+```
+
+这项设计把“没有找到根因”从模型失败中分离出来,但没有降低证据要求。
+
+## 9. SafeFallback 不是通用错误文案
+
+SafeFallback 是结构化、确定性的发布结果。它可以包含:
+
+- `verified_sources`:通过当前 Run 验真的来源;
+- `observed_facts`:有界、去重后的事实或负向观察;
+- `limitations`:为什么不能确认根因;
+- `next_steps`:下一步需要补充的信息或检查;
+- `validation_issues`:稳定违规码和目标字段;
+- `failure_stage`:失败发生在收集、证据还是语义阶段。
+
+它不能包含 Prompt、原始 Draft、raw Tool payload、内部异常或模型 reasoning。Fallback 不是“把错误吞掉”,而是只发布系统已经能够证明的部分。
+
+## 10. 为什么不采用其他方案
+
+### 10.1 单个 Verifier Agent
+
+优点是实现表面简单,缺点是把 ID、Run ownership、Schema 和业务支持度混在同一次模型判断中。本来可以 100% 用代码拒绝的跨 Run 引用,也会变成概率审查。
+
+### 10.2 Guard 自动改写报告
+
+可以提高表面成功率,但破坏唯一作者原则。Guard 为了“修好”结论,往往会引入新解释;此时必须重新验证新内容,最终形成循环。
+
+### 10.3 只做引用存在检查
+
+能阻止伪造 ID,却无法阻止“真实证据 + 错误根因”。这正是 SemanticGuard 存在的原因。
+
+### 10.4 语义失败直接技术报错
+
+丢失了已经验真的事实,也把“结论不受支持”错误表达为基础设施故障。SafeFallback 可以保留过程价值,同时拒绝发布根因。
+
+## 11. 真实问题如何改变设计
+
+| 问题 | 暴露的设计缺陷 | 修正 |
+|---|---|---|
+| 旧 Verifier 同时验引用、判断语义和改写答案 | 验证职责没有边界 | EvidenceGuard、SemanticGuard、Release 三段拆分 |
+| Tool 结果面向开发者,含 raw 和重复正文 | Guard 与 Agent 没有独立事实面 | 先建立 canonical truth 和 verified snapshot |
+| `NO_EVIDENCE` 被当成一般失败或正向证据 | 负向观察没有范围约束 | AnalysisKind 与 EvidenceStatus 匹配校验 |
+| Repair 可能改变结论 | 修复者变成新的报告作者 | SemanticDraftView 前后等义检查 |
+| 非法 Draft 使已完成 Tool 检查全部丢失 | Draft 是唯一可发布价值来源 | 只在 ProgressSnapshot 已验真时允许过程型 Fallback |
+| SemanticGuard 技术失败时行为不一致 | 各层自行决定降级 | Release 统一生成 `SEMANTIC_UNAVAILABLE` |
+
+## 12. 代价与剩余风险
+
+1. SemanticGuard 增加一次模型调用和延迟;这是语义安全与成本之间的明确取舍。
+2. Semantic verdict 不是形式化证明,仍可能误判;当前设计限制的是权限和失败影响,而不是宣称模型绝对正确。
+3. EvidenceGuard 需要理解每种 Tool 的 typed result;新增 Tool 必须同步增加验证规则。
+4. Repair 的语义等价由结构化视图定义,无法证明两个自然语言文本在所有解释下完全等价,因此 Repair 能力被刻意限制。
+5. Canonical record TTL 到期后无法重新完成完整验真,所以公开决策必须在当前 Run 内完成。
+
+## 13. 如何验证
+
+| 需要证明 | 代表性验证 |
+|---|---|
+| 缺失、重复、未知 analysis 引用被拒绝 | `EvidenceGuardTest` |
+| cross-run、非 READY、ID/status 不一致被拒绝 | `EvidenceGuardTest` + canonical fixtures |
+| NO_EVIDENCE 只能形成限定负向观察 | Evidence kind focused cases |
+| Repair 不得改变用户可见语义 | `DiagnosisReleaseUseCaseTest`、Repair tests |
+| Semantic 非法输出只有限重试并安全降级 | `SemanticGuardTest` |
+| Guard 不改写报告,Release 只发布原 Draft 或 Fallback | `DiagnosisReleaseUseCaseTest` |
+| 无结论、非法 Draft、受控停止正确映射 | `DiagnosisChatExecutorTest`、Release focused cases |
+| exact-run Trace 能解释每一道门禁 | live E2E Timeline |
+
+## 14. 与前后链路的关系
+
+证据安全链依赖 [Tool 双视图](Harness-Tool双视图-从原始结果到可验证证据.md)提供独立 canonical truth;它解决的是“哪些内容能够发布”,不负责判断 Agent 是否还应该继续取证。正常收敛由[Harness信息增益停止-让无证据诊断正常收敛.md](Harness信息增益停止-让无证据诊断正常收敛.md)负责。
diff --git a/mvp/engineering/harness/README.md b/mvp/engineering/harness/README.md
new file mode 100644
index 0000000..408f970
--- /dev/null
+++ b/mvp/engineering/harness/README.md
@@ -0,0 +1,77 @@
+# Harness:先从一次诊断请求开始
+
+这不是 Harness 的完整说明,而是一页入门导读。
+
+第一次阅读时,不需要记组件名,不需要看状态枚举,也不需要理解所有边界。先回答一个问题:**为什么 Diagnosis Agent 外面还需要一层 Harness?**
+
+## 1. 如果只有 Agent,会发生什么
+
+假设用户问:
+
+> 为什么支付服务在 10:00 到 10:10 大量超时?
+
+Diagnosis Agent 会自己决定查什么、调用哪些 Tool、什么时候停止,并根据返回结果写出结论。这里有四个不能只靠 Prompt 解决的问题:
+
+- 它可能查询过多,耗尽时间和 Token;
+- Tool 原始结果可能包含敏感或超大内容,不能直接交给模型;
+- 它引用了某条日志,不代表这条日志真的来自本次查询;
+- 它写出了一个看似合理的根因,不代表证据足以支持这个根因。
+
+这些都不是“诊断能力”问题,而是**执行是否受控、结果是否可信**的问题。Harness 就是为此存在的。
+
+## 2. Harness 在一次请求中做了什么
+
+```mermaid
+flowchart LR
+ Q["用户提出诊断问题"] --> R["给本次执行建立 Run
限制时间和资源"]
+ R --> A["Agent 分析问题
选择 Tool"]
+ A --> T["Harness 执行 Tool
保存事实,只给 Agent 安全视图"]
+ T --> A
+ A --> D["Agent 写出诊断草稿"]
+ D --> G["Harness 检查
引用是否真实、证据是否支持结论"]
+ G --> P["发布报告
或安全地说明证据不足"]
+```
+
+顺着这条线看,Harness 只做三类事情:
+
+1. **执行前设边界**:为本次请求建立身份、deadline、预算和取消能力。
+2. **执行中管事实**:所有 Tool 经过统一入口,完整事实由系统保管,模型只看到安全、有限的内容。
+3. **发布前做验证**:先验证引用,再判断证据是否支持结论;不满足条件就发布 Fallback,而不是让未经验证的结论出去。
+
+Agent 仍然负责“问题的根因是什么”。Harness 不替 Agent 推理,它负责的是:**让这次推理有边界、有证据、能停止。**
+
+## 3. 先建立这个最小心智模型
+
+```mermaid
+flowchart TB
+ A["Diagnosis Agent
负责业务推理"]
+ H["Harness
负责确定性控制"]
+ U["用户最终看到的结果"]
+
+ A -->|"提出 Tool 请求和诊断草稿"| H
+ H -->|"返回受控的 Tool 观察"| A
+ H -->|"验证通过后发布,失败则 Fallback"| U
+```
+
+到这里,第一次阅读就可以停下。只要能说清下面这句话,就已经抓住了主干:
+
+> Agent 决定如何诊断,Harness 决定这次诊断可以消耗什么、可以看到什么、何时必须停止,以及什么结果允许发布。
+
+## 4. 需要时再往下读
+
+不要按目录顺序阅读。遇到具体问题时,只进入对应文档:
+
+| 当你想知道 | 再阅读 |
+|---|---|
+| 想逐步认识 Harness 的各组组件 | [组件渐进式导读](components/README.md) |
+| 为什么选这种控制边界,而不是工作流编排 | [Harness 主设计](Harness设计-非确定性Agent的确定性控制边界.md) |
+| 一次请求从开始到结束经历什么 | [生命周期与状态](Harness生命周期与状态.md) |
+| Tool 为什么要保存一份事实、给模型另一份视图 | [Tool 双视图](Harness-Tool双视图-从原始结果到可验证证据.md) |
+| 如何阻止“引用是真的,但结论是错的” | [证据安全链](Harness证据安全链-从引用真实到结论可发布.md) |
+| Agent 为什么不会无限查询 | [信息增益停止](Harness信息增益停止-让无证据诊断正常收敛.md) |
+| 某个类属于哪里、负责什么 | [组件全景](Harness组件全景-职责-设计原因与边界.md) |
+| 某个名词或状态是什么意思 | [Context 词典](CONTEXT.md) |
+
+如果准备系统学习,建议先读组件渐进式导读的四篇文章。每次只读一篇,读到“先记住这些”就可以停止。
+
+“组件全景”和“Context”都是参考手册,不需要从头读,也不需要背。
diff --git a/mvp/engineering/harness/components/01-运行控制-让一次请求有边界.md b/mvp/engineering/harness/components/01-运行控制-让一次请求有边界.md
new file mode 100644
index 0000000..0f0adf2
--- /dev/null
+++ b/mvp/engineering/harness/components/01-运行控制-让一次请求有边界.md
@@ -0,0 +1,121 @@
+# 01 运行控制:让一次请求有边界
+
+这一篇只回答一个问题:**一次诊断请求由谁负责到底?**
+
+## 1. 先看一个具体问题
+
+用户发起支付超时诊断。请求开始后,Router 调了一次模型,Diagnosis Agent 又调用多轮模型和 Tool,SemanticGuard 最后还要调用一次模型。与此同时,客户端可能断开,某次调用可能超时,两个线程也可能同时报告成功和失败。
+
+如果没有统一的运行控制,每个组件都会有自己的理解:
+
+- Controller 认为连接断开了,后台 Agent 却还在继续查询;
+- Agent 认为还可以调用 Tool,Run 的总预算其实已经耗尽;
+- 超时线程先写入失败,迟到的模型结果又把它覆盖为成功;
+- SDK 在内部偷偷重试,系统无法解释多出来的延迟和 Token;
+- 线上只看到最终失败,却不知道请求在哪一步、因为什么停止。
+
+所以运行控制不是一个计时器,而是由几个职责不同的组件共同完成。
+
+## 2. 四组组件怎样协作
+
+```mermaid
+sequenceDiagram
+ participant APP as Application
+ participant CORE as Core / RunContext
+ participant WORK as Agent、Tool、Guard
+ participant RETRY as Retry
+ participant AUDIT as Audit
+
+ APP->>CORE: 创建 RunContext
+ CORE-->>APP: runId、deadline、budget、cancel、lifecycle
+ APP->>WORK: 显式传入 RunContext
+ WORK->>CORE: 每次消耗前检查 active 和预算
+ WORK->>RETRY: 仅允许策略声明的技术重试
+ RETRY->>AUDIT: 记录每个 attempt
+ WORK->>AUDIT: 记录模型、Tool 和阶段元数据
+ APP->>CORE: 尝试写入最终终态
+ CORE-->>APP: first-terminal-wins
+ APP->>AUDIT: 记录公开结果和预算对账
+```
+
+第一次阅读可以把它们理解为:
+
+- **Application 是负责人**:组织一次请求从创建到公开结果。
+- **Core 是执行规则**:统一管理身份、deadline、预算、取消和唯一终态。
+- **Retry 是重做规则**:明确什么失败允许再试一次。
+- **Audit 是运行账本**:记录发生过什么,但不保存敏感正文。
+
+## 3. Application:负责人,而不是推理者
+
+Application 负责建立一次请求的应用边界:读取安全历史、创建 Run、判断 intent、调用对应执行分支、持久化结果,并把安全内容交给 SSE。
+
+设计它的原因,是这些步骤必须由一个地方协调。如果散落在 Controller、Agent 和 Guard 中,会出现多个结果出口和不同的失败语义。
+
+它不负责判断支付超时的根因,也不负责自己拼一份诊断 Fallback。它只负责把正确的执行组件按顺序接起来,并确保结果经过统一出口。
+
+主要代码入口:`ChatApplicationUseCase`、`DiagnosisChatExecutor`、`KnowledgeQueryExecutor`、`SystemChatExecutor`、`ChatRunStore`。
+
+## 4. Core:一次 Run 的共同规则
+
+Core 创建 `RunContext`。这个上下文显式携带:
+
+```text
+这是谁的请求:sessionId + runId
+最晚执行到何时:deadline
+还能消耗多少:RunBudget
+是否要求停止:RunCancellation
+最终如何结束:RunLifecycle
+模型消耗如何对账:ModelCallLedger
+信息收集是否仍有价值:DiagnosisProgressTracker
+```
+
+这里最重要的决策是**显式传递 RunContext**,而不是使用 ThreadLocal 或让每个组件自己查询全局状态。原因是模型和 Tool 可能跨线程运行;隐式上下文很容易丢失、串线,也很难在测试中证明归属关系。
+
+`RunLifecycle` 使用 first-terminal-wins:第一个成功写入的终态不可被迟到结果覆盖。它解决的是并发一致性,不是公开结果的业务含义。
+
+主要代码入口:`DiagnosisHarnessCore`、`RunContext`、`RunBudget`、`RunCancellation`、`RunLifecycle`。
+
+## 5. Retry:不能让“再试一次”藏起来
+
+重试会增加成本和延迟,也可能重复副作用,因此不能由 SDK、HTTP Client 和各组件各自决定。当前策略是:
+
+- Router 和 SemanticGuard 的特定技术失败最多尝试两次;
+- Diagnosis Agent、业务 Tool 和 EvidenceRepair 不做隐藏自动重试;
+- 取消、预算耗尽和协议错误不能被重试吞掉。
+
+这里没有设计通用重试 DSL。首版只需要不可变的 `RetryPolicy`、稳定的失败分类和统一的 `HarnessRetryExecutor`,复杂配置系统反而会掩盖真实执行路径。
+
+## 6. Audit:留下解释,而不是留下全部内容
+
+线上排障需要知道:调用了哪个组件、用了多少 Token、Tool 是否完成、为什么停止、为什么发布 Fallback。但这不等于长期保存 Prompt、SQL、日志原文、Tool raw response 和模型 reasoning。
+
+因此 Audit 长期保留的是有界元数据,完整 Tool 真相只在 canonical store 中短期存在。这个决策同时满足可回放和最小暴露原则。
+
+主要代码入口:`DiagnosisTraceRecorder`、`TraceAuditEvents`、`ToolInvocationAuditSink`、`HarnessAgentAuditHook`、`ModelCallAuditor`。
+
+## 7. 为什么没有合并成一个 RunManager
+
+把上述职责都放进一个 `RunManager` 看起来更简单,但它会同时知道 HTTP、路由、预算、模型、Tool、数据库和 SSE,最后变成新的业务工作流引擎。
+
+当前拆分依据不是“代码越细越好”,而是决策权不同:
+
+| 组件 | 它拥有的决定 | 它不能决定 |
+|---|---|---|
+| Application | 请求走哪条应用分支、何时持久化和返回 | 业务根因是否成立 |
+| Core | 是否仍可执行、资源是否允许、哪个终态生效 | 返回正常报告还是 Fallback |
+| Retry | 某类技术失败是否允许下一 attempt | 修改业务结果 |
+| Audit | 记录哪些安全元数据 | 影响执行结果 |
+
+## 8. 先记住这些
+
+第一次阅读只需记住:
+
+1. Application 对一次请求负责,Core 对执行不变量负责。
+2. RunContext 必须显式传播,所有模型和 Tool 共用同一预算与取消信号。
+3. 第一个终态获胜,迟到结果不能翻案。
+4. Retry 必须显式、分类、可审计。
+5. Audit 记录控制事实,不长期复制敏感正文。
+
+下一篇:[02 Agent 接入与收敛](02-Agent接入与收敛-让推理可以工作也可以停止.md)。
+
+需要查完整状态转换时,阅读[生命周期与状态](../Harness生命周期与状态.md);需要查全部类型时,阅读[组件全景](../Harness组件全景-职责-设计原因与边界.md)。
diff --git a/mvp/engineering/harness/components/02-Agent接入与收敛-让推理可以工作也可以停止.md b/mvp/engineering/harness/components/02-Agent接入与收敛-让推理可以工作也可以停止.md
new file mode 100644
index 0000000..c181438
--- /dev/null
+++ b/mvp/engineering/harness/components/02-Agent接入与收敛-让推理可以工作也可以停止.md
@@ -0,0 +1,148 @@
+# 02 Agent 接入与收敛:让推理可以工作,也可以停止
+
+这一篇只回答一个问题:**怎样保留 Agent 的自主推理,同时阻止它重复查询或无限空转?**
+
+## 1. 先看一个具体问题
+
+Diagnosis Agent 第一次查询 10:00 到 10:10 的支付日志,没有发现异常;第二次换了关键词,仍然没有新信息;第三次又提交了与第一次等价的查询。
+
+仅设置“最多调用 10 次 Tool”只能限制最坏损失,却回答不了:
+
+- 上一次结果有没有推进诊断?
+- 这次查询是否已经做过?
+- 连续多少次没有新信息后应该停止?
+- Agent 已经收到停止要求,为什么还能继续调用 Tool?
+- 停止后,怎样安全地向用户说明已经检查过什么?
+
+这就是 Agent 接入层和 Progress 组件共同解决的问题。
+
+## 2. 一轮 ReAct 怎样经过 Harness
+
+```mermaid
+sequenceDiagram
+ participant A as Diagnosis Agent
+ participant MI as Model Interceptor
+ participant TI as Tool Interceptor
+ participant P as Progress Tracker
+ participant T as Tool Boundary
+
+ A->>MI: 发起一轮模型调用
+ MI->>MI: 检查 Run、预留预算、记录 Token
+ A->>TI: 请求调用 Tool
+ TI->>P: 评价上一轮是否有信息增益
+ P-->>TI: 可继续 / 重复 / 已饱和 / 协议错误
+ alt 允许继续
+ TI->>T: 执行业务 Tool
+ T-->>TI: Control View + Model Observation
+ TI->>P: 登记已完成调用和 scope
+ TI-->>A: 只返回 Model Observation
+ else 必须停止
+ TI-->>A: STOP_REQUIRED
+ end
+```
+
+第一次阅读可以把它们理解为:
+
+- **Agent 接入层是检查站**:把框架原生 ReAct loop 接入预算、Tool 和审计边界。
+- **Progress Tracker 是收敛记录器**:判断是否重复、是否连续无增益、是否必须停止。
+
+## 3. 为什么复用框架 ReAct,而不是自己重写循环
+
+Diagnosis Agent 需要模型原生 Tool Calling 和多轮 ReAct。项目没有再写一套 `while` 循环,而是通过 Factory、Model Interceptor、Tool Interceptor 和 Audit Hook 接入框架。
+
+这样做的决策依据是:
+
+- ReAct 的业务推理由框架和模型负责;
+- 预算、Tool 授权、事实保存和停止协议由 Harness 负责;
+- 两者通过明确边界连接,不互相复制实现。
+
+如果 Harness 自己维护另一套 ReAct 状态机,就会同时出现“框架认为的下一步”和“Harness 认为的下一步”,调试时很难确定谁才是真理源。
+
+主要代码入口:`DiagnosisAgentFactory`、`DiagnosisAgentUseCase`、`HarnessModelInterceptor`、`HarnessToolInterceptor`、`HarnessEvidenceTools`。
+
+## 4. Model Interceptor:每一轮模型调用都必须记账
+
+一个 Diagnosis Agent 执行不等于一次模型调用。ReAct 可能经历多轮思考和 Tool 返回,因此每一轮都要:
+
+1. 检查 Run 是否仍然 active;
+2. 在调用前预留模型预算;
+3. 调用后记录 Provider 返回的 Token usage;
+4. 再次检查取消或迟到结果。
+
+Interceptor 不负责重试模型,也不判断输出是否支持根因。它只确保框架内部的每轮调用无法绕过 Harness。
+
+## 5. Tool Interceptor:先检查进展,再允许查询
+
+模型提交的 Tool Call 不只是业务参数,还携带对上一轮结果的评价。Tool Interceptor 会依次检查:
+
+- previous observation 是否完整、顺序是否正确;
+- 上一轮是 `GAINED` 还是 `NO_GAIN`;
+- 当前 Tool scope 是否已经完成过;
+- 收集状态是否已经 `SATURATED`;
+- 通过检查后,才把业务请求交给 ToolBoundary。
+
+它不执行后端查询,也不再次扣 Tool 预算。实际执行属于 ToolBoundary;收敛状态属于 ProgressTracker。Interceptor 只是两者与 ReAct 框架之间的接合点。
+
+## 6. 为什么“有结果”不等于“有信息增益”
+
+Tool 可以客观判断是否返回候选内容,却不知道这些内容是否推进了当前假设。例如同一条超时日志再次出现:
+
+```text
+InvocationStatus = READY
+EvidenceStatus = EVIDENCE_FOUND
+InformationGain = NO_GAIN
+```
+
+前三个状态回答不同问题:查询是否完成、是否有候选内容、内容是否推进当前诊断。把它们合成一个 `SUCCESS` 会让系统无法正常收敛。
+
+当前由 Agent 在**下一次 Tool Call** 中评价上一轮的信息增益。这样评价发生在它真正做出下一步行动时,Harness 也能机械检查调用顺序,而不需要再增加一个 Progress Judge 模型。
+
+## 7. Progress Tracker 保存什么
+
+Tracker 只保存控制所需的最小状态:
+
+- 已完成的 `tool_call_id` 和规范化 scope;
+- 哪个结果仍等待 Agent 评价;
+- 连续 `NO_GAIN` 次数;
+- 收集状态和停止原因;
+- 停止指令是否已经交付。
+
+它不保存 raw response,也不判断日志是否证明支付线程池耗尽。业务价值判断仍由 Diagnosis Agent 负责,事实内容仍由 canonical store 负责。
+
+主要代码入口:`DiagnosisProgressTracker`、`ToolScopeNormalizer`、`DiagnosisProgressProjector`。
+
+## 8. 为什么停止不直接等于失败
+
+连续无增益后停止,是一次正常的受控收敛,不是技术异常。Harness 会从已经完成的 canonical Tool 记录中投影 `ProgressSnapshot`,只保留可验真的检查范围、观察事实和限制,再形成安全 Fallback。
+
+```mermaid
+flowchart LR
+ N["连续 NO_GAIN 或重复查询"] --> S["CollectionState = SATURATED"]
+ S --> X["拒绝新的证据 Tool"]
+ X --> P["生成安全 ProgressSnapshot"]
+ P --> F["发布证据不足的 Fallback"]
+```
+
+因此,“没有找到足够证据”可以正常结束;只有违反进展协议、预算耗尽且没有安全进展等情况,才可能升级为失败。
+
+## 9. 为什么没有采用其他方案
+
+| 方案 | 没有采用的原因 |
+|---|---|
+| 只设 Tool 次数上限 | 只能止损,不能识别查询已经没有价值 |
+| Harness 根据结果条数判断增益 | 条数是客观统计,不代表是否推进业务假设 |
+| 增加 Progress Judge Agent | 增加模型成本和新的非确定性判断点 |
+| 用自然语言相似度判断重复 | 首版难以稳定解释误判,当前只做 typed 参数级 scope 规范化 |
+| 把剩余预算告诉模型 | 容易让模型围绕阈值博弈,硬限制应由 Harness 保持 |
+
+## 10. 先记住这些
+
+1. 框架负责 ReAct loop,Harness 通过 Interceptor 接入控制能力。
+2. 预算回答“还能不能查”,信息增益回答“继续查有没有价值”。
+3. Tool 是否有结果与结果是否推进诊断是两件事。
+4. ProgressTracker 只保存控制状态,不保存完整证据。
+5. 信息饱和可以正常发布 Fallback,不等于 Run 执行失败。
+
+下一篇:[03 Tool 事实边界](03-Tool事实边界-让模型看到必要信息而系统保留真相.md)。
+
+需要深入停止协议时,阅读[信息增益停止专题](../Harness信息增益停止-让无证据诊断正常收敛.md)。
diff --git a/mvp/engineering/harness/components/03-Tool事实边界-让模型看到必要信息而系统保留真相.md b/mvp/engineering/harness/components/03-Tool事实边界-让模型看到必要信息而系统保留真相.md
new file mode 100644
index 0000000..3a7b322
--- /dev/null
+++ b/mvp/engineering/harness/components/03-Tool事实边界-让模型看到必要信息而系统保留真相.md
@@ -0,0 +1,135 @@
+# 03 Tool 事实边界:让模型看到必要信息,系统保留真相
+
+这一篇只回答一个问题:**Tool 返回的数据应该由谁保管,模型究竟可以看到多少?**
+
+## 1. 先看一个具体问题
+
+Agent 查询支付日志,后端一次返回了几千行内容,其中包含重复堆栈、敏感字段和远超上下文窗口的正文。
+
+直接把原始结果塞回模型会产生几个问题:
+
+- 敏感数据进入模型上下文;
+- 大结果挤占 Token,真正关键的证据反而被淹没;
+- 模型后续引用经过裁剪的文本,系统无法证明它对应哪次真实调用;
+- 若只保存裁剪结果,EvidenceGuard 又失去了独立验真的事实来源;
+- 若把完整 raw 长期写入数据库,泄露面和存储成本都会扩大。
+
+所以 Tool 设计的核心不是“怎样调用后端”,而是**谁拥有原始事实,以及不同消费者应该看到哪一层数据**。
+
+## 2. 一次 Tool 调用的数据怎样变化
+
+```mermaid
+flowchart LR
+ REQ["Typed Tool Request"] --> B["ToolBoundary
权限、只读、预算、大小检查"]
+ B --> RAW["Backend Raw Result"]
+ RAW --> P["Tool-specific Projector"]
+ P --> C["Canonical Invocation
短期保存 request、raw、agent_result 和状态"]
+ C --> CV["Control View
供 Harness 判断状态和进展"]
+ C --> MO["Model Observation
供 Agent 推理的白名单内容"]
+ C --> EG["EvidenceGuard
按当前 Run 独立验真"]
+ B -.-> AU["Durable Audit
长期只留有界元数据"]
+```
+
+第一次阅读只需区分三份内容:
+
+- **Canonical Invocation**:系统在当前 Run 内短期保管的完整调用真相。
+- **Control View**:Harness 用来判断状态、scope 和证据数量的控制字段。
+- **Model Observation**:模型真正看到的安全、有限内容。
+
+## 3. ToolBoundary:所有 Tool 的统一入口
+
+RAG、日志和 MySQL 后端完全不同,但它们都必须遵守相同的不变量:
+
+1. Run 仍然 active,调用 ID 与当前 Run 匹配;
+2. Tool 已授权,请求声明和实际行为保持只读;
+3. 调用前预算允许,结果大小没有越界;
+4. canonical 状态只能从 `PROJECTING` 进入 `READY` 或 `ERROR`;
+5. 长期 Audit 不保存完整请求和 raw response。
+
+如果每个 Adapter 自己实现这些规则,三种 Tool 很快会出现不同的错误码、预算顺序和存储行为。ToolBoundary 集中处理共同规则,Adapter 只处理业务协议。
+
+ToolBoundary 不判断信息增益,也不判断某条日志是否支持根因。它只保证调用合法、状态可信、结果有界。
+
+主要代码入口:`ToolBoundary`、`ToolCallRequestEnvelope`、`ToolBoundaryResult`、`ToolBoundaryErrorCode`。
+
+## 4. Projector:把后端结果变成稳定事实
+
+不同后端的 raw 数据不能直接成为 Agent 契约:
+
+- RAG 需要限制证据数量和摘录长度,并保留文档身份;
+- Logs 需要限制事件、聚合 pattern,明确实际时间范围和是否截断;
+- MySQL 需要限制行、列、单元格和总 bytes,并保留查询 scope。
+
+每种 Tool 使用自己的 `ToolResultProjector`。Projector 负责标准化和计算客观 `EvidenceStatus`,但它不调用模型,也不判断这些事实是否足以支持最终结论。
+
+这是刻意保留的业务差异。强行做一个“万能 JSON Cleaner”,往往会丢掉每类证据真正需要的身份和范围信息。
+
+## 5. Canonical Store:为什么要保留独立真相
+
+Agent Observation 是为推理优化的,它会被裁剪和白名单投影,不能反过来充当验真依据。Canonical Store 保存当前调用的 request、raw、标准化 `agent_result`、状态和时间,使 EvidenceGuard 能绕开 Agent 上下文独立读取事实。
+
+当前选择 Redis TTL,是因为完整调用记录:
+
+- 只在当前 Run 的验证阶段需要;
+- 可能包含敏感内容,不应永久保存;
+- 需要按 `runId + tool_call_id` 快速定位;
+- 容量必须有硬上限,读取不能自动续期。
+
+raw 超限时选择失败,而不是悄悄截断。因为一旦截断,canonical record 就不再代表后端真实返回,后续验真会建立在不完整事实之上。
+
+主要代码入口:`CanonicalInvocationStore`、`RedisCanonicalInvocationStore`、`CanonicalToolInvocation`、`CanonicalInvocationLimits`。
+
+## 6. Model Observation:模型只拿完成任务所需的内容
+
+即使 canonical `agent_result` 已经标准化,其中仍可能包含 Harness 控制字段。`ToolResultViewProjector` 会进一步拆成:
+
+```text
+Control View:status、evidence count、scope、截断状态等
+Model Observation:有界证据正文、可读来源和下一步推理所需字段
+```
+
+模型不会看到 raw response、Redis key、预算阈值、重复指纹和完整 Harness 状态。这样既减少上下文噪声,也避免模型利用或复述内部控制信息。
+
+## 7. MySQL 为什么还需要单独的只读沙箱
+
+`readOnly=true` 只是请求声明,不能证明模型生成的 SQL 安全。MySQL Tool 在进入真实执行前还要经过:
+
+- JSqlParser AST 解析;
+- 单条 SELECT 和保守语法子集限制;
+- 数据源、schema、table、column 精确 allowlist;
+- 禁止投影通配符和元数据探测;
+- 只读账号、timeout、LIMIT 与最大行数。
+
+Validator 产生已经批准的 `MysqlQueryPlan`,Executor 只接受这个 plan,不重新信任原始字符串。这是纵深防御:任何一层都不能单独被当作完整授权。
+
+## 8. 为什么短期真相和长期审计要分开
+
+```mermaid
+flowchart TB
+ C["Canonical Store
完整、敏感、短期"] -->|"用于"| V["当前 Run 验真"]
+ A["Durable Audit
有界、元数据、长期"] -->|"用于"| O["排障、统计、成本对账"]
+```
+
+把两者合并会走向两个极端:要么长期复制所有敏感正文,要么为了安全只存元数据,导致当前 Run 无法验真。分开后,每种存储只承担自己的责任。
+
+## 9. 为什么没有采用其他方案
+
+| 方案 | 没有采用的原因 |
+|---|---|
+| raw 直接返回 Agent | 泄露、超限、噪声和无法独立验真 |
+| 只保存 Agent Observation | 投影内容不是完整事实,Guard 会依赖模型看到的版本 |
+| 完整 raw 永久写 MySQL | 扩大敏感数据暴露面和长期存储成本 |
+| raw 超限后静默截断 | canonical truth 会变成不完整真相 |
+| 所有 Tool 共用万能 Projector | 无法保持日志、RAG、表格各自的身份和 scope 语义 |
+
+## 10. 先记住这些
+
+1. ToolBoundary 统一执行规则,Adapter 处理具体后端。
+2. Canonical Invocation 是系统真相,Model Observation 是模型视图。
+3. Agent 看到的内容不能反过来成为 EvidenceGuard 的事实来源。
+4. 完整真相短期保存,长期 Audit 只留有界元数据。
+5. Tool 找到候选内容,不代表它支持最终根因。
+
+下一篇:[04 验证与发布](04-验证与发布-让未经证明的结论无法越过出口.md)。
+
+需要深入字段和存储取舍时,阅读[Tool 双视图专题](../Harness-Tool双视图-从原始结果到可验证证据.md)。
diff --git a/mvp/engineering/harness/components/04-验证与发布-让未经证明的结论无法越过出口.md b/mvp/engineering/harness/components/04-验证与发布-让未经证明的结论无法越过出口.md
new file mode 100644
index 0000000..15e4ed9
--- /dev/null
+++ b/mvp/engineering/harness/components/04-验证与发布-让未经证明的结论无法越过出口.md
@@ -0,0 +1,162 @@
+# 04 验证与发布:让未经证明的结论无法越过出口
+
+这一篇只回答一个问题:**Agent 写完诊断草稿以后,为什么还不能直接返回给用户?**
+
+## 1. 先看一个具体问题
+
+Agent 在报告中写道:
+
+> 10:03 出现数据库连接池耗尽,因此支付接口大量超时。
+
+它同时引用了一次日志 Tool Call。系统即使确认这个调用真实存在、属于当前 Run,而且日志中确实出现“connection timeout”,仍然不能直接发布上述结论。
+
+因为这里至少有三个不同问题:
+
+1. Agent 引用的 Tool Call 是不是真的?
+2. 真实日志是否足以证明“数据库连接池耗尽导致支付超时”?
+3. 验证失败后,系统最终应该向用户发布什么?
+
+它们分别属于 EvidenceGuard、SemanticGuard 和 Release。
+
+## 2. 草稿怎样通过发布链
+
+```mermaid
+flowchart LR
+ D["DiagnosisDraft
Agent 写出的草稿"] --> E["EvidenceGuard
机械验证引用和归属"]
+ E -->|"结构或引用可修复"| R["EvidenceRepair
只修引用,不改语义"]
+ R --> E
+ E -->|"得到已验真证据"| S["SemanticGuard
判断证据是否支持报告"]
+ S -->|"SUPPORTED"| P["Release SUCCESS
发布原始安全 Draft"]
+ E -->|"无法验真"| F["SafeFallback"]
+ S -->|"UNSUPPORTED 或不可用"| F
+ F --> O["Release FALLBACK"]
+```
+
+第一次阅读只需记住:
+
+- **EvidenceGuard 验真**:证明引用确实来自当前 Run 的已完成 Tool 调用。
+- **SemanticGuard 验义**:判断这些真实证据是否支持报告中的结论。
+- **Release 决定出口**:只发布验证通过的原 Draft,或者发布确定性的 SafeFallback。
+
+## 3. EvidenceGuard:代码能证明的事交给代码
+
+EvidenceGuard 会检查:
+
+- Draft 结构和 analysis ID 是否合法;
+- 所有结论分析是否形成完整引用闭包;
+- `tool_call_id` 是否属于当前 Run;
+- canonical invocation 是否为 `READY`;
+- `EvidenceStatus` 与引用类型是否一致;
+- 引用的 evidence 是否确实存在于标准化 Tool 结果中。
+
+这些都是确定性问题,不需要模型判断。让模型验证自己的引用,会让同一个非确定性来源同时当作者和裁判。
+
+验证通过后,EvidenceGuard 生成 `VerifiedEvidenceSnapshot`。它只包含 SemanticGuard 所需的已验真证据、来源和 scope,不包含 Tool raw response。
+
+主要代码入口:`EvidenceGuard`、`EvidenceGuardResult`、`VerifiedEvidenceSnapshot`、`EvidenceViolation`。
+
+## 4. EvidenceRepair:为什么允许修,又为什么只能修一次
+
+有些 Draft 的业务语义可能没有问题,只是引用结构出错,例如漏写一个 analysis ID。直接丢弃会浪费一次昂贵诊断,因此系统允许一次 EvidenceRepair。
+
+但 Repair 不是第二个报告作者。它必须满足:
+
+```text
+修复前用户可见语义 == 修复后用户可见语义
+```
+
+它只能修结构和引用,不能新增事实、改变结论或补写建议;修复后还必须重新经过 EvidenceGuard。若语义视图发生变化,Repair 立即失败。
+
+只允许一次,是为了避免形成“修复失败再修复”的隐式 Agent loop,也让成本和执行路径保持可解释。
+
+## 5. SemanticGuard:引用真实不等于推论成立
+
+一条真实的 `connection timeout` 日志,可能来自下游网络问题,也可能只是故障结果,不能自动证明数据库连接池耗尽。
+
+这类支持关系无法完全用规则判断,因此 SemanticGuard 使用一次隔离的模型调用,只接收:
+
+- 用户原始问题;
+- Draft 的用户可见语义;
+- EvidenceGuard 产生的 verified snapshot。
+
+它没有 Tool、没有记忆、没有 ReAct loop、不访问 Redis,也不能改写报告。输出只有 `SUPPORTED / UNSUPPORTED` 和审计原因。
+
+这个设计没有追求一个看似精确的置信度分数。首版真正需要的是发布门禁,而未经校准的 0.73 并不能形成比二元判定更可靠的协议。
+
+主要代码入口:`SemanticGuard`、`SemanticGuardInput`、`SemanticGuardDecision`、`GuardModelCall`。
+
+## 6. Release:为什么必须只有一个出口
+
+如果 Agent、Guard、Application 都能各自构造最终结果,会出现:
+
+- 同一验证失败被不同层解释成不同文案;
+- 某个分支忘记经过 SemanticGuard;
+- 迟到的 Draft 绕过已经确定的取消或失败;
+- Fallback 混入未经验证的 Agent 内容。
+
+`DiagnosisReleaseUseCase` 因此拥有唯一发布决策。它协调 EvidenceGuard、可选 Repair、SemanticGuard 和 SafeFallbackFactory,但自己不写新根因。
+
+```mermaid
+flowchart TB
+ V{"可以安全发布正常报告吗?"}
+ V -->|"引用真实且语义受支持"| OK["ReleaseOutcome.SUCCESS
发布原 Draft"]
+ V -->|"证据不足或验证不通过"| FB["ReleaseOutcome.FALLBACK
发布 SafeFallback"]
+ V -->|"无法形成安全业务结果"| ER["ReleaseOutcome.FAILED"]
+```
+
+这里必须区分:
+
+```text
+RunState.SUCCESS + ReleaseOutcome.FALLBACK
+```
+
+这表示系统完整、安全地处理了请求,但证据不足以发布正常诊断结论。Fallback 是安全产品结果,不等于执行失败。
+
+## 7. SafeFallback:不是让另一个模型重新回答
+
+验证失败后再次让模型“写得保守一点”,仍然可能产生新事实。SafeFallback 因此由代码确定性构造,只允许使用:
+
+- 已验真的来源和检查范围;
+- 可以安全表达的观察事实;
+- 当前证据的限制;
+- 面向补充数据的下一步建议;
+- 稳定、可公开的问题类型。
+
+它不会包含 Agent 原 Draft 的未验证结论、SemanticGuard 内部原因、Provider 错误或异常栈。
+
+主要代码入口:`DiagnosisReleaseUseCase`、`EvidenceRepair`、`SafeFallbackFactory`、`DiagnosisReleaseResult`。
+
+## 8. Contract:为什么状态和结果必须类型化
+
+发布链横跨模型 JSON、Java、Redis、数据库和 SSE。如果各层都使用自由字符串 `SUCCESS`,很快就无法区分:
+
+- Tool 调用完成;
+- Tool 找到候选证据;
+- 语义审查通过;
+- Run 执行成功;
+- 最终发布正常报告。
+
+Contract 包用不同类型保持这些问题正交,例如 `InvocationStatus`、`EvidenceStatus`、`SemanticVerdict`、`RunState` 和 `ReleaseOutcome`。类型多不是为了复杂,而是为了阻止一个含糊的 `status` 穿过整个系统。
+
+## 9. 为什么没有采用其他方案
+
+| 方案 | 没有采用的原因 |
+|---|---|
+| 只检查 Tool Call ID 存在 | 只能证明引用存在,不能证明归属、状态和内容一致 |
+| 一个 Verifier Agent 同时验引用和语义 | 混合确定性与非确定性判断,失败后难以定位责任 |
+| Guard 自动改写报告 | Guard 会变成第二个作者,并可能引入未验证事实 |
+| SemanticGuard 使用 Tool 再查证 | 会形成第二条诊断链,预算和证据归属变复杂 |
+| 验证失败直接抛技术错误 | 证据不足是正常业务结果,用户仍需要安全说明 |
+| 用置信度阈值发布 | 未校准分数不能充当可靠安全协议 |
+
+## 10. 先记住这些
+
+1. Draft 是候选结果,不是已发布报告。
+2. EvidenceGuard 用代码证明引用真实,SemanticGuard 判断证据是否支持语义。
+3. Repair 只能修引用,不能改变用户可见语义,而且只尝试一次。
+4. Release 是唯一出口,只发布原 Draft 或确定性 SafeFallback。
+5. `FALLBACK` 可以对应一次正常完成的 Run。
+
+四篇组件导读到这里结束。需要回看整体路径时返回[组件学习地图](README.md)。
+
+需要深入验证规则时,阅读[证据安全链专题](../Harness证据安全链-从引用真实到结论可发布.md);需要查所有 contract 类型时,阅读[组件全景](../Harness组件全景-职责-设计原因与边界.md)。
diff --git a/mvp/engineering/harness/components/README.md b/mvp/engineering/harness/components/README.md
new file mode 100644
index 0000000..9ef19da
--- /dev/null
+++ b/mvp/engineering/harness/components/README.md
@@ -0,0 +1,22 @@
+# Harness 组件:从一次请求逐步认识
+
+这里不按照 Java 包逐个介绍组件,而是沿着一次诊断请求,分四步回答四个问题。
+
+```mermaid
+flowchart LR
+ Q["一个诊断请求来了"] --> C1["01 运行控制
怎样保证这次执行受控"]
+ C1 --> C2["02 Agent 接入与收敛
怎样让 Agent 工作但不空转"]
+ C2 --> C3["03 Tool 事实边界
怎样安全地取得事实"]
+ C3 --> C4["04 验证与发布
怎样决定结果能否交给用户"]
+```
+
+| 顺序 | 先回答的问题 | 涉及的职责域 |
+|---|---|---|
+| [01 运行控制](01-运行控制-让一次请求有边界.md) | 谁创建 Run,谁限制资源,谁记录它如何结束? | Application、Core、Retry、Audit |
+| [02 Agent 接入与收敛](02-Agent接入与收敛-让推理可以工作也可以停止.md) | 怎样使用框架 ReAct,同时阻止重复查询和无增益空转? | Agent、Progress |
+| [03 Tool 事实边界](03-Tool事实边界-让模型看到必要信息而系统保留真相.md) | Tool 原始结果由谁保存,模型究竟能看到什么? | Tool |
+| [04 验证与发布](04-验证与发布-让未经证明的结论无法越过出口.md) | 引用真实是否等于结论成立,最终由谁决定发布? | Guard、Release、Contract |
+
+建议一次只读一篇。每篇读到“先记住这些”就可以停下;类名只在最后用于定位代码。
+
+需要查全部生产类型时,再使用[组件全景参考手册](../Harness组件全景-职责-设计原因与边界.md)。