441 lines
16 KiB
Markdown
441 lines
16 KiB
Markdown
# 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 不能互相替代。
|