Files
SuperBizAgent-java/mvp/engineering/harness/Harness生命周期与状态.md
T

441 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 不能互相替代。