# 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 不能互相替代。