docs(mvp): add harness design and progressive guides

This commit is contained in:
zhuyongxin
2026-07-29 19:04:44 +08:00
parent 584639fa2a
commit 3a7eee8af4
15 changed files with 3550 additions and 0 deletions
@@ -0,0 +1,503 @@
# Harness 设计:非确定性 Agent 的确定性控制边界
**更新日期**:2026-07-29
**适用范围**:当前 MVP Chat / Diagnosis Harness
**代码基线**:`com.superbiz.agent.harness`
**术语与状态**:[CONTEXT.md](CONTEXT.md) · [Harness生命周期与状态.md](Harness生命周期与状态.md)
**组件索引**:[Harness组件全景-职责-设计原因与边界.md](Harness组件全景-职责-设计原因与边界.md)
## 1. 结论先行
这个系统真正需要解决的,不是“怎样让模型多调用几个 Tool”,而是:
> 当业务推理由非确定性模型完成时,怎样保证每一次执行仍然有身份、有边界、有停止条件、有证据闭包,并且只发布系统能够负责的内容。
当前 Harness 给出的答案是职责分治:
- Diagnosis Agent 负责提出假设、选择 Tool、理解结果和撰写 Draft。
- Harness 负责所有必须确定的事情:Run 生命周期、预算、取消、Tool 授权、结果隔离、停止、验真、发布和审计。
- Tool 只报告客观执行结果,不宣称业务根因。
- Guard 不重新做诊断,只回答受限的验证问题。
- Release 是唯一对外发布决策点,只能发布原始安全 Draft 或确定性 SafeFallback。
这不是一个新的工作流引擎。Harness 不复制 ReAct 循环,不维护 Planner / Executor / Composer 图,也不替模型判断根因。它包围 ReAct 的不确定部分,在所有外部副作用和最终发布点建立确定性门禁。
## 2. 根问题:模型能推理,但系统必须能够负责
### 2.1 旧架构把“推理角色”当成了“安全边界”
旧方案使用 Planner、Executor、Verifier、Composer 等多个 Agent 串联。表面上每个角色各司其职,实际上每个角色都拥有一部分 Prompt、模型调用、Schema 转换、重试和 Fallback 逻辑。结果是:
1. 一次诊断的控制权分散在多个模型角色和业务 Service 中。
2. 同一种错误可能被不同层重试、改写或吞掉。
3. Verifier 既检查引用,又判断语义,还可能重写报告,成为第二个诊断者。
4. Tool 原始结果、Agent 上下文和长期审计没有明确的数据边界。
5. Run 身份依赖 ThreadLocal 传播,跨线程后无法证明一次调用属于哪个 Run。
```mermaid
flowchart LR
U["用户请求"] --> P["Planner Agent"]
P --> E["Executor Agent"]
E --> T["Tool / raw result"]
E --> V["Verifier Agent"]
V --> C["Composer Agent"]
C --> O["公开结果"]
P -.-> X1["独立 Prompt / retry / schema"]
E -.-> X2["独立 Prompt / retry / fallback"]
V -.-> X3["验真、语义判断、改写混合"]
C -.-> X4["再次生成用户事实"]
T -.-> X5["raw、trace、正文边界不清"]
H["ThreadLocal context"] -.-> P
H -.-> E
H -.-> V
classDef risk fill:#fff1f0,stroke:#cf1322,color:#5c0011;
class X1,X2,X3,X4,X5,H risk;
```
问题并不在于“Agent 数量多”本身,而在于控制职责没有单一所有者。只要预算、重试、证据和发布仍分散,多 Agent 换成 Graph 也不会自然变得可靠。
### 2.2 单 Agent 仍然不等于受控系统
把多 Agent 合并成一个 ReAct Agent,只消除了重复推理链,并没有自动解决以下问题:
- 模型可能无限改写相似查询;
- SDK 可能在 Harness 之外自动重试;
- Tool 可能返回过大、敏感或不可引用的原始数据;
- 模型可以引用其他 Run 或不存在的 Tool Call;
- 证据引用真实,不代表结论被证据支持;
- 客户端断开后,迟到的模型结果仍可能覆盖终态;
- “没有足够证据”可能被错误地发布为技术失败。
所以重构的核心不是“单 Agent”,而是“单 Agent + 确定性 Harness”。
## 3. 设计约束与不变量
以下不变量是组件拆分的依据,不是实现后的总结。
| 不变量 | 系统含义 | 由谁保证 |
|---|---|---|
| 一个 Run 只有一个终态 | 成功、失败、取消、超时、预算耗尽不能相互覆盖 | `RunLifecycle` 的 first-terminal-wins |
| 所有边界显式携带 Run 身份 | 不依赖线程绑定的隐式上下文 | `RunContext` 和 framework `tool_call_id` |
| 所有模型与 Tool 消耗可计量 | SDK 隐式 retry 不能绕过预算 | Core、Model/Tool interceptor、Harness retry |
| Tool raw 不直接进入模型 | 内部数据、体积和控制字段不能污染上下文 | ToolBoundary、canonical store、双视图投影 |
| 只有当前 Run 的 READY 调用可被引用 | 防止伪造、串 Run 和引用失败调用 | EvidenceGuard |
| 引用真实性与结论支持度分开 | 确定性规则和语义判断不互相冒充 | EvidenceGuard、SemanticGuard |
| Guard 不写报告 | 防止验证器成为第二个业务 Agent | Release Policy |
| 停止权属于 Harness | Prompt 建议不能替代系统保证 | ProgressTracker、预算和 interceptor |
| 没有根因是合法业务结果 | 证据不足不应伪装成内部故障 | `conclusion=null`、ProgressSnapshot、SafeFallback |
| 普通审计不保存敏感正文 | 可观测性不能以泄露 Prompt、raw、reasoning 为代价 | metadata-only audit、reasoning 独立存储 |
## 4. 当前总体架构
```mermaid
flowchart TB
subgraph Entry["应用入口与 Run 所有权"]
APP["ChatApplicationUseCase"]
ROUTER["IntentRouter"]
EXEC["DiagnosisChatExecutor"]
STORE["ChatRunStore"]
end
subgraph Harness["确定性 Harness 边界"]
CORE["Core<br/>context / lifecycle / budget / cancel"]
MI["Model Interceptor<br/>调用预算与 Token"]
TI["Tool Interceptor<br/>协议、重复、停止"]
TB["ToolBoundary<br/>授权、执行、canonical"]
PROGRESS["ProgressTracker<br/>信息增益与饱和"]
EG["EvidenceGuard<br/>引用真实性"]
SG["SemanticGuard<br/>结论支持度"]
RELEASE["Release Policy<br/>SUCCESS / FALLBACK"]
AUDIT["Audit / Trace<br/>metadata-only"]
end
subgraph Nondeterministic["非确定性区域"]
AGENT["Diagnosis ReAct Agent"]
MODEL["Chat Model"]
end
subgraph Evidence["证据执行与存储"]
ADAPTER["RAG / Logs / MySQL Adapter"]
BACKEND["Evidence Backend"]
CANON["Redis Canonical Invocation"]
META["MySQL Durable Metadata"]
end
APP --> CORE
APP --> ROUTER
ROUTER --> MODEL
APP --> EXEC
EXEC --> AGENT
AGENT --> MI --> MODEL
AGENT --> TI
TI --> PROGRESS
TI --> TB --> ADAPTER --> BACKEND
TB --> CANON
TB --> META
TB --> TI --> AGENT
EXEC --> EG --> SG --> RELEASE
PROGRESS --> RELEASE
RELEASE --> APP --> STORE
CORE -.-> MI
CORE -.-> TI
CORE -.-> TB
CORE -.-> EG
CORE -.-> SG
APP -.-> AUDIT
MI -.-> AUDIT
TI -.-> AUDIT
TB -.-> AUDIT
EG -.-> AUDIT
SG -.-> AUDIT
RELEASE -.-> AUDIT
```
图中的关键边界有三条:
1. **Agent 之前**:Application 创建 Run,Core 固定身份、预算和终止语义。
2. **Tool 前后**:Interceptor 管控制协议,ToolBoundary 管执行与真相,Projector 管模型可见内容。
3. **发布之前**:EvidenceGuard 验引用,SemanticGuard 验推论,Release 决定唯一公开内容。
## 5. 关键决策及设计推导
### 5.1 决策一:只保留一个拥有 Tool loop 的 Diagnosis Agent
**问题**:多角色 Agent 把同一份业务上下文在多个模型之间传递,每一跳都可能增加信息损失、重试和幻觉面。
**候选方案**:
| 方案 | 优点 | 主要问题 |
|---|---|---|
| 保留 Planner / Executor / Verifier / Composer | 角色概念直观 | 控制权分散;多套 Prompt 和重试;Verifier/Composer 会产生新事实 |
| 外层自建 ReAct / StateGraph | 流程显式 | 与框架 ReAct 重复;状态和异常路径翻倍 |
| 单 ReAct Agent + Harness | 推理上下文连续;控制边界集中 | 对 Harness 的契约和门禁设计要求更高 |
**决策**:Diagnosis Agent 是唯一业务推理者和 Draft 作者,使用框架 `ReactAgent` 自己完成 Thought / Action / Observation。项目不在外层复制循环。
**代价**:单 Agent 并不能通过“角色互审”获得表面冗余,因此必须把可机械验证的安全要求下沉到 Guard,把语义审查收缩成隔离的单轮判断。
### 5.2 决策二:RunContext 显式传播,结构不可变、状态句柄线程安全
**问题**:旧 ThreadLocal 可以在同步调用中工作,但异步 Tool、线程池和取消回调跨线程后,调用方无法证明读到的是当前 Run 的上下文。
**容易走错的方案**:把所有字段都做成不可变值对象。这样 deadline 和 identity 很干净,但预算、取消和终态不得不在外部另建全局 Map,反而形成第二真相源。
**决策**:`RunContext` 本身是 record,固定 `sessionId / runId / deadline` 和各状态句柄引用;预算、取消、生命周期、模型账本和进展 Tracker 在各自线程安全对象内变化。
```mermaid
flowchart LR
RC["RunContext<br/>结构不可变"] --> ID["sessionId / runId / deadline"]
RC --> C["RunCancellation<br/>first-reason-wins"]
RC --> B["RunBudget<br/>同步复合计数"]
RC --> L["RunLifecycle<br/>first-terminal-wins"]
RC --> M["ModelCallLedger<br/>组件/轮次账本"]
RC --> P["ProgressTracker<br/>Run 内进展"]
```
**为什么不是全局 Run Registry**:Harness 只控制当前调用,不承担 Run 查询和持久化;持久化真相仍由 `diagnosis_run` 所有,避免 Core 演变成工作流引擎。
### 5.3 决策三:关闭 SDK 隐式 retry,由 Harness 按失败类型拥有 retry
**问题**:Spring AI 默认 `maxAttempts=10`。如果 SDK 在模型边界内部自动重试,Harness 看到的一次调用可能对应多个 Provider attempt,预算、延迟、Trace 和取消都失真。
**决策**:底层 SDK retry 设为 1;仅在 Harness 中使用类型化策略:
- IntentRouter:超时、传输、非法输出最多 2 次 attempt;
- SemanticGuard:超时、传输、解析或 Schema 问题最多 2 次 attempt;
- Diagnosis Agent、业务 Tool、EvidenceRepair:1 次,不自动重试;
- 取消、预算耗尽、`NO_EVIDENCE`、业务拒绝:从不重试。
**设计理由**:重试不是通用容错开关。只有调用者知道一次失败是否幂等、是否还在 deadline 内、是否应该再次消耗预算。
**代价**:Provider 瞬时抖动更容易直接暴露,但真实 attempt 终于可计算、可审计,且不会把业务无证据误判为技术重试条件。
### 5.4 决策四:Tool 使用 canonical truth 与 Agent projection 双视图
**问题**:同一份 Tool 返回同时服务三个目标,而三个目标互相冲突:
- 证据验真需要完整、稳定、按 Run 归属的记录;
- Agent 只需要完成下一步推理的最小内容;
- 长期审计应保留身份和耗时,但不能长期保存敏感 raw。
**决策**:把 Tool 数据分成三层,而不是让一个 JSON 到处流转。
```mermaid
flowchart LR
REQ["framework tool_call_id<br/>typed request"] --> B["ToolBoundary"]
B --> RAW["backend raw response"]
RAW --> CAN["Redis canonical invocation<br/>request + raw + agent_result<br/>短 TTL"]
RAW --> PRJ["Tool-specific projector"]
PRJ --> CTRL["Harness control view"]
PRJ --> OBS["Agent observation<br/>白名单、有界"]
B --> META["Durable audit<br/>identity / status / latency / bytes"]
CTRL --> STOP["重复、NO_GAIN、停止控制"]
OBS --> AGENT["Diagnosis Agent"]
CAN --> GUARD["EvidenceGuard"]
```
**关键取舍**:
- 使用框架 `tool_call_id`,Harness 不生成第二套 ID;
- `PROJECTING / READY / ERROR` 表达调用生命周期;
- `EVIDENCE_FOUND / NO_EVIDENCE / ERROR` 表达客观结果语义;
- raw 超限直接失败,不静默截断;Agent projection 可以有界截断,但必须标记 `truncated`;
- Redis 是 TTL 内完整 Tool 真相源,MySQL 只做长期 metadata audit;
- Redis 读取不续期,避免一次历史读取无限延长敏感 raw 生命周期。
**代价**:同一次 Tool 调用需要 projector、store 和 audit 三套表达。但它们不是重复数据模型,而是分别回答“真实发生了什么”“模型允许看到什么”“长期允许保留什么”。
### 5.5 决策五:EvidenceGuard、SemanticGuard 和 Release 三段分工
**问题**:“引用是真实的”和“结论被引用支持”是两个不同命题。只做前者会放过牵强推论;都交给模型则无法确定性防止伪造 ID、跨 Run 引用或修改报告。
**决策**:
1. `EvidenceGuard` 使用纯代码检查 Draft 结构、analysis ID、Tool Call 当前 Run 所有权、READY 状态、evidence status 和引用闭包。
2. 首次引用失败时,`EvidenceRepair` 只允许修复结构和引用;修复前后用户可见语义必须一致,然后重新执行 EvidenceGuard。
3. `SemanticGuard` 只接收 query、完整 Draft 和 verified snapshot,进行无 Tool、无记忆的单轮 `SUPPORTED / UNSUPPORTED` 判断。
4. `DiagnosisReleaseUseCase` 是唯一发布决策点。Guard 无权改写最终报告。
```mermaid
sequenceDiagram
participant A as Diagnosis Agent
participant R as Release UseCase
participant E as EvidenceGuard
participant C as Canonical Store
participant X as EvidenceRepair
participant S as SemanticGuard
participant P as Public Result
A->>R: DiagnosisDraft
R->>E: validate draft references
E->>C: read exact run/tool_call_id
C-->>E: READY agent_result
alt 引用或结构错误
E-->>R: violations
R->>X: 原 Draft + violations
X-->>R: 语义不变的修复 Draft
R->>E: revalidate once
end
alt 证据闭包有效
E-->>R: verified snapshot
R->>S: query + draft + snapshot
S-->>R: SUPPORTED / UNSUPPORTED
end
alt SUPPORTED
R-->>P: 原始安全 Draft
else 任一门禁失败
R-->>P: deterministic SafeFallback
end
```
**为什么 Repair 不能修正文义**:一旦 Repair 可以修改结论,它就成为新的报告作者;此时最终内容不再是 Diagnosis Agent 的 Draft,也无法证明修复只解决了引用问题。
**为什么 SemanticGuard 不是第二个 Agent**:它没有 Tool、历史记忆或 ReAct loop,只回答一个受约束的二值审查问题,不能探索新事实或生成新结论。
### 5.6 决策六:资源预算和信息增益是两套停止机制
**问题**:预算只能回答“还能不能花资源”,不能回答“继续查询还有没有价值”。知识库只有通用资料、日志持续为空时,Agent 可以在预算范围内不断换关键词,最终以 `BUDGET_EXHAUSTED` 结束。用户得到的是技术失败,而系统实际已经知道“当前范围证据不足”。
**决策**:
- Tool 负责客观事实:是否执行成功、是否为空、实际 scope;
- Harness 机械判定空结果和完全重复的 `tool_name + normalized_scope` 为 `NO_GAIN`;
- 其他成功非空结果由模型在下一次 Tool Call Envelope 中声明 `GAINED / NO_GAIN`;
- `DiagnosisProgressTracker` 维护连续 `NO_GAIN`,达到阈值后进入 `SATURATED`;
- 连续 Envelope 协议错误使用独立计数和 `PROGRESS_PROTOCOL_VIOLATED`,不伪装成无信息增益;
- STOP_REQUIRED 只交付一次,再次请求 Tool 直接受控终止;
- `INFORMATION_SATURATED` 与 `BUDGET_LIMIT_REACHED` 始终分开记录。
```mermaid
stateDiagram-v2
[*] --> COLLECTING
COLLECTING --> COLLECTING: GAINED / 清零 NO_GAIN
COLLECTING --> COLLECTING: NO_GAIN / 未达阈值
COLLECTING --> SATURATED: 连续 NO_GAIN 达阈值
COLLECTING --> SATURATED: 连续协议错误达阈值
COLLECTING --> STOPPED: 硬预算到达
SATURATED --> DRAFT_CHANCE: 单次 STOP_REQUIRED
DRAFT_CHANCE --> STOPPED: 再次请求 Tool
DRAFT_CHANCE --> RELEASE: 输出合法 Draft
STOPPED --> RELEASE: ProgressSnapshot
```
**为什么不用 `new_count`**:跨 RAG、日志和数据库建立统一内容指纹成本高,而且“新记录”不等于“对假设有价值”。
**为什么不用独立 Progress Judge**:它会增加模型成本和新的失败点,还会把简单的空结果、重复 scope 判断模型化。
**首版边界**:重复检测只比较规范化参数,不承诺识别自然语言语义等价查询。
### 5.7 决策七:`conclusion=null` 是合法完成,不是模型失败
**问题**:如果成功的唯一含义是“必须给出根因”,Agent 在缺少企业、时间范围或错误信息时只能继续盲查,或者编造结论。
**决策**:DiagnosisDraft 允许 `conclusion=null`:
- 缺少开始查询所需信息:`MISSING_REQUIRED_CONTEXT`;
- 已完成有限检查但证据不足:`INSUFFICIENT_EVIDENCE`;
- 有结论:才进入完整 EvidenceGuard、Repair、SemanticGuard 链。
最终公开生命周期仍只有 `SUCCESS / FALLBACK / FAILED / CANCELLED`。证据不足是 `FALLBACK` 的原因,不再引入一套与 Run 终态平行的诊断状态机。
如果模型输出非法 Draft,系统会丢弃非法正文;只有当前 Run 已存在可验真的 ProgressSnapshot,才允许降级成过程型 Fallback,否则保持 fail closed。
### 5.8 决策八:可观测性记录决策证据,不复制敏感上下文
**问题**:为了调试 Agent,最直接的做法是保存 Prompt、模型正文、Tool arguments 和 raw response。但这会让普通 Trace 变成敏感数据仓库,也会造成多份事实副本。
**决策**:
- `diagnosis_trace_event` 只保存追加式、按 exact Run 排序的事件和有界 metadata;
- `ToolInvocation` 长期保存 Tool 身份、状态、耗时和字节数,不保存完整请求/响应;
- `AgentStep` 保存步骤元数据和 Token,不保存 Prompt、Tool payload;
- Provider reasoning 与 assistant text 放入独立受限审计表和接口;
- Trace 写入失败不得改变业务结果;
- Model usage 按组件和轮次入账,Run 结束做 Token 对账;
- Tool 请求被 Harness 拒绝时记录 `TOOL_REQUEST_REJECTED`,不能伪装成一次真实 `TOOL_INVOCATION`。
**尚未完成的治理**:Reasoning 的访问控制、保留期限和加密仍由 ISS-015 跟踪。已有隔离不代表完整合规闭环。
## 6. 一次诊断的完整控制流程
```mermaid
sequenceDiagram
participant U as Client
participant App as Chat Application
participant Core as Harness Core
participant Agent as Diagnosis Agent
participant TI as Tool Interceptor
participant TB as ToolBoundary
participant Store as Canonical Store
participant Guard as Guards
participant Release as Release
U->>App: query + optional sessionId
App->>Core: startRun(sessionId)
Core-->>App: RunContext(runId, deadline, handles)
App->>Agent: query + bounded safe previousTurn
loop ReAct 由框架拥有
Agent->>TI: Tool Call Envelope
TI->>TI: 应用上一轮信息增益、检查重复/饱和/协议
alt 门禁允许
TI->>TB: framework id + business input + RunContext
TB->>TB: active / auth / readonly / budget / size
TB->>Store: PROJECTING -> READY or ERROR
TB-->>TI: bounded canonical agent_result
TI-->>Agent: whitelist observation
else 信息饱和或协议停止
TI-->>Agent: one-shot STOP_REQUIRED
end
end
Agent-->>App: DiagnosisDraft 或受控停止
App->>Release: Draft + ProgressSnapshot + stop reason
alt 有结论
Release->>Guard: evidence truth + semantic support
Guard-->>Release: verified / unsupported
else 无结论或受控停止
Release->>Release: 构造确定性 Fallback
end
Release-->>App: original safe Draft or SafeFallback
App->>Core: first terminal wins
App-->>U: content/failure + done
```
## 7. 真实问题如何反向修正设计
这些不是零散 Bug 清单。每个问题都暴露了一个原设计假设不成立,并促成了边界调整。
| 现场问题 | 被证伪的假设 | 设计修正 | 固化位置 |
|---|---|---|---|
| Spring AI 默认 10 次 retry | 一次 Harness 模型调用等于一次 Provider attempt | SDK retry=1,retry 所有权上移 | Core / Retry / 配置测试 |
| ThreadLocal 跨异步边界不稳定 | 同线程上下文足以表示 Run 所有权 | 显式 `RunContext` 贯穿调用链 | Core / framework metadata |
| Tool raw 直接进入 Agent | Tool 返回可同时服务推理、验真和审计 | canonical / control / observation 三层拆分 | ToolBoundary / Projector / Store |
| 0 条日志被表达为 `success=false` | 空结果等于技术失败 | 技术执行与 `NO_EVIDENCE` 分离 | Tool contract / Projector |
| `REFERENCE` 被当作诊断证据 | 非空候选等于支持结论 | 保留相关度,语义增益交给模型 | RAG projector / Progress |
| Redis canonical 全部 `STORE_ERROR` | 单测 ObjectMapper 与生产配置行为一致 | 使用生产 ObjectMapper 能力并增加 live E2E | Store wiring / E2E |
| raw/rewritten query 出现在日志 | 可观测性可以直接打印检索输入 | 普通日志和 Trace 仅保存安全 metadata | Audit boundary |
| 空查反复改写直到预算耗尽 | 硬预算可以承担正常收敛 | 引入信息增益和饱和停止 | ProgressTracker / Interceptor |
| 9 次协议拒绝仍消耗 13 轮模型 | 协议错误会被模型自然修正 | 独立协议错误阈值和一次 STOP_REQUIRED | Progress protocol |
| 非法 Draft 导致已完成检查全部丢失 | Draft 失败意味着整个 Run 没有安全价值 | 仅在已有验真进展时发布过程型 Fallback | ProgressSnapshot / Release |
## 8. 关键决策总表
| 决策 | 选择 | 放弃的方案 | 获得的能力 | 付出的代价 |
|---|---|---|---|---|
| 推理拓扑 | 单 Diagnosis ReAct Agent | 多 Agent Graph、外层 ReAct | 上下文连续、唯一 Draft 作者 | Harness 门禁必须完整 |
| Run 上下文 | 显式 record + 状态句柄 | ThreadLocal、全局 Registry | 异步可证明、状态所有权清楚 | 参数需要显式传递 |
| Tool ID | framework `tool_call_id` | Harness 二次生成 ID | 引用链唯一 | 依赖框架 ID 契约 |
| Tool 真相 | Redis canonical,短 TTL | JPA 保存完整 raw | 可验真且限制敏感数据寿命 | Redis 可用性成为验证依赖 |
| Agent 输入 | 白名单 projection | raw / 完整 canonical 直传 | 上下文有界、减少泄露 | 需为每类 Tool 维护 projector |
| 长期审计 | metadata-only | 永久保存请求和响应 | 降低泄露与重复真相 | 深度回放受 TTL 限制 |
| 证据安全 | 确定性 EvidenceGuard + 隔离 SemanticGuard | 单一 Verifier Agent | 分清真实性和支持度 | 两段门禁增加延迟 |
| 修复 | 只修引用且语义必须不变 | Guard 重写报告 | 保持唯一作者 | 部分报告只能 Fallback |
| 停止 | 预算 + 信息增益双机制 | 只靠 Prompt 或硬上限 | 正常无证据收敛 | 首版只能做参数级重复判断 |
| 无结论 | 合法 Draft + SafeFallback | 强制根因 | 避免盲查和编造 | 调用方需理解 FALLBACK 是业务结果 |
| 发布 | 唯一 Release Policy | 各层自行 fallback | 对外语义一致 | Release 成为关键集中组件 |
## 9. Harness 明确不做什么
边界是否清晰,既看它做什么,也看它拒绝做什么:
- 不判断业务根因;
- 不实现 Planner / Executor / Verifier / Composer 角色图;
- 不在框架外复制 ReAct while-loop;
- 不让 Tool 声称结果是否支持诊断结论;
- 不把检索分数直接提升为结论可信度;
- 不依赖 Prompt 作为唯一预算或停止机制;
- 不自动重试 Diagnosis Agent 和业务 Tool;
- 不允许 Guard 生成新事实或改写结论;
- 不把 Redis canonical 变成永久审计库;
- 不承诺同步 Provider 调用一定能被立即物理中断;
- 不在首版做自然语言语义去重或跨 Tool 内容指纹。
这些非目标是在防止 Harness 再次长成一套不可维护的业务编排系统。
## 10. 如何验证设计成立
验证重点不是某个类是否被调用,而是上述不变量能否在失败和竞态下保持:
| 验证层 | 需要证明的事实 | 代表性测试/证据 |
|---|---|---|
| Core 单测 | deadline、取消、预算、first-terminal-wins、异步显式 context | `RunContextTest`、`RunBudgetTest`、`DiagnosisHarnessCoreTest` |
| Tool 单测 | cross-run、重复 ID、只读、大小、状态迁移、投影 | `ToolBoundaryTest`、`CanonicalInvocationStoreTest`、各 Projector test |
| Agent loop | framework ID、Envelope、STOP_REQUIRED、非法 Draft | `DiagnosisAgentUseCaseTest`、`HarnessToolInterceptorTest` |
| Guard / Release | 引用闭包、Repair 语义不变、Unsupported Fallback | `EvidenceGuardTest`、`SemanticGuardTest`、`DiagnosisReleaseUseCaseTest` |
| Audit | metadata 边界、Token 对账、拒绝与执行分离 | audit 包 focused tests、exact-run Trace |
| Live E2E | 生产序列化、Redis、数据库、模型和 SSE 的组合契约 | `devflow/projects/2026-07-22-single-react-cleanup-e2e/`、ISS-016 evidence |
单元测试能证明局部状态机,不能证明生产 `ObjectMapper`、Redis serializer、Provider Tool Calling 和 SSE 串联正确。因此 canonical store 的 Java Time 问题、零日志语义和协议空转都必须依靠 live E2E 反证,而不能只看 mock tests。
## 11. 当前边界与后续治理
当前设计已经建立可运行的确定性边界,但仍有明确限制:
1. 重复检测是 `tool_name + normalized_scope` 的参数等价,不识别语义近似查询。
2. Redis canonical 受 TTL 约束;TTL 过后只能依赖 metadata audit,不能重建完整证据正文。
3. 同步模型请求的取消主要阻止后续边界和迟到发布,不承诺 Provider 已执行计算立即停止。
4. SemanticGuard 仍是模型判断,只是被限制在无 Tool、无记忆、二值输出的最小范围内。
5. Reasoning 已与普通 Trace 隔离,但访问控制、保留期限和加密仍需完成。
6. 信息增益阈值默认为 2,仍需通过固定评测集持续校准,不能通过线上直觉随意调大。
## 12. 可复用的设计原则
这套 Harness 最值得复用的不是某个 Java 类,而是以下判断顺序:
1. 先列出系统必须保证的确定性不变量,再决定组件。
2. 将推理权留给模型,将授权、预算、归属、验真和发布权留给代码。
3. 不让一个数据表示同时承担内部真相、模型上下文和长期审计。
4. 不用资源耗尽代替业务收敛,也不用 Prompt 代替硬门禁。
5. 不让验证器成为第二个作者;验证失败时降级,而不是偷偷改写。
6. 把“没有足够证据”设计成一等业务结果,系统才不需要用幻觉换取成功率。
## 13. 代码与文档入口
- 完整组件说明:[Harness组件全景-职责-设计原因与边界.md](Harness组件全景-职责-设计原因与边界.md)
- 当前质量门禁:[harness-quality-gates.md](../../architecture/harness-quality-gates.md)
- Diagnosis Agent 架构:[agent-orchestration.md](../../architecture/agent-orchestration.md)
- 信息增益停止:[diagnosis-information-gain-stop-architecture.md](../../architecture/diagnosis-information-gain-stop-architecture.md)
- 核心重构 Issue:[ISS-014-single-react-agent-harness-aci-ptk-refactor.md](../../issues/archived/ISS-014-single-react-agent-harness-aci-ptk-refactor.md)
- 信息增益 Issue:[ISS-016-diagnosis-information-gain-stop-contract.md](../../issues/active/ISS-016-diagnosis-information-gain-stop-contract.md)