## Context 当前 `ToolInvocationRecorder` 将 JPA `ToolInvocation` 作为旧链路的 durable audit,保存 `output_preview`(500 字符)和 retrieval details,并通过 `SessionContextHolder` 补 session/run。它不能作为 EvidenceGuard 的 canonical source:raw response 已被截断、生命周期没有 PROJECTING/READY/ERROR、没有框架 Tool Call ID,也没有单 Run 容量/TTL 门禁。 阶段 2 已提供 `RunContext`、budget、capacity 和 `ToolCallKeyFactory`。本阶段需把所有 Tool 共用的执行边界与 canonical invocation store 建起来,供阶段 3B/3C 的 projector 直接复用。 ## Goals / Non-Goals **Goals:** - 在 Tool 调用前统一校验 Run 所有权、框架 ID、JSON object、授权、只读和 Tool/Run budget。 - 保存同一 canonical record 的完整 request/raw_response/agent_result 及状态、证据语义、时间和错误。 - 集中执行 PROJECTING -> READY/ERROR,防止未经投影的 raw 进入 Agent。 - 固定 TTL 不续期、单记录/Agent result/Run bytes 上限和 RESULT_TOO_LARGE 语义。 - 提供 Redis 实现和 store-independent Fake boundary tests。 **Non-Goals:** - 不实现 RAG/log/MySQL specific projector 或 adapter。 - 不修改旧 ToolInvocationRecorder/JPA/数据库、Chat/AIOps、Controller/SSE。 - 不让 Agent 访问 Redis/client/key/raw record。 - 不实现并发 Lua/CAS 更新、永久 audit、脱敏或跨 Run 查询。 ## Decisions ### 1. Internal ToolBoundary envelope and result `ToolCallRequestEnvelope` 是 Harness 内部输入,包含 `runId`、框架 `toolCallId`、toolName、JSON request、`authorized` 和 `readOnly`。Agent-facing DTO 不暴露这些门禁字段;后续 Alibaba ToolInterceptor 负责组装 envelope。 `ToolBoundaryResult` 只返回 invocation status、evidence status、原始框架 ID、bounded `agentResult` 或安全 errorCode;raw 只进入 store,不返回 Agent。 ### 2. Canonical record and lifecycle `CanonicalToolInvocation` 是 JSON serializable record,字段包含 toolCallId、runId、toolName、request、rawResponse、agentResult、InvocationStatus、EvidenceStatus、errorCode、startedAt、completedAt。`begin` 只接受 PROJECTING;`markReady` 只接受 PROJECTING + 非空 bounded projection + FOUND/NO_EVIDENCE;`markError` 将 evidence status 固定为 ERROR。 状态转换和 duplicate 检查在 `CanonicalInvocationStore` 内集中执行。ToolBoundary 不直接写 Redis。 ### 3. Redis value and TTL Redis 实现复用现有 `RedisTemplate`,将 record 序列化为 JSON String。创建使用 `setIfAbsent(key,json,ttl)`,保证同一 `runId+toolCallId` 不覆盖;读取不调用 expire。更新先读取剩余毫秒 TTL,再用不大于该值的 TTL 写回,避免恢复初始 TTL。过期/缺失读取返回 empty。 替代方案是 Redis Hash;单 JSON value 能保证 request/raw/agent 原子同记录,并让 Fake/序列化 schema 与 canonical record 一致,故采用。并发 projector 更新窗口是已接受风险,后续需要时再升级 Lua/CAS。 ### 4. Size and status rules `CanonicalInvocationLimits` 由 caller 提供 TTL、maxRecordBytes 和 maxAgentResultBytes。request/raw/agent 使用 UTF-8 bytes 计数;raw 超过 record 或 agent projection 超过独立上限,均不截断,记录 ERROR/RESULT_TOO_LARGE。Run bytes 通过阶段 2 Core 再做单 Run 累计门禁。 PROJECTING 时 evidence status 仅作为内部未知/ERROR 占位;READY 只接受 `EVIDENCE_FOUND` 或 `NO_EVIDENCE`;ERROR 永远不可引用。NO_EVIDENCE 不触发重试或成功解释。 ### 5. Preflight and projector boundary ToolBoundary 顺序固定: ```text RunContext active/deadline -> runId + toolCallId + authorization + read-only + JSON object -> Core.beforeToolCall + request/run capacity -> store.begin(PROJECTING) -> ToolExecutor(raw) -> raw size/capacity -> ToolResultProjector(agentResult,evidenceStatus) -> agent size/capacity -> store.markReady or markError -> bounded ToolBoundaryResult ``` 执行或投影异常都会写 ERROR;raw 已在可信边界且未超限时保留在 canonical record,但不返回 Agent。preflight/duplicate/cross-run 错误在 begin 前返回安全 ERROR。 ### 6. Old audit separation 旧 recorder/JPA 继续接收旧 Tool 调用,阶段 3A 不改其字段和 ThreadLocal fallback。新 canonical store 没有旧消费者;阶段 3B/3C 接入时必须明确写新 boundary,并在需要 durable audit 时另行脱敏摘要。 ## Module Flow ```text Alibaba ToolInterceptor (future) -> ToolCallRequestEnvelope -> ToolBoundary -> DiagnosisHarnessCore + ToolCallKeyFactory -> CanonicalInvocationStore (Redis JSON / Fake) -> ToolExecutor -> ToolResultProjector (future RAG/log/MySQL) -> bounded ToolBoundaryResult ``` ## Risks / Trade-offs - [Redis update read-TTL-write 存在并发窗口] -> 当前每个 invocation 只允许 boundary 顺序更新;后续并发需求升级 Lua/CAS。 - [canonical raw 可能敏感] -> 仅 Harness store 访问,TTL/ACL/容量受限;脱敏在 projector/durable audit 阶段处理。 - [旧 recorder 与新 store 短期并存] -> 包和接口隔离,spec 明确旧 preview 不能作为 canonical evidence。 - [preflight 失败可能没有 canonical record] -> 返回安全 ERROR 且不执行 Tool;阶段 3A 的可引用记录只针对已通过 begin 的调用。 ## Migration Plan 1. 本阶段新增 boundary/store/Redis adapter 和 fake tests,旧运行链路不变。 2. 阶段 3B/3C 将各 Tool adapter/projector 包装到本 boundary。 3. 阶段 4 Diagnosis Agent 只接收 boundary 的 bounded result。 4. 阶段 6A/7 再决定 durable audit 如何从 canonical 摘要回填,并清理旧 recorder/ThreadLocal。 ## Open Questions 无。真实 Redis 的 ACL、网络和 TTL 由最终运行/E2E 阶段验证;并发更新 Lua 化留作后续需求。