# Harness Tool 双视图:从原始结果到可验证证据 **更新日期**:2026-07-29 **主题**:ToolBoundary、Canonical Invocation、Harness Control View 与 Agent Observation **术语与状态**:[CONTEXT.md](CONTEXT.md) · [Harness生命周期与状态.md](Harness生命周期与状态.md) **上位设计**:[Harness设计-非确定性Agent的确定性控制边界.md](Harness设计-非确定性Agent的确定性控制边界.md) ## 1. 要解决的不是 Tool 调用,而是 Tool 结果的所有权 Agent 调用 Tool 后,最直接的实现是把 backend 返回的 JSON 原样放进模型上下文。这在 Demo 中可以工作,但进入可验证的诊断系统后会出现一个根本矛盾:同一份结果需要同时服务推理、控制、验真和审计,而这些消费者需要的数据范围完全不同。 以日志查询为例: - Agent 需要少量匹配事件、模式、实际时间范围和是否截断; - Harness 需要返回数量、规范化 scope、重复身份和客观证据状态; - EvidenceGuard 需要证明这份结果确实来自当前 Run 的某次 READY 调用; - 审计需要 Tool 名、调用 ID、状态、耗时和字节数; - 安全边界又要求密码、Token、主机、IP、SQL 字面量和无限日志正文不能进入模型或长期审计。 如果只保留一份 JSON,只能在两个错误方向中选择:要么信息过多导致泄露和上下文膨胀,要么信息过少导致后续无法验真。 因此这项设计的核心不是“增加一个 Redis Store”,而是重新定义 Tool 数据所有权: > backend raw 属于 Harness;模型只能获得为推理目的生成的有界观察;长期审计只保留允许运营保存的元数据。 ## 2. 三个消费者,三种数据责任 虽然实现中常称为“Tool 双视图”,完整的数据分层实际上包含三种用途: | 数据形态 | 消费者 | 解决的问题 | 生命周期 | |---|---|---|---| | Canonical Invocation | ToolBoundary、EvidenceGuard、ProgressProjector | 这次 Tool 真实执行了什么、属于哪个 Run、能否引用 | Redis 短 TTL | | Harness Control View / Agent Observation | Harness / Diagnosis Agent | 是否重复、是否为空、模型下一步需要看到什么 | 当前 Run / 模型上下文 | | Durable Audit | Trace、运营排障 | 何时调用、状态、耗时、大小、关联 Step | MySQL 长期 metadata | 所谓“双视图”,特指 canonical 标准结果被进一步分成: 1. Harness 使用的 Control View; 2. 模型使用的 Agent Observation。 Durable Audit 是第三个持久化面,但它不是 Tool 内容视图,不参与推理或证据验真。 ```mermaid flowchart LR A["Typed Tool Request"] --> B["ToolBoundary"] B --> C["Backend Raw Response"] C --> D["Tool-specific Projector"] D --> E["Canonical agent_result"] B --> F["Redis Canonical Invocation
request + raw + agent_result"] E --> F E --> G["Harness Control View
count / status / scope"] E --> H["Agent Observation
白名单、有界、脱敏"] B --> I["Durable Audit
identity / status / latency / bytes"] G --> J["重复检测、NO_GAIN、停止"] H --> K["Diagnosis Agent Context"] F --> L["EvidenceGuard / ProgressSnapshot"] ``` ## 3. 为什么不能让 Agent Observation 充当真相源 Agent Observation 是为了控制上下文而生成的投影,它可能: - 只保留前 N 条结果; - 截断单条文本; - 对敏感列和日志字段做脱敏; - 把大量事件聚合成模式; - 只暴露粗粒度相关度,不暴露检索轨迹和原始分数。 这意味着它适合帮助模型推理,却不适合作为“后台真实返回”的完整证明。如果 EvidenceGuard 反过来验证 Agent 自己收到的 observation,就相当于用被审查对象提供的摘要证明其自身真实性。 Canonical Invocation 解决的正是这个独立性问题。它以 exact `runId + tool_call_id` 建立记录,并保存: ```text tool_call_id run_id tool_name request raw_response agent_result invocation status evidence status error code started_at / completed_at ``` EvidenceGuard 不信任 Draft 中的引用,也不从模型历史反推 Tool 结果,而是重新按当前 Run 构造 key,读取 canonical record 并验证状态和投影内容。 ## 4. ToolBoundary 为什么必须是统一入口 RAG、日志和 MySQL 的业务执行方式不同,但以下控制规则完全相同: - Run 必须仍然 active; - envelope 的 runId 必须等于当前 Run; - `tool_call_id` 必须合法且不能重复; - Tool 必须已授权并声明只读; - 调用前必须预占 Tool 预算和 request bytes; - raw response 和 agent result 必须分别检查容量; - canonical 状态只能按合法路径迁移; - 对 Agent 只返回稳定错误码; - durable audit 失败不能改变已经得到的 Tool 结果。 如果把这些逻辑复制到三个 Adapter,任何新增 Tool 都可能漏掉其中一项。因此统一由 `ToolBoundary` 编排一次调用,具体 Adapter 只负责 typed request、backend 和 projector 的连接。 ```mermaid sequenceDiagram participant I as Tool Interceptor participant B as ToolBoundary participant C as Harness Core participant S as Canonical Store participant T as Backend participant P as Projector participant A as Audit Sink I->>B: RunContext + ToolCallRequestEnvelope B->>B: run / id / authorization / readonly / JSON preflight B->>C: reserve Tool call + request bytes B->>S: begin PROJECTING B->>T: execute typed request T-->>B: raw response B->>C: reserve raw bytes B->>P: project raw response P-->>B: bounded agent_result + evidence_status B->>C: reserve projection bytes B->>S: mark READY B-->>I: ToolBoundaryResult B-->>A: best-effort metadata audit ``` 错误发生时,已经创建的 canonical record 会尽力迁移到 ERROR;如果连 Store 都不可用,则向上只返回 `STORE_ERROR` 等稳定码,不把 Redis 或 backend 异常正文交给模型。 ## 5. 两套状态为什么不能合并 Tool 调用同时有两个正交维度: | 维度 | 状态 | 回答的问题 | |---|---|---| | Invocation lifecycle | `PROJECTING / READY / ERROR` | 这次调用是否完成并形成了可引用记录 | | Evidence semantics | `EVIDENCE_FOUND / NO_EVIDENCE / ERROR` | 客观结果中是否存在候选证据 | 例如日志查询成功返回 0 条: ```text InvocationStatus = READY EvidenceStatus = NO_EVIDENCE ``` 它不是 `ERROR`。这条负向观察可以支持“在指定时间、服务和查询条件下没有匹配日志”,但不能支持“故障不存在”。 合法组合被类型约束为: | InvocationStatus | EvidenceStatus | 是否允许引用 | |---|---|---:| | `PROJECTING` | `null` | 否 | | `READY` | `EVIDENCE_FOUND` | 是 | | `READY` | `NO_EVIDENCE` | 是,但只能作为限定范围负向观察 | | `ERROR` | `ERROR` | 否 | 把两者合成一个 `SUCCESS / FAILED` 会丢失最重要的信息:技术成功但业务范围内没有证据。 ## 6. 三类 Tool 如何投影 ### 6.1 RAG `RagResultProjector` 从 backend 结果中提取有界 evidence block,稳定文档身份,限制 excerpt 数量和长度,并兼容 `relevanceLevel / relevance_level` 后统一为 `relevance_level`。 这里有一个刻意保留的区分: ```text evidence 非空 -> EVIDENCE_FOUND relevance_level=REFERENCE -> 相关度一般 ``` `EVIDENCE_FOUND` 只说明存在候选内容,`REFERENCE` 也不自动表示 `NO_GAIN`。内容是否推进当前诊断假设,需要模型结合上下文判断。 ### 6.2 Logs `QueryLogsResultProjector` 不只是截取前几条日志,它还会: - 脱敏 password、token、secret、API key; - 脱敏 pod、host、PID、IP 和 SQL literal; - 去掉堆栈尾部噪声; - 对数字做模式归一化并聚合重复事件; - 对事件做跨范围采样,而不是只保留开头; - 保存实际时间、topic、query scope 和截断标记。 如果 backend 执行成功且日志数组为空,投影结果是 READY + NO_EVIDENCE。backend 明确返回失败,才是 Tool 执行或投影错误。 ### 6.3 MySQL MySQL 在投影之前还有独立的只读安全链:`MysqlSqlValidator` 根据逻辑数据源、schema、table 和 column allowlist 生成 `MysqlQueryPlan`,`JdbcMysqlReadOnlyExecutor` 只执行该 Plan。 `MysqlResultProjector` 再负责: - 限制最大行数、单元格长度和总 bytes; - 保持 number、boolean 和 null 类型; - 对 password、token、secret、credential 等敏感列强制脱敏; - 结果缩减时标记 `truncated=true`。 Validator 负责“能不能执行”,Projector 负责“模型能看到什么”,两者不能合并。 ## 7. Control View 与 Model Observation 的字段边界 | 字段 | Harness | Agent | 原因 | |---|---:|---:|---| | `tool_call_id` | 是 | 是 | 引用和上一轮评价都需要 | | 实际 scope | 是 | 是 | Harness 去重;模型理解负向观察边界 | | 有界 evidence/events/rows | 是 | 是 | 模型推理所需事实 | | `evidence_status` | 是 | 是 | 区分候选证据、空结果和错误 | | `relevance_level` | 是 | 是 | 给模型粗粒度检索语义 | | `truncated` | 是 | 是 | 防止模型误以为结果完整 | | `returned_count` | 是 | 否 | Harness 统计,不必消耗模型上下文 | | normalized scope / duplicate identity | 是 | 否 | 内部控制实现 | | 连续 NO_GAIN、阈值和剩余预算 | 是 | 否 | 防止模型围绕限制博弈 | | raw response / 检索轨迹 / 原始分数 | 是 | 否 | 敏感且体积不可控 | 只有 Harness 必须改变模型行为时,才注入 `STOP_REQUIRED`、错误码或已检查范围等有限控制信息。 ## 8. 存储决策:为什么是 Redis canonical + MySQL metadata ### 8.1 不把完整 raw 长期写 MySQL 完整 Tool 结果可能包含日志、SQL 查询结果和内部知识内容。长期保存会扩大泄露半径,也会让 JPA audit 成为第二个事实源。MySQL 只保存运营和对账需要的字段,可以长时间保留而不复制正文。 ### 8.2 Canonical 读取不续期 Redis record 在创建时设置 TTL,读取或状态更新不恢复初始 TTL。原因是:如果一次历史查询就能续期,敏感 raw 可能因审计访问而永久存在。 状态更新保留当前剩余 TTL,代价是 read-TTL-write 存在小的并发窗口,但它比无界续期更符合数据治理目标。 ### 8.3 raw 超限为什么不截断 raw 是内部事实。如果静默截断后仍标记 READY,系统无法区分“backend 只返回这些内容”和“Harness 丢掉了内容”。因此 raw 或完整 record 超限时调用进入 ERROR。 Agent projection 可以截断,因为它本来就是面向消费的摘要,但必须通过 `truncated=true` 明示不完整。 ## 9. 真实问题如何改变设计 | 真实问题 | 暴露的错误假设 | 最终修正 | |---|---|---| | Tool raw 直接进入 Agent | backend 输出天然适合模型消费 | 增加 Tool-specific projector 和白名单 observation | | Trace、raw、ContextPack、重复正文同时存在 | 多保存几份可以提高可观测性 | canonical、model view、metadata audit 明确分层 | | Mock 日志 0 命中返回 `success=false` | 没数据等于调用失败 | 技术执行状态与 NO_EVIDENCE 分离 | | RAG 的 `REFERENCE` 在投影中丢失 | 只要 evidence 非空就够了 | 保留 relevance_level,但不提升为根因证据 | | 生产 ObjectMapper 未注册 Java Time 模块,Redis 全部 STORE_ERROR | 单元测试序列化环境等同生产 | 使用生产装配验证 canonical record,并增加 live E2E | | backend 日志打印 raw/rewritten query | 排障信息可以直接进入普通日志 | 普通日志和 durable audit 只保留安全摘要 | ## 10. 代价与边界 这项设计不是免费的: 1. 每增加一种 Tool,都要同时定义 typed request/result、Adapter、Projector 和 EvidenceGuard 读取规则。 2. Redis 在 TTL 内成为证据校验依赖;不可用时系统应 fail closed,而不是相信 Draft。 3. Agent 看到的是有损投影,Projector 设计不当可能丢掉模型真正需要的诊断信号。 4. durable audit 不能替代 canonical replay;TTL 到期后只能解释调用元数据,无法恢复完整正文。 但这些成本换来了明确的责任:Backend 决定原始事实,Projector 决定模型可见范围,Canonical Store 决定短期验真事实,Audit 决定长期允许保存什么。 ## 11. 如何验证 | 验证内容 | 代表性测试或证据 | |---|---| | Run mismatch、未授权、非只读、重复 ID、预算和超限 | `ToolBoundaryTest` | | PROJECTING / READY / ERROR 和 TTL 行为 | `CanonicalInvocationStoreTest` | | RAG evidence identity、relevance 和 bytes | `RagResultProjectorTest` | | 日志脱敏、模式聚合、空结果和采样 | `QueryLogsResultProjectorTest` | | MySQL 行列/敏感字段/容量边界 | `MysqlResultProjectorTest` | | typed Tool contract 不漂移 | 三类 `*ToolContractTest` | | 生产 Redis serializer 与 ObjectMapper | single-react cleanup live E2E | 单元测试只能证明数据变换和状态机;生产 Redis serializer、实际 Tool Calling ID 和 backend 返回格式仍必须通过 live E2E 验证。 ## 12. 与后续机制的关系 Tool 双视图只解决“什么是可验证的 Tool 事实、模型允许看到什么”,并不解决: - 这些事实是否支持最终结论:见[Harness证据安全链-从引用真实到结论可发布.md](Harness证据安全链-从引用真实到结论可发布.md); - Agent 是否应该继续查询:见[Harness信息增益停止-让无证据诊断正常收敛.md](Harness信息增益停止-让无证据诊断正常收敛.md)。