# Harness 失败图谱:异常、停止、降级与终态如何对应 **更新日期**:2026-07-30 **主题**:失败分类、停止决策、安全降级、Run 终态与发布结果 **术语与状态**:[CONTEXT.md](CONTEXT.md) · [Harness生命周期与状态.md](Harness生命周期与状态.md) 在 Harness 中,“没有找到根因”“Tool 报错”“证据不够”“超时”和“客户端断开”不是同一种失败。如果把它们都压成一个 `FAILED`,用户无法知道系统是否完成了有效检查,开发者也无法判断应该重试、换 Tool、发布 Fallback,还是立即终止。 这篇文章不从状态枚举开始,而是用三个问题建立一张失败图谱: 1. 问题发生后,这次 Run 还能继续吗? 2. 已经产生的内容中,有没有可验证的安全事实? 3. 最终能公开正常报告、安全说明,还是只能失败? 第一次阅读只看第 1、2、8、10 和 13 节即可。先掌握判断方法和几个典型场景,不需要记住全部状态。 ## 1. 先记住:Harness 的失败处理不是“捕获异常” 假设一次支付超时诊断中连续发生这些事情: ```text 日志 Tool 查询成功,但指定时间段没有记录 RAG Tool 第一次调用网络超时 Agent 换了一个查询范围,找到一条可验证的配置事实 Agent 据此声称“数据库连接池耗尽” EvidenceGuard 发现报告引用的证据并不支持这个结论 ``` 如果只看局部,既出现了空结果、技术异常、有效事实,又出现了发布门禁失败。系统不能把其中任何一个事件直接等同于整次 Run 的终态。 Harness 真正要做的是分层决策: ```mermaid flowchart TB X["某个问题发生"] --> Q1{"Run 是否仍可继续?"} Q1 -->|"可以"| C["继续诊断或改查其他 Tool"] Q1 -->|"不可以"| Q2{"是否已有可验证的安全进展?"} C --> Q3{"最终报告能否通过发布门禁?"} Q3 -->|"可以"| S["ReleaseOutcome.SUCCESS
发布诊断报告"] Q3 -->|"不可以,但有安全说明"| F["ReleaseOutcome.FALLBACK
发布 SafeFallback"] Q3 -->|"没有任何安全内容"| E["ReleaseOutcome.FAILED
发布 failure"] Q2 -->|"有"| F Q2 -->|"无"| E ``` 这张图背后的核心决策是: > 局部事件决定下一步动作,只有 Run Lifecycle 和 Release 才能决定整次请求如何结束、什么可以公开。 因此,失败处理不是一个全局 `try/catch`,而是执行控制、安全事实和发布策略共同完成的结果。 ## 2. 第一类:没有答案,但系统没有坏 最容易被误判为失败的场景,是 Tool 正常执行却没有返回证据。 当前 Tool 结果使用两个正交状态: ```text InvocationStatus:PROJECTING / READY / ERROR EvidenceStatus:EVIDENCE_FOUND / NO_EVIDENCE / ERROR ``` 它们回答不同问题: | 组合 | 含义 | 是否是技术失败 | |---|---|---:| | `READY + EVIDENCE_FOUND` | Tool 成功,当前 scope 有候选证据 | 否 | | `READY + NO_EVIDENCE` | Tool 成功,当前 scope 没有候选证据 | 否 | | `ERROR + ERROR` | Tool 执行、投影或 canonical 保存失败 | 是 | `READY + NO_EVIDENCE` 只能证明“本次查询范围为空”,不能证明“整个系统不存在该问题”。例如查询 10:00 至 10:10 的支付日志为空,不代表全天没有超时,也不代表日志系统之外没有故障。 ```mermaid flowchart LR T["执行 Tool"] --> I{"InvocationStatus"} I -->|"ERROR"| X["技术失败路径"] I -->|"READY"| E{"EvidenceStatus"} E -->|"EVIDENCE_FOUND"| G["交给 Agent 判断信息增益"] E -->|"NO_EVIDENCE"| N["记录限定 scope 的空事实
累计 NO_GAIN"] N --> Q{"还值得继续查询吗?"} Q -->|"值得"| R["调整假设或 scope"] Q -->|"连续无增益"| P["CollectionState.SATURATED"] ``` 类似的正常无结果还包括: - 用户没有提供企业、服务、时间范围等必要上下文; - 多次查询都合法,但连续没有推进诊断; - Agent 遵循协议主动输出 `conclusion=null`; - 已完成有限检查,但证据只够描述现象,不够支持根因。 这些场景不应该伪装成系统异常。只要请求被正常处理并形成安全说明,就可以是: ```text RunState.SUCCESS + ReleaseOutcome.FALLBACK ``` ### 为什么不把 NO_EVIDENCE 设计成异常 如果空结果抛异常,系统会产生三个问题: - Agent 无法区分“查询为空”和“查询服务不可用”; - 监控会把正常业务分布统计成技术故障; - Release 无法向用户解释已经检查的范围。 因此放弃了“Tool 无数据即失败”的简化方案,代价是状态维度增加,但换来了可解释的控制语义。 ## 3. 第二类:技术异常可能可以重试 并非所有技术异常都应立即结束 Run。对于瞬时、明确、可能恢复的 Provider 故障,Harness 可以执行有限的显式重试。 当前重试策略遵循两个原则: ```text 只有稳定分类为可恢复的技术失败才重试 所有 attempt 都由 Harness 计数、计费并记录 ``` 典型策略如下: | 组件 | 可重试场景 | 最大 attempt | 不重试场景 | |---|---|---:|---| | Intent Router | timeout、transport、非法输出 | 2 | 已得到合法路由结果 | | SemanticGuard | timeout、transport、parse/schema failure | 2 | `UNSUPPORTED` | | Diagnosis Agent | 不执行隐藏 retry | 1 个受控业务循环 | 正常下一轮 ReAct 不是 retry | | Tool | 不执行隐藏 retry | 每个 Tool Call 一次 | Agent 可以基于结果选择其他 Tool | | EvidenceRepair | 一次显式修复机会 | 1 | 不是无限修复循环 | ```mermaid sequenceDiagram participant H as Harness participant P as Router / Semantic Provider participant B as Budget & Trace H->>B: reserve attempt #1 H->>P: request #1 P-->>H: timeout / transport / invalid output H->>B: record classified failure H->>B: reserve attempt #2 H->>P: request #2 alt 得到合法结果 P-->>H: valid result H->>B: record success else 再次技术失败 P-->>H: unavailable H->>B: record final failure H-->>H: 进入 Fallback 或 FAILED 决策 end ``` ### 为什么不使用 SDK 的默认隐藏重试 隐藏重试会让系统无法准确回答: - 这次请求实际调用了 Provider 几次; - Token、deadline 和 attempt 消耗在哪里; - Trace 中的一次调用为什么延迟异常; - 客户端取消后是否还在后台继续重试。 所以重试必须是 Harness 的控制行为,而不是各组件自行决定。代价是需要维护失败分类和 attempt 协议,但预算与审计仍然闭合。 ### `UNSUPPORTED` 为什么不重试 SemanticGuard 返回 `UNSUPPORTED`,表示它成功完成了判断,只是证据不支持报告。这是业务结果,不是技术不可用。重试同一份报告只是在要求模型重新投票,既不能创造新证据,也会削弱门禁的一致性。 ## 4. 第三类:一个 Tool 失败,不等于整个 Run 失败 Diagnosis Agent 通常拥有多个只读 Tool。某个 Tool 出现 `ERROR` 后,Agent 可能仍然可以: - 改查另一个数据源; - 缩小或调整查询范围; - 使用已经取得的其他 canonical 事实; - 明确说明某个数据源不可用,并结束为 Fallback。 ```mermaid flowchart TB E["单个 Tool ERROR"] --> A{"Run 仍 active 且预算允许?"} A -->|"否"| T["进入终止决策"] A -->|"是"| O{"是否还有合法替代动作?"} O -->|"换 Tool / 换 scope"| C["Agent 继续诊断"] O -->|"没有"| P{"已有安全进展?"} P -->|"有"| F["ReleaseOutcome.FALLBACK"] P -->|"无"| X["ReleaseOutcome.FAILED"] C --> D{"最终是否形成可发布报告?"} D -->|"是"| S["ReleaseOutcome.SUCCESS"] D -->|"否"| P ``` 这里不能建立一条简单映射: ```text Tool ERROR -> RunState.FAILED ``` 真正必须终止的情况,是错误破坏了 Harness 的安全前提,或者已经没有合法的恢复路径。例如: - canonical store 无法保存或回读 Tool 真相; - Tool raw 或 Agent result 超过硬 bytes 上限; - 路由经过允许的 attempts 后仍不可用; - Agent 输出非法 Draft,且不存在可验证的 ProgressSnapshot; - Harness 自身出现无法分类、无法形成安全响应的内部错误。 ### 为什么不让 Tool 自己决定 Run 失败 Tool 只知道一次 invocation 是否成功,不知道整个诊断还拥有多少预算、其他 Tool 是否可用、是否已有安全事实,也不知道最终发布策略。让 Tool 抛出全局终止异常,会把局部职责扩大成 Run 决策权。 当前设计的代价是 Agent 和 Application 必须处理结构化 Tool 错误,而不是依赖异常一路冒泡;收益是局部故障不会无条件摧毁整次诊断。 ## 5. 第四类:执行成功,发布仍可能被拒绝 Agent 完成 Draft 并不表示用户一定能看到这份报告。发布前还要经过两类门禁: ```text EvidenceGuard:引用是否真实、是否属于当前 Run、是否来自 READY invocation SemanticGuard:这些真实证据是否支持用户可见结论 ``` ```mermaid flowchart LR D["DiagnosisDraft"] --> E{"EvidenceGuard"} E -->|"通过"| S{"SemanticGuard"} E -->|"失败"| EF["EVIDENCE_VALIDATION_FAILED
SafeFallback"] S -->|"SUPPORTED"| R["发布 Diagnosis Report"] S -->|"UNSUPPORTED"| SU["SEMANTIC_UNSUPPORTED
SafeFallback"] S -->|"技术不可用"| SA["SEMANTIC_UNAVAILABLE
SafeFallback"] ``` 对应的典型结果是: | 发布门禁结果 | RunState | ReleaseOutcome | FallbackType | |---|---|---|---| | 两层门禁通过 | `SUCCESS` | `SUCCESS` | 无 | | EvidenceGuard 最终失败 | `SUCCESS` | `FALLBACK` | `EVIDENCE_VALIDATION_FAILED` | | SemanticGuard 判断不支持 | `SUCCESS` | `FALLBACK` | `SEMANTIC_UNSUPPORTED` | | SemanticGuard 技术不可用 | `SUCCESS` | `FALLBACK` | `SEMANTIC_UNAVAILABLE` | 这里最反直觉的一点是:Guard 拒绝发布原报告,通常仍然是 `RunState.SUCCESS`。因为 Harness 成功执行了安全策略,并向用户发布了诚实的降级结果;失败的是“原报告获得发布资格”,不是“控制系统无法完成请求”。 ### 为什么 Guard 不能直接改写结论 项目放弃了让 Verifier 或 SemanticGuard 顺手生成“更正确答案”的方案。Guard 没有业务 Tool、完整 ReAct 上下文和重新取证能力,改写报告会让审查者同时成为证据生产者。 因此 Guard 只能批准或拒绝,Release 只能发布原报告或确定性 SafeFallback。代价是部分“看起来只差一点”的报告也会降级,但发布责任保持清晰。 ## 6. 第五类:停止收集,不等于 Run 已终止 当连续查询没有信息增益,或 Agent 持续违反 progress 协议时,Harness 会把证据收集状态切换为: ```text CollectionState.SATURATED ``` 可能的停止原因包括: ```text INFORMATION_SATURATED PROGRESS_PROTOCOL_VIOLATED BUDGET_LIMIT_REACHED ``` `SATURATED` 只表示不再允许新的 evidence Tool,不是 Run 终态。Harness 仍然要给 Agent 一次机会输出 Draft,或者根据 canonical records 投影 `ProgressSnapshot`。 ```mermaid stateDiagram-v2 [*] --> COLLECTING COLLECTING --> COLLECTING: GAINED COLLECTING --> COLLECTING: NO_GAIN / 未达阈值 COLLECTING --> SATURATED: 连续 NO_GAIN 或协议违规 SATURATED --> DRAFTING: 交付一次 STOP_REQUIRED DRAFTING --> RELEASE: Agent 正常结束 DRAFTING --> TERMINATED: Agent 再次请求 Tool RELEASE --> [*] TERMINATED --> [*] ``` 这项区分解决了一个早期问题:系统过去只能依靠预算把空转“撞停”,最终把证据不足表达成 `BUDGET_EXHAUSTED` 或内部失败。引入 Collection 状态后,业务收敛可以发生在硬资源终止之前。 ## 7. 第六类:超时、预算和取消是三种不同终止 Run 的终态只有: ```text RUNNING SUCCESS FAILED CANCELLED TIMED_OUT BUDGET_EXHAUSTED ``` `RunLifecycle` 使用 first-terminal-wins:第一个成功设置的终态不可被后来返回的 Provider、Tool 或异步回调覆盖。 ### Deadline 到期 deadline 回答“这次 Run 还能否继续占用时间”。到期后终态是 `TIMED_OUT`。如果没有可发布的安全内容,Release 为 `FAILED`;当前实现不会为了美化结果在超时后继续调用模型生成说明。 ### Budget 耗尽 预算可能限制模型 attempt、Tool 调用、Token 或 bytes。终态是 `BUDGET_EXHAUSTED`,但发布结果取决于是否已有安全进展: ```text 有 ProgressSnapshot -> ReleaseOutcome.FALLBACK 没有安全进展 -> ReleaseOutcome.FAILED ``` 这说明 `RunState` 和 `ReleaseOutcome` 不能一一对应。 ### 客户端断开 客户端断开意味着公开通道已经不存在,Run 转为 `CANCELLED`。取消是协作式的:已经发出的同步 Provider 调用可能无法被物理中止,但返回后必须经过 active check 和 SSE state 检查,迟到结果不能再发布。 ```mermaid sequenceDiagram participant C as Client participant S as ChatSseSession participant H as Harness Core participant P as Provider / Tool H->>P: 已发出的同步调用 C--xS: 断开连接 S->>H: cancel Run H->>H: first terminal = CANCELLED P-->>H: 迟到结果 H->>H: checkActive 拒绝继续 H-->>S: 不发送 content / done S->>S: DISCONNECTED 拒绝迟到事件 ``` 客户端断开时不可靠地发送 `done(CANCELLED)`,因为连接已经不可用。取消事实应由服务端 Trace 和 Run 状态观察,而不是假设客户端还能收到终止事件。 ## 8. “安全进展”决定 FALLBACK 还是 FAILED 停止之后,系统不能把 Agent 的未验证草稿直接当作降级内容。所谓安全进展,必须来自当前 Run 的 canonical READY records,并经过有界投影。 `ProgressSnapshot` 可以包含: - 已实际执行的查询 scope; - 可验证的 observed facts; - 限定范围内的 `NO_EVIDENCE`; - 数据源不可用、结果截断或投影失败等 limitations; - 推荐补充的上下文或下一步检查。 它不能包含未经支持的根因结论。 ```mermaid flowchart TD T["Run 无法继续或 Agent 无结论"] --> P["从 canonical records
构建 ProgressSnapshot"] P --> V{"存在可验证的 observed facts?"} V -->|"有"| F["FALLBACK
说明已检查内容、限制和下一步"] V -->|"没有"| M{"是否明确缺少必要上下文?"} M -->|"是"| C["FALLBACK
MISSING_REQUIRED_CONTEXT"] M -->|"否"| E["FAILED
不公开未验证内容"] ``` 因此,下面两次预算耗尽可以有不同结果: | 场景 | RunState | ReleaseOutcome | 用户看到什么 | |---|---|---|---| | 已验证支付服务在目标时段无日志,随后预算耗尽 | `BUDGET_EXHAUSTED` | `FALLBACK` | 已检查范围、空结果限制和下一步 | | 第一次模型调用前预算检查失败,没有任何安全事实 | `BUDGET_EXHAUSTED` | `FAILED` | failure | ### 为什么不为所有失败生成一段“友好回答” 如果失败后再调用模型组织解释,会继续消耗已经耗尽的预算,也可能根据异常文本编造业务结论。确定性模板虽然表达能力有限,但不会把未知包装成答案。 这项设计选择了 fail closed:有安全事实才降级,没有就失败。代价是用户体验不总是“自然语言很完整”,但不会为了完整感牺牲可信度。 ## 9. 同一个结果,要从四个视图理解 Harness 没有一条能容纳全部语义的总状态机。一次请求结束后,至少要区分四个观察面: ```mermaid flowchart LR R["RunState
执行为什么停止"] --> X["一次请求"] O["ReleaseOutcome
最终公开什么"] --> X D["diagnosis_run.status
请求是否被安全处理"] --> X S["SSE 事件
客户端实际收到什么"] --> X ``` ### RunState:执行为什么停止 它由 Harness Core 拥有,表达正常完成、内部失败、取消、超时或预算耗尽。 ### ReleaseOutcome:最终公开什么 ```text SUCCESS / FALLBACK / FAILED / CANCELLED ``` 它由 Release 决定,表达正常诊断报告、安全降级、失败或取消。 ### 数据库 status:请求是否被安全处理 `JpaChatRunStore` 当前映射为: | ReleaseOutcome | diagnosis_run.status | |---|---| | `SUCCESS` | `SUCCESS` | | `FALLBACK` | `SUCCESS` | | `FAILED` | `FAILED` | | `CANCELLED` | `CANCELLED` | 因此: ```text status=SUCCESS + release_outcome=FALLBACK ``` 表示请求被正常、安全地处理并发布了降级内容,不表示系统找到了根因。 只有 `DIAGNOSIS + ReleaseOutcome.SUCCESS + published_result` 才能进入下一轮 `PreviousTurn`。Fallback 不进入后续上下文,避免把“证据不足”当作已确认结论继续传播。 ### SSE:客户端实际收到什么 公开事件顺序是: ```text metadata -> status* -> content | failure -> done ``` - `content` 最多一次; - `content` 与 `failure` 互斥; - `TERMINAL / DISCONNECTED` 后拒绝迟到结果; - 当前 `done` 使用 `ReleaseOutcome`,不是另一套遗留终态。 四个视图回答不同问题,排障时不能拿数据库 `SUCCESS` 推断用户收到了一份成功诊断报告。 ## 10. 典型场景总表 | 场景 | RunState | ReleaseOutcome | 公开内容或 FallbackType | |---|---|---|---| | EvidenceGuard、SemanticGuard 全部通过 | `SUCCESS` | `SUCCESS` | Diagnosis report | | 缺少企业、服务、时间等必要上下文 | `SUCCESS` | `FALLBACK` | `MISSING_REQUIRED_CONTEXT` | | 有限检查后仍然证据不足 | `SUCCESS` | `FALLBACK` | `INSUFFICIENT_EVIDENCE` | | 信息饱和且有安全进展 | `SUCCESS` | `FALLBACK` | `INSUFFICIENT_EVIDENCE` | | Progress 协议停止且有安全进展 | `SUCCESS` | `FALLBACK` | `INSUFFICIENT_EVIDENCE` | | EvidenceGuard 最终失败 | `SUCCESS` | `FALLBACK` | `EVIDENCE_VALIDATION_FAILED` | | SemanticGuard 判断不支持 | `SUCCESS` | `FALLBACK` | `SEMANTIC_UNSUPPORTED` | | SemanticGuard attempts 后不可用 | `SUCCESS` | `FALLBACK` | `SEMANTIC_UNAVAILABLE` | | 预算耗尽但有安全进展 | `BUDGET_EXHAUSTED` | `FALLBACK` | 当前发布为 `INSUFFICIENT_EVIDENCE` | | 超时且没有安全内容 | `TIMED_OUT` | `FAILED` | failure | | 预算耗尽且没有安全进展 | `BUDGET_EXHAUSTED` | `FAILED` | failure | | 不可恢复内部失败 | `FAILED` | `FAILED` | failure | | 客户端断开 | `CANCELLED` | `CANCELLED` | 不可靠发送 `done` | 这张表不是一个双向转换规则。例如看到 `ReleaseOutcome.FALLBACK`,不能单独推断 Run 是正常完成还是预算耗尽;仍要结合 Run termination 和 Trace。 ## 11. 排障时按什么顺序看 面对“用户为什么没有看到诊断结论”,不要先搜索所有异常日志。按发布结果向前追踪更容易定位: ```mermaid flowchart LR U["用户实际收到的 SSE"] --> O["release_outcome
fallback_type"] O --> R["Run termination
deadline / budget / cancel"] O --> G["Evidence / Semantic
Release Trace"] R --> T["Tool Invocation
Progress / canonical record"] G --> T ``` 推荐顺序: 1. 看 SSE 是 `content`、`failure`,还是连接提前断开。 2. 看 `release_outcome` 和 `FallbackType`,确认是正常报告、降级还是失败。 3. 看 Run termination,区分正常完成、超时、预算耗尽和取消。 4. 看 Release、EvidenceGuard、SemanticGuard Trace,确认原报告为什么不能发布。 5. 看 Tool 的 `InvocationStatus + EvidenceStatus`,不要只看一个 `success`。 6. 最后查看 canonical record、scope、bytes、budget usage 和 progress stop reason。 这个顺序从“用户看到什么”追到“内部为什么这样决定”,比从第一条异常开始阅读整条 Timeline 更容易建立因果关系。 ## 12. 这套设计放弃了哪些更简单的方案 ### 一个 `status` 表示一切 放弃原因:`SUCCESS` 无法同时表达 Tool 执行、Run 终止、报告发布和数据库处理结果。单状态简单,但必然丢失原因。 ### 任意异常直接终止整个 Run 放弃原因:局部 Tool 故障仍可能有替代路径;空结果也不是异常。这样做会降低系统可用性并掩盖有限检查的价值。 ### 所有错误都自动重试 放弃原因:业务拒绝、容量上限、非法输入和证据不支持不会因为重试自动恢复;隐藏重试还会破坏预算和审计。 ### Agent 自己决定何时降级 放弃原因:Agent 无法读取 canonical truth,也不能验证自己的引用。让它同时生成结论和批准发布,会形成自证循环。 ### 失败后继续调用模型润色 Fallback 放弃原因:终止后继续消耗资源,且无法保证新文本只包含已验证事实。当前使用确定性 Release Policy 和安全模板。 ### 取消时强制等待所有底层调用结束 放弃原因:同步 Provider 未必支持真正中断,等待会延长资源占用。当前采用协作式取消、active check、first-terminal-wins 和 SSE 状态拒绝迟到发布。 ## 13. 面试时如何讲这套失败设计 可以用下面这段话概括: > 我们没有把 Harness 的失败处理设计成一个全局异常捕获器,因为 Agent 系统里“没有证据、单个 Tool 失败、证据不支持结论、预算耗尽和客户端取消”代表完全不同的控制语义。系统先判断 Run 是否还能继续,再从当前 Run 的 canonical records 判断是否已有可验证进展,最后由唯一的 Release Policy 决定发布正常报告、SafeFallback 还是 failure。Tool 使用 InvocationStatus 和 EvidenceStatus 区分技术失败与正常空结果;RunState 解释执行为什么停止,ReleaseOutcome 解释用户最终看到什么,两者不做一一映射。可恢复的 Provider 故障只允许 Harness 做有限、可审计的显式重试,Guard 拒绝发布通常降级而不是把整次请求标成失败,超时、预算和取消则通过 first-terminal-wins 与 SSE 状态阻止迟到结果。这样做的目标不是让每次诊断都成功,而是保证任何结束方式都可解释、可审计,并且不会把未经验证的内容发布给用户。 这段回答要表达的不是“系统定义了很多状态”,而是:**每个状态都对应不同的决策权和失败责任。** ## 14. 当前边界与代价 1. 多套正交状态提高了准确性,也增加了学习成本,必须通过 Context、映射表和 Trace 查询规范维持统一口径。 2. 内存中的 `RunTermination.state/reason` 当前没有完整独立持久化;精确判断 `TIMED_OUT / BUDGET_EXHAUSTED` 仍需结合 Trace、异常路径和预算记录。 3. 协作式取消能阻止迟到发布,但不保证立即终止已经发出的同步 Provider 计算。 4. Tool ERROR 后是否继续由 Agent 在 Harness 门禁内选择,因此模型可能做出次优恢复动作;硬预算和停止协议负责限制损失。 5. 有安全进展才能 Fallback 的策略会让部分请求直接失败,但这是避免发布未验证内容的主动取舍。 6. `FallbackType` 枚举仍保留 `BUDGET_EXHAUSTED`,但当前预算受控停止实际统一发布 `INSUFFICIENT_EVIDENCE`;这是已知命名债务。未来若要区分资源不足与业务证据不足,需要先明确对外语义和兼容策略,不能只替换当前映射。 ## 15. 事实来源与延伸阅读 本文对应的主要实现边界: - `ChatApplicationUseCase`:Application 生命周期、Router、Run 终止和 Release 协作; - `DiagnosisHarnessCore`:Run active check、预算、终态与控制边界; - `DiagnosisAgentUseCase`:Tool loop、受控停止和 Draft 形成; - `DiagnosisReleaseUseCase`:EvidenceGuard、SemanticGuard 与唯一发布策略; - `JpaChatRunStore`:ReleaseOutcome 到数据库 status 的映射; - `ChatSseSession`:SSE 单内容、终态和断开规则。 继续阅读: - [Harness 入门](README.md) - [支付超时诊断案例](案例-从一次支付超时诊断看Harness如何控制Agent.md) - [Harness 生命周期与状态](Harness生命周期与状态.md) - [信息增益停止](Harness信息增益停止-让无证据诊断正常收敛.md) - [证据安全链](Harness证据安全链-从引用真实到结论可发布.md) - [Tool 双视图](Harness-Tool双视图-从原始结果到可验证证据.md)