16 KiB
Harness 生命周期与状态
更新日期:2026-07-29 状态:当前实现口径 术语前置:CONTEXT.md
1. 先澄清:系统不存在一条包含所有状态的总状态机
当前 Harness 有多组正交状态:
- RunState 控制内存执行终态;
- ChatApplicationStatus 表示用户可见处理阶段;
- InvocationStatus 表示单次 Tool invocation 生命周期;
- EvidenceStatus 表示 Tool 客观结果;
- CollectionState 表示能否继续收集证据;
- StopReason 表示停止收集的内部原因;
- SemanticVerdict 表示结论支持度;
- ReleaseOutcome 表示最终发布结果;
- SSE Session State 表示连接能否继续发送。
它们在一次请求中并行演进,只在少数边界发生映射。生命周期文档的目标不是把它们合并,而是说明谁先发生、由谁拥有、在哪里汇合。
2. 一次 Run 的主时间线
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 状态
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 和预算路径也会先确定终态,再触发取消阻止后续工作。
取消语义是协作式的:
- 后续模型、Tool、Guard 边界调用
checkActive时立即失败; - SSE 断开后不再发送 content;
- first-terminal-wins 阻止迟到成功覆盖 CANCELLED/TIMED_OUT;
- 已经进入 Provider 的同步调用是否物理停止,取决于底层客户端,Harness 不作虚假保证。
5. Application 阶段不是生命周期状态
ChatApplicationStatus 用于 SSE status 事件:
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 状态转换
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
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 生命周期
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 有自己的连接状态机:
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 --> [*]
公开事件顺序是:
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 写入:
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:
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:
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 的终止安全依赖三层协作:
RunLifecycle:first-terminal-wins,终态不可覆盖;DiagnosisHarnessCore.checkActive:每个关键边界阻止终止后的新工作;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. 生命周期不变量
- 一个 Run 只能有一个 RunTermination。
- 一个公开 SSE 最多有一次 content/failure 和一次 done。
- Tool Invocation 只有 READY 才能被引用。
- READY 必须同时拥有
EVIDENCE_FOUND或NO_EVIDENCE。 NO_EVIDENCE不能被提升为全局否定。- SATURATED 后不再执行新的 evidence Tool。
- StopReason 不直接决定公开内容,必须经过 Release。
- 有 conclusion 才执行完整 EvidenceGuard/Repair/SemanticGuard 链。
- FALLBACK 是正常发布结果,不等于 RunState.FAILED。
- 数据库 status、ReleaseOutcome 和 RunState 不能互相替代。