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
This commit is contained in:
@@ -0,0 +1,415 @@
|
||||
# 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` |
|
||||
Reference in New Issue
Block a user