Files
SuperBizAgent-java/mvp/engineering/harness/Harness progress 代码学习笔记-从拦截器五道门到唯一发布点.md
zhuyongxin 7844bcea40 docs(harness): annotate progress core classes and add code-level learning notes
- Annotate DiagnosisProgressTracker, HarnessToolInterceptor, DiagnosisProgressProjector,
  DiagnosisReleaseUseCase, ToolBoundary, ToolBoundaryResult, CanonicalToolInvocation
- Add progress code learning note: interceptor gates, tracker state machine,
  canonical lifecycle, execution gate, projection/release pipeline
- Update learning roadmap: progress marked as deeply learned, next is tool domain
2026-08-03 18:37:23 +08:00

416 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 progress 代码学习笔记:从拦截器五道门到唯一发布点
**更新日期**:2026-08-03
**主题**:Progress 子系统的代码落地——拦截器五道门、Tracker 状态机、canonical 生命周期、执行门禁、投影与发布闭环
**术语与状态**:[CONTEXT.md](CONTEXT.md) · [Harness生命周期与状态.md](Harness生命周期与状态.md)
**设计视角**:[Harness 信息增益停止-让无证据诊断正常收敛](Harness信息增益停止-让无证据诊断正常收敛.md)(讲「为什么这样设计」)
**本文视角**:代码里怎么落地(类地图、调用链、生命周期、状态机、门禁、易错点、面试话术)
## 1. 定位:双停止机制与三方判断权
### 1.1 双停止机制(progress 存在的根本理由)
预算管「能不能花」,progress 管「值不值得继续查」。让预算充当正常停止策略,会把「当前证据不足」错误表达成「系统执行失败」——这是语义错误,不是资源问题。
| 机制 | 问的问题 | 归属 |
|---|---|---|
| RunBudget | 这次 Run 最多允许消耗多少? | core 域 |
| Progress | 继续查询是否仍可能推进当前诊断? | progress 域 |
### 1.2 三方判断权(信任边界)
```mermaid
flowchart LR
T["Tool / Projector<br/>客观结果"] -->|"evidence_status:空不空(代码判)"| H
M["Diagnosis Agent<br/>语义价值"] -->|"information_gain:有没有用(模型判)"| H
H["Harness<br/>最终停止权"] -->|"scope 重复 / 连续 NO_GAIN / 协议合规"| R["停止裁决"]
```
- **谁判空**:`EVIDENCE_FOUND / NO_EVIDENCE` 是 Harness 代码判(证据数组空不空),Projector 投影时客观计算,不需要模型。
- **谁判价值**:`GAINED / NO_GAIN` 的语义价值是模型判——非空结果「有没有用」只有结合诊断上下文才能判断。
- **谁决定停止**:Harness。模型可以主动结束(输出 Draft),但不能用继续发 Tool Call 绕过 Harness 已定的饱和状态。
**两层信任边界分开**:模型输出乱来(瞎报增益)时,Harness 手里仍有 evidenceStatus 这个与模型无关的事实层(canonical 记录、审计、验真都建立在它之上)。
## 2. 类地图与调用链
### 2.1 14 个文件分类
| 类别 | 类 | 作用 |
|---|---|---|
| 状态机核心 | `DiagnosisProgressTracker` | 记账(双计数/pending/identity)+ 停止裁决 |
| 判重 | `ToolScopeNormalizer` + `ToolScopeIdentity` | 业务输入 → 稳定 scope 指纹 |
| 投影 | `DiagnosisProgressProjector` + `DiagnosisProgressProjection` | identity → 回读 canonical → 有界快照 |
| 枚举 | `InformationGain` / `DiagnosisCollectionState` / `DiagnosisStopReason` / `ProgressProtocolViolationType` | GAINED·NO_GAIN / COLLECTING·SATURATED / 三种停止原因 / 五类违规 |
| record | `PreviousObservation` / `CompletedToolCall` / `DiagnosisProgressSnapshot` / `DiagnosisProgressSnapshotState` | 协议字段 / 完成 identity / 对外快照 / 内部快照 |
| 异常 | `ProgressProtocolViolationException` | 受控协议违规 |
### 2.2 一次 Tool Call 的完整链路
```mermaid
sequenceDiagram
participant I as HarnessToolInterceptor(per-Run)
participant ET as HarnessEvidenceTools(注册表)
participant AD as Adapter(接线员)
participant TB as ToolBoundary(门禁)
participant S as CanonicalInvocationStore(真相)
participant P as Projector(投影)
I->>ET: invoke(context, toolName, toolCallId, args)
ET->>AD: bridge 包装的 invoker
AD->>TB: boundary.execute(context, envelope, executor, projector)
TB->>S: begin(PROJECTING)
TB->>TB: executor.execute(requestJson)(backend raw)
TB->>P: projector.project(raw) → agent_result + evidenceStatus
TB->>S: markReady(READY) 或 markError(ERROR)
TB-->>AD: ToolBoundaryResult(READY/ERROR)
AD-->>I: 一路 return
I->>I: 双源交叉验证 → recordCompleted → 返回有界 observation
```
### 2.3 三层关系
**持有关系**:
```
HarnessToolInterceptor ──持有──▶ RunContext(含 Tracker)/ HarnessEvidenceTools
HarnessEvidenceTools ──持有──▶ Map<toolName, EvidenceToolInvoker>(bridge 注册表)
Adapter ──持有──▶ ToolBoundary + 具体 backend + 具体 Projector
ToolBoundary ──持有──▶ DiagnosisHarnessCore / CanonicalInvocationStore / ToolCallKeyFactory
```
**接口-实现关系**:
| 接口 | 实现 |
|---|---|
| `EvidenceToolInvoker`(@FunctionalInterface) | `HarnessEvidenceTools.fromAdapters` 的 bridge |
| `AdapterCall`(内部 @FunctionalInterface) | 三个 Adapter 的 execute 方法 |
| `ToolExecutor`(@FunctionalInterface) | Adapter 里 `ignored -> legacyExecutor.execute(query)` |
| `ToolResultProjector` | `RagResultProjector` / `QueryLogsResultProjector` / `MysqlResultProjector` |
| `CanonicalInvocationStore` | `JpaCanonicalInvocationStore` 等 |
## 3. 生命周期:单例 vs per-Run
```mermaid
flowchart TD
subgraph 应用启动(一次)
A["Spring 容器装配单例 bean"]
A --> B["DiagnosisHarnessCore / ToolBoundary / Adapter / HarnessEvidenceTools / DiagnosisAgentFactory"]
end
subgraph 每次请求(多次)
C["core.startRun() → RunContext(含 new DiagnosisProgressTracker)"]
C --> D["DiagnosisAgentFactory.create(context)"]
D --> E["new HarnessModelInterceptor / HarnessToolInterceptor(绑定本 Run context)"]
E --> F["ReactAgent.call() → ReAct loop"]
F --> G["Run 结束:拦截器/ReactAgent 变成垃圾"]
end
```
| 对象 | 生命周期 | 原因 |
|---|---|---|
| core / ToolBoundary / Adapter / EvidenceTools / AgentFactory | 单例 | 无状态,只存依赖与规则 |
| RunContext | per-Run | 状态容器(Tracker/Budget/Lifecycle) |
| 两个 Interceptor | per-Run | 持有本 Run 的 RunContext |
| ReactAgent | per-Run | 框架有状态对象(loop/memory) |
**无状态体现**:单例 bean 的字段全是构造注入的依赖/配置(创建后不变);可变状态全部外置到 RunContext 和持久化存储。方法一律以 `context` 参数显式传入——同一个 ToolBoundary 实例可并发服务多个 Run,各算各的账,**状态外置是并发正确性的硬要求**(若在 bean 里存「当前预算计数」,并发 Run 会互相覆盖)。
## 4. 拦截器五道门(代码核心)
### 4.1 门卫 + 执行者合一
证据工具的 `ToolCallback` 被**故意定义为直接抛异常**(`"Harness evidence Tools require the framework Tool interceptor"`)——如果框架绕过拦截器执行 `handler.call()`,直接爆炸。这从结构上保证:**执行不可绕过门禁,校验+执行是原子操作**。
```mermaid
flowchart LR
REQ["Tool Call 到达"] --> CHK{"evidenceTools.supports?"}
CHK -->|"非证据工具"| HANDLER["handler.call() 透传"]
CHK -->|"证据工具"| GATES["五道门(校验+执行+记账)"]
```
**为什么不在 definition 里写**(四个原因):
1. 拦截器接管执行时**根本不经过 ToolCallback**——写在 definition 里永远不会执行;
2. ToolCallback 是纯函数(input→output),**拿不到 RunContext**,调不了 `context.progress()`;
3. ToolCallback 本身就是执行,**没有「执行前」时机**——判重必须在 backend 前,只有拦截器同时拥有执行前/后两个观察点;
4. progress 逻辑横跨所有证据工具,写在每工具 callback 会**复制漂移**。
### 4.2 五道门流程
```mermaid
flowchart TD
A["① 已停止检查"] -->|"SATURATED && stopInstructionDelivered"| B["抛 DiagnosisCollectionStoppedException(硬停止)"]
A --> C["② 解析 + 协议校验"]
C -->|"违规"| D["recordProgressProtocolViolation → 可修复反馈 / 达阈值饱和"]
C -->|"评价导致饱和"| E["stopRequired(软停止)"]
C --> F["③ 判重:isDuplicate(toolName, normalizedScope)"]
F -->|"重复"| G["recordDuplicateScope(直接 NO_GAIN)→ 饱和? stopRequired : 返回 DUPLICATE_SCOPE 观察"]
F --> H["④ ToolBoundary 执行(预算/canonical/审计/checkActive)"]
H -->|"READY"| I["双源交叉验证 → recordCompleted → ⑤ 收尾(NO_EVIDENCE 立即 NO_GAIN / FOUND 挂 pending / 饱和交付 stop_required)"]
H -->|"非 READY"| J["error observation(BUDGET_EXHAUSTED 额外 markBudgetLimitReached)"]
```
### 4.3 协议三类违规(第二道门)
校验的是 **Envelope 与 Tracker pending 状态的协议关系**(不是 JSON 结构——JSON 结构在 parse 用严格反序列化做了):
| 违规 | 含义 | 判定条件 |
|---|---|---|
| `UNEXPECTED_PREVIOUS_OBSERVATION` | 没欠账却带评价 | pending == null 且 observation != null |
| `MISSING_PREVIOUS_OBSERVATION` | 欠账不还 | pending != null 且 observation == null |
| `OUT_OF_ORDER_PREVIOUS_OBSERVATION` | 还错账 | observation.toolCallId() != pendingToolCallId |
另两类(parse 阶段):`MISSING_INPUT`(缺业务输入)、`INVALID_ENVELOPE`(JSON/字段非法)。
**可修复反馈**:首次违规(未达阈值)返回 `repair_required:true` 观察——带 `violation_type` / `missing_field` / `expected_previous_tool_call_id` / `allowed_information_gain` / `instruction`,让模型下一轮自愈;**连续**违规达独立阈值才饱和(PROGRESS_PROTOCOL_VIOLATED)。
### 4.4 双源交叉验证(第四道门,READY 后)
```text
源1:result.evidenceStatus() ← Projector 投影时计算并携带的声明值
源2:controlView.evidenceStatus() ← 从 agent_result 内容里解析 "evidence_status" 字段
比较:一致 ? 通过 : OBSERVATION_CONTRACT_MISMATCH 拒绝
```
本质是「自洽性防线」:同一状态被两处描述(结果对象字段 vs 内容 JSON 字段),必须一致——防止投影 bug / 数据损坏 / 构造不一致导致 progress 基于错误状态决策(如声明 FOUND 但内容空 → 挂 pending 等评价不存在的证据;声明 NO_EVIDENCE 但内容有证据 → 误记 NO_GAIN)。
### 4.5 三副 observation 面孔
| 面孔 | 触发 | 关键字段 |
|---|---|---|
| 正常执行结果 | READY 且未饱和 | 有界观察 + 可选 `stop_required` |
| `STOP_REQUIRED` | 饱和后交付一次 | `stop_required:true` + `reason` |
| 可修复协议错误 | 协议违规未达阈值 | `repair_required:true` + violation_type/missing_field/expected id/instruction |
## 5. Tracker 状态机
### 5.1 11 个字段
| 字段 | 含义 |
|---|---|
| `stopAfterConsecutiveNoGain` | 连续 NO_GAIN 阈值(默认 2,yml 可配) |
| `stopAfterConsecutiveProgressProtocolViolations` | 连续协议违规阈值(默认 2) |
| `completedScopes` | `toolName + normalizedScope` 去重集合 |
| `completedToolCalls` | 已完成调用 identity 列表(无 payload) |
| `consecutiveNoGain` | 连续无增益计数(GAINED 清零) |
| `consecutiveProgressProtocolViolations` | 连续协议违规计数(合法评价清零) |
| `collectionState` | `COLLECTING` / `SATURATED` |
| `stopReason` | `INFORMATION_SATURATED` / `BUDGET_LIMIT_REACHED` / `PROGRESS_PROTOCOL_VIOLATED` |
| `pendingToolCallId` | 待评价调用 ID(同一时刻最多一个) |
| `stopInstructionDelivered` | STOP_REQUIRED 是否已交付(只一次) |
### 5.2 两条独立计数(重点:协议违规 ≠ NO_GAIN)
```text
NO_GAIN 路径(applyGain)—— backend 执行了但没增益:
入口:applyPreviousObservation(NO_GAIN) / recordDuplicateScope() / recordCompleted(NO_EVIDENCE)
阈值:stopAfterConsecutiveNoGain(2) → SATURATED + INFORMATION_SATURATED
GAINED 清零连续计数
协议违规路径(recordProgressProtocolViolation)—— backend 根本没执行:
入口:拦截器 catch 分支
阈值:stopAfterConsecutiveProgressProtocolViolations(2) → SATURATED + PROGRESS_PROTOCOL_VIOLATED
一次合法评价(applyPreviousObservation 通过)清零
```
**为什么分开**:`NO_GAIN` 表示「Tool 执行了但没推进诊断」;协议错误表示「模型没守契约,Tool 根本没执行」。混淆会让真实空转无法归因(真实 E2E:9 次协议拒绝 + 13 轮模型调用空转)。
### 5.3 pending 协议(锚点)
```mermaid
flowchart LR
A["recordCompleted(EVIDENCE_FOUND)"] --> B["pendingToolCallId = toolCallId(挂账)"]
B --> C["模型下一轮回带 previous_observation"]
C --> D{"ID == pending ?"}
D -->|"是"| E["清 pending → applyGain"]
D -->|"否"| F["OUT_OF_ORDER 违规"]
```
- 只对**非空成功**结果设 pending;NO_EVIDENCE 不设(Harness 已自己判 NO_GAIN)。
- pending 是「唯一欠账」——保证协议逐轮、不乱序、不重评。
- 模型读完 observation 直接输出 Draft → pending 不消费也合法(不需要评价最后一轮)。
### 5.4 收集状态机
```mermaid
stateDiagram-v2
[*] --> COLLECTING
COLLECTING --> COLLECTING: GAINED / consecutiveNoGain=0
COLLECTING --> COLLECTING: NO_GAIN 未达阈值
COLLECTING --> SATURATED: NO_GAIN 达阈值 / 协议违规达阈值
SATURATED --> SATURATED: claimStopInstruction 交付一次(软停止)
SATURATED --> [*]: 模型再请求 Tool → DiagnosisCollectionStoppedException(硬停止)
```
**软/硬停止两段式**(易错点:stopReason 设置 ≠ 软停止):
```text
饱和瞬间 → stopReason 已设置(状态事实)
软停止 = 第一次交付 stop_required 观察(claimStopInstruction 返回 true,流程不中断)
→ 给模型合法输出 Draft 的机会
硬停止 = 模型无视指令再次请求 Tool → 入口检查 SATURATED && delivered
→ 抛 DiagnosisCollectionStoppedException 穿出框架 loop
```
### 5.5 为什么需要 Tracker(三层)
1. **progress 本身需要**:预算只止损,不能当正常停止策略;
2. **必须单一所有者**:「连续无增益」是跨轮次、跨入口(模型评价/代码判空/重复检测)的全局判断,分散记账会状态漂移;
3. **最小状态**:不存 payload(真相在 Canonical Store),避免第二份真相。
## 6. canonical 生命周期(tool 域衔接)
### 6.1 状态机
```mermaid
stateDiagram-v2
[不存在] --> PROJECTING: store.begin(preflight+预算通过后)
PROJECTING --> READY: store.markReady(backend 成功+投影成功)
PROJECTING --> ERROR: store.markError(任何异常)
note right of PROJECTING: 仅身份+request+startedAt
note right of READY: raw + agent_result + evidence + completedAt
note right of ERROR: raw + error_code + completedAt(禁 agent_result)
```
### 6.2 为什么分 begin/markReady/markError 三段
| 动机 | 说明 |
|---|---|
| 崩溃恢复 | backend 执行可能耗时数秒,一次写入会丢「已开始」的事实;begin 先留痕(PROJECTING = WAL) |
| 审计耗时 | 需要 startedAt(begin 记)和 completedAt(迁移记)算调用耗时 |
| 状态合法 | 每种状态只允许合法字段组合,由构造校验兜底 |
| 防篡改 | record 不可变,迁移生成新实例——READY 一旦写入不可改(证据可验真的前提) |
| 幂等 | begin 抛 `DuplicateInvocationException`(同 key 重复 begin 拒绝)→ `DUPLICATE_TOOL_CALL` |
**两类失败不落库**:requestBytes 超限、preflight 非法、重复 begin——都发生在 begin 之前,canonical 里**无记录**。
### 6.3 Store vs Tracker 分层
```text
Store(事实层):request / raw_response / agent_result / 状态 / 时间戳 —— 完整真相
Tracker(判断层):identity(引用)+ 双计数 + pending + 状态 —— 决策状态
NO_GAIN 计数不是「第二份真相」:它无法从 Store 重建
(模型评价是瞬时的、不落 Store),是独立增量状态
```
## 7. ToolBoundary 执行门禁
### 7.1 executeCanonical 五阶段
```mermaid
flowchart TD
A["阶段一:preflight + Tool 预算 + request bytes 预留 → begin(PROJECTING)"]
B["阶段二:executor.execute(requestJson)(backend raw)"]
C["阶段三:raw 大小校验 + Run bytes 预留"]
D["阶段四:projector.project(raw) → 有界 agent_result + evidenceStatus"]
E["阶段五:agent_result 校验 + Run bytes 预留 → markReady(READY)"]
A --> B --> C --> D --> E
```
**三笔 bytes 预留**:request(阶段一)/ raw(阶段三)/ agent_result(阶段五)——Run 的 bytes 预算分三个时间点消耗,任何一笔超限触发对应错误码。
### 7.2 失败路径错误码
| 失败点 | 是否已 begin | canonical | 错误码 |
|---|---|---|---|
| requestBytes 超限 / preflight 非法 / 重复 begin / Run 终态 | 否 | 无记录 | `RESULT_TOO_LARGE` / 分类错误码 / `DUPLICATE_TOOL_CALL` / `BUDGET_EXHAUSTED`·`RUN_INACTIVE` |
| executor 抛错 | 是 | ERROR | `TOOL_EXECUTION_ERROR` |
| raw 过大 / 预算 / 取消 | 是 | ERROR | `RESULT_TOO_LARGE` / `BUDGET_EXHAUSTED` / `RUN_INACTIVE` |
| 投影失败 | 是 | ERROR | `PROJECTION_ERROR` |
| agent_result 过大 / store 失败 | 是 | ERROR | `RESULT_TOO_LARGE` / `PROJECTION_ERROR` / `STORE_ERROR` |
### 7.3 checkActive 在哪
progress 拦截器流程里看不到显式 checkActive——它在 **ToolBoundary/core 层**隐式执行:`core.beforeToolCall` / `core.reserveRunBytes` 内部若 Run 已终态,抛 `RunAbortedException` → 映射为 `RUN_INACTIVE` 错误码。
## 8. 投影与发布闭环
### 8.1 停止后的数据流
```mermaid
flowchart LR
T["Tracker(identity 列表)"] --> P["DiagnosisProgressProjector"]
P --> S["CanonicalInvocationStore(回读 READY)"]
S --> P
P --> SN["ProgressSnapshot(verifiedSources + observedFacts + limitations + stopReason)"]
SN --> R["DiagnosisReleaseUseCase"]
R --> O["SUCCESS / FALLBACK(INSUFFICIENT_EVIDENCE 等)"]
```
### 8.2 Projector 有界投影
- 三重校验(`isReferencableBy` / toolCallId / toolName)通过才发布;无法验真/不可读/格式非法 → limitation,**绝不输出 raw**;
- 空结果也投影为有界事实(「该范围内未发现」)——限定范围的空查询是「已检查」的证明;
- 硬截断:`MAX_FACTS=12`、summary/scope 320 字符、source 160 字符;
- 去重:`sourceKey = type + source + scope`,`factKey = sourceKey + summary`(LinkedHashMap 保序)。
### 8.3 Release 决策树
```mermaid
flowchart TD
EX["execute(execution)"] --> D1{"draft == null?"}
D1 -->|"是"| CS["releaseControlledStop<br/>需 hasObservedFacts → INSUFFICIENT_EVIDENCE"]
D1 -->|"否"| D2{"conclusion == null?"}
D2 -->|"是"| NC["releaseNoConclusion<br/>有事实 → INSUFFICIENT_EVIDENCE<br/>missing_info → MISSING_REQUIRED_CONTEXT<br/>都没有 → fail closed"]
D2 -->|"否"| C["releaseConclusion<br/>EvidenceGuard 验引用 → repair → SemanticGuard 裁决<br/>SUPPORTED ? SUCCESS : FALLBACK"]
```
## 9. 易错点清单
| 易错 | 正确 |
|---|---|
| EVIDENCE_FOUND 累计 NO_GAIN | **NO_EVIDENCE**(空结果)才立即累计;FOUND 挂 pending 等模型评价 |
| 重复调用计入协议违规 | 重复走 **NO_GAIN** 路径(recordDuplicateScope),不是违规 |
| 软停止 = 设置 stopReason | stopReason 饱和时就设了;软停止是**交付 stop_required 观察** |
| checkActive 应该在拦截器 | 在 ToolBoundary/core 层,`RUN_INACTIVE` 错误码 |
| 拦截器直接 invoke = 绕过设计 | 是**唯一执行通道**(证据工具 handler 必炸) |
| 数据都在 Tracker | 判断层在 Tracker,**事实层在 Canonical Store** |
| 判重对比裸入参 | 对比的是**规范化指纹**(字段顺序/格式/缺省值统一后) |
| 首次协议违规直接终止 | 首次返回**可修复反馈**,连续违规才饱和 |
## 10. 面试话术合集(30 秒)
### 10.1 双停止机制
> "Harness 有两套停止机制。预算是资源门禁,管『能不能花』;Progress 是收敛门禁,管『继续查有没有价值』。核心设计是:Tool 只提供客观结果,模型负责判断语义增益,但停止权归 Harness。信息增益故意只保留 GAINED/NO_GAIN 两值,靠模型在下次 Tool Call 里用 previous_observation 回传评价;连续两次 NO_GAIN 就进入饱和,交付一次 STOP_REQUIRED 给模型合法收尾的机会,再纠缠就抛受控异常穿出框架。"
### 10.2 为什么需要 Tracker(单一所有者)
> "ProgressTracker 是 Run 内收敛控制的单一决策者:它不存证据内容,只保存最小账——已完成调用的 identity、连续 NO_GAIN 和协议违规两条计数、一个待评价的 pending ID、以及 COLLECTING/SATURATED 状态。三路输入(模型评价、代码判空、重复检测)汇入计数,GAINED 清零、NO_GAIN 累加,达阈值进入饱和,交付一次停止指令。所有方法 synchronized,是并发安全的单一所有者。"
### 10.3 拦截器为什么自己执行(必炸路径)
> "Harness 的证据工具不是『拦截后放行』——拦截器对它们既是门卫又是执行者。证据工具的 ToolCallback 被故意定义为直接抛异常,使 handler 路径成为必炸路径,从结构上保证执行不可绕过门禁;校验(判重/协议/预算)和执行(ToolBoundary/canonical)内联在同一个流程里,保证『执行前判重、执行后记录』的时机原子性,且模型拿到的永远是有界观察而不是 raw。"
### 10.4 为什么 begin/markReady/markError 三段式
> "Tool 执行是跨门禁、backend IO、校验、投影多个不可靠阶段的流程,所以 canonical 记录用 begin → markReady/markError 的追加式状态机:begin 先落 PROJECTING 保证已开始的调用必有痕迹,backend 成功且投影成功才 markReady 定案为 READY,任何异常 markError 收尾;三个状态各校验合法字段组合,record 不可变保证一旦写入不可篡改——这样崩溃可恢复、审计可对账(startedAt/completedAt 算耗时)、证据可验真(READY 不可造假),重复 begin 还能幂等拦截。"
### 10.5 谁判空谁判价值
> "空不空是客观事实,代码可判,所以 evidenceStatus 归 Harness 的 Projector;有没有用是语义判断,代码判不了(一段通用知识可能看似相关实则无用),所以 information_gain 归模型。Harness 绝不把客观状态交给模型声明,模型也绝不替 Harness 做停止裁决——两层的信任边界是分开的。"
### 10.6 无状态 bean
> "Harness 的单例 bean(core/ToolBoundary/Adapter)全部无状态:字段只有构造注入的依赖和配置,创建后不变;所有可变状态外置到每个 Run 的 RunContext(预算计数、进度 Tracker、生命周期)和持久化存储里。方法一律以 context 参数显式传入,所以同一个 bean 实例能并发服务多个 Run 而不串账——状态在参数里,不在实例里。"
## 11. 代码位置索引
| 类 | 文件 |
|---|---|
| `DiagnosisProgressTracker` | `src/main/java/com/superbiz/agent/harness/progress/DiagnosisProgressTracker.java` |
| `ToolScopeNormalizer` | `src/main/java/com/superbiz/agent/harness/progress/ToolScopeNormalizer.java` |
| `DiagnosisProgressProjector` | `src/main/java/com/superbiz/agent/harness/progress/DiagnosisProgressProjector.java` |
| `DiagnosisProgressProjection` | `src/main/java/com/superbiz/agent/harness/progress/DiagnosisProgressProjection.java` |
| progress 枚举/record/异常 | `src/main/java/com/superbiz/agent/harness/progress/`(其余 9 个文件) |
| `HarnessToolInterceptor` | `src/main/java/com/superbiz/agent/harness/agent/HarnessToolInterceptor.java` |
| `HarnessEvidenceTools` | `src/main/java/com/superbiz/agent/harness/agent/HarnessEvidenceTools.java` |
| `DiagnosisAgentFactory` | `src/main/java/com/superbiz/agent/harness/agent/DiagnosisAgentFactory.java` |
| `RagToolAdapter` | `src/main/java/com/superbiz/agent/harness/tool/adapter/RagToolAdapter.java` |
| `ToolBoundary` | `src/main/java/com/superbiz/agent/harness/tool/boundary/ToolBoundary.java` |
| `ToolBoundaryResult` | `src/main/java/com/superbiz/agent/harness/tool/boundary/ToolBoundaryResult.java` |
| `CanonicalToolInvocation` | `src/main/java/com/superbiz/agent/harness/tool/store/CanonicalToolInvocation.java` |
| `CanonicalInvocationStore` | `src/main/java/com/superbiz/agent/harness/tool/store/CanonicalInvocationStore.java` |
| `DiagnosisReleaseUseCase` | `src/main/java/com/superbiz/agent/harness/release/DiagnosisReleaseUseCase.java` |
| 装配 | `src/main/java/com/superbiz/agent/config/HarnessChatConfiguration.java` |