docs(mvp): add harness design and progressive guides
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user