Files
SuperBizAgent-java/mvp/engineering/harness/Harness失败图谱-异常-停止-降级与终态.md

536 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<br/>发布诊断报告"]
Q3 -->|"不可以,但有安全说明"| F["ReleaseOutcome.FALLBACK<br/>发布 SafeFallback"]
Q3 -->|"没有任何安全内容"| E["ReleaseOutcome.FAILED<br/>发布 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 的空事实<br/>累计 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<br/>SafeFallback"]
S -->|"SUPPORTED"| R["发布 Diagnosis Report"]
S -->|"UNSUPPORTED"| SU["SEMANTIC_UNSUPPORTED<br/>SafeFallback"]
S -->|"技术不可用"| SA["SEMANTIC_UNAVAILABLE<br/>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<br/>构建 ProgressSnapshot"]
P --> V{"存在可验证的 observed facts?"}
V -->|"有"| F["FALLBACK<br/>说明已检查内容、限制和下一步"]
V -->|"没有"| M{"是否明确缺少必要上下文?"}
M -->|"是"| C["FALLBACK<br/>MISSING_REQUIRED_CONTEXT"]
M -->|"否"| E["FAILED<br/>不公开未验证内容"]
```
因此,下面两次预算耗尽可以有不同结果:
| 场景 | RunState | ReleaseOutcome | 用户看到什么 |
|---|---|---|---|
| 已验证支付服务在目标时段无日志,随后预算耗尽 | `BUDGET_EXHAUSTED` | `FALLBACK` | 已检查范围、空结果限制和下一步 |
| 第一次模型调用前预算检查失败,没有任何安全事实 | `BUDGET_EXHAUSTED` | `FAILED` | failure |
### 为什么不为所有失败生成一段“友好回答”
如果失败后再调用模型组织解释,会继续消耗已经耗尽的预算,也可能根据异常文本编造业务结论。确定性模板虽然表达能力有限,但不会把未知包装成答案。
这项设计选择了 fail closed:有安全事实才降级,没有就失败。代价是用户体验不总是“自然语言很完整”,但不会为了完整感牺牲可信度。
## 9. 同一个结果,要从四个视图理解
Harness 没有一条能容纳全部语义的总状态机。一次请求结束后,至少要区分四个观察面:
```mermaid
flowchart LR
R["RunState<br/>执行为什么停止"] --> X["一次请求"]
O["ReleaseOutcome<br/>最终公开什么"] --> X
D["diagnosis_run.status<br/>请求是否被安全处理"] --> X
S["SSE 事件<br/>客户端实际收到什么"] --> 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<br/>fallback_type"]
O --> R["Run termination<br/>deadline / budget / cancel"]
O --> G["Evidence / Semantic<br/>Release Trace"]
R --> T["Tool Invocation<br/>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)