Files
SuperBizAgent-java/mvp/engineering/audit/审计设计演进-从SessionTrace到ExactRun.md
T

399 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 审计设计演进:从 Session Trace 到 Exact Run
今天看到的审计系统包含 exact Run、决策 Timeline、Agent Step、Tool Invocation、Token 对账和独立 Reasoning。它看起来像一套预先设计好的完整架构,但真实过程并不是这样。
这套设计是被一系列具体问题推出来的:先是答案无法回放,然后是 Tool 记录语义不一致,再后来是同一 Session 多轮串线,最后 Single ReAct 重构又让旧审计链失去所有权。每次变化都解决了当时最紧迫的问题,也留下了下一阶段才看得见的新缺口。
这篇文章不按提交逐条记流水账,而是解释六次能力跃迁。第一次阅读只看第 1、2、4、7 和 10 节,就能抓住主线。
## 1. 先看完整演进
```mermaid
flowchart LR
A["只能看到最终答案"] --> B["01 可查询 Trace<br/>答案可以回放"]
B --> C["02 Evidence 与质量语义<br/>记录开始可比较"]
C --> D["03 exact Run<br/>多轮不再串线"]
D --> E["04 Canonical Tool Truth<br/>事实与长期审计分层"]
E --> F["05 Single ReAct 审计重建<br/>责任回到 Harness"]
F --> G["06 Timeline / Token / Reasoning<br/>决策、成本与敏感正文分治"]
```
六个阶段分别改变了审计系统回答问题的能力:
| 阶段 | 审计开始能够回答 |
|---|---|
| 01 可查询 Trace | 一次诊断大致执行过哪些 Agent Step 和 Tool |
| 02 语义加固 | Tool 成功、无证据、失败以及 Prompt/规则版本分别是什么 |
| 03 exact Run | 这些记录究竟属于同一 Session 中的哪一轮 |
| 04 Canonical Tool Truth | 当前发布校验依赖的完整事实,与长期审计元数据如何分开 |
| 05 Single ReAct 重建 | 新 Harness 中谁负责记录 Agent 和 Tool,审计失败如何处理 |
| 06 决策、成本与敏感正文 | 决策顺序、模型成本、Reasoning 可用性和治理缺口是什么 |
这不是从“简单”机械升级到“复杂”。每一步都在重新定义审计的边界。
## 2. 第一阶段:先让最终答案可以被回放
### 当时的问题
系统已经能执行诊断并返回结果,但演示者只能展示最终答案。面试官如果继续问“Agent 调了哪些 Tool、Verifier 做了什么、证据在哪里”,只能翻数据库或日志临时解释。
### 当时的选择
2026-07-03 的 MVP Trace 变更增加了独立只读 API:
```http
GET /api/diagnosis/{sessionId}/trace
```
它直接聚合已有持久化数据:
```mermaid
flowchart LR
API["Trace API"] --> SESSION[("diagnosis_session")]
API --> STEP[("agent_step")]
API --> TOOL[("tool_invocation")]
SESSION --> RESP["聚合 Trace Response"]
STEP --> RESP
TOOL --> RESP
```
这个阶段没有改变 Chat 主流程,也没有新增数据库表。目标很克制:先把已经存在的执行记录变成一个稳定、只读、可演示的查询入口。
### 没有选择什么
- 没有把 Trace 塞进 `/api/chat` 响应;
- 没有为了演示制造全离线假运行时;
- 没有先设计通用事件平台;
- 没有重写已有 Agent 和 Tool 持久化。
### 解决了什么
系统第一次可以从最终答案回到 Agent Step 和 Tool 记录,演示流程也形成了“Chat → Trace → Feedback”的闭环。
### 新暴露的问题
Trace 能查出来,不代表记录语义已经可靠:
- 不同 Tool 的持久化路径并不统一;
- `success`、无结果和失败的含义不稳定;
- Trace 仍以 `sessionId` 为唯一身份;
- 记录能展示,但还不能稳定支撑评测。
第一阶段解决的是“有没有回放入口”,不是“审计是否已经正确”。
## 3. 第二阶段:从“有记录”走向“有稳定语义”
### 当时的问题
评测系统准备使用 Tool Trace 计算证据质量时,发现不同 Tool 对状态的表达不一致:有的调用统一走 recorder,有的在本地 helper 中写表;无命中、失败和降级路径也可能被混成一个模糊结果。
与此同时,AIOps 还是一条独立入口。它能执行自动诊断,却没有与 Chat 相同的 Trace 故事。
### 当时的选择
2026-07-04 到 07-09 的一组变化没有急着增加新表,而是先收紧契约:
1. 统一 Tool recorder 和 evidence summary 语义;
2. 区分调用成功、没有证据和真正失败;
3. 覆盖 Verifier 缺失、非法输出和 degraded path;
4. 让 AIOps 复用已有 Trace 基础设施;
5. 用 compact `prompt_audit` 和 Gatekeeper rule-set version 记录决策版本,不保存完整 Prompt。
```mermaid
flowchart TB
TOOL["Tool 调用"] --> S1["执行是否成功"]
S1 --> S2["是否找到 Evidence"]
S2 --> S3["Verifier / Gatekeeper 如何判断"]
S3 --> S4["使用哪一版 Prompt / Rule"]
S4 --> EVAL["稳定 Trace 与确定性评测"]
```
### 没有选择什么
- 没有把完整 Prompt 存进 Trace;
- 没有使用 Prompt 内容 hash 作为频繁变化的版本协议;
- 没有引入 LLM-as-judge 代替确定性 fixture;
- 没有为 AIOps 另建一套 Trace 数据模型。
### 解决了什么
审计记录开始拥有可比较的语义。评测可以区分“Tool 正常但无证据”和“Tool 本身失败”,也能知道某次判断使用了哪版 Prompt 与规则。
AIOps 当时被定义为 Chat 的兄弟入口:不同触发方式,共享同一套持久化 Trace。这在当时是合理的复用选择。
### 新暴露的问题
两个入口共享 Trace,并没有解决最根本的身份问题。只要同一个 `sessionId` 连续执行多轮,所有 Step 和 Tool 仍会混到一起。
而且审计维度越丰富,跨轮污染的破坏越大:不仅回放错误,Feedback、Verifier、评分和案例沉淀都会读错对象。
## 4. 第三阶段:Session 不是一次执行,必须引入 exact Run
### 触发它的真实故障
2026-07-10 的 E2E 使用同一个 `sessionId` 连续请求两轮。Redis 多轮上下文表现正常,但 MySQL 出现了另一种现实:
```text
diagnosis_session
query / answer 被后一轮覆盖
agent_step
第一轮 + 第二轮持续追加
tool_invocation
第一轮 + 第二轮持续追加
```
主记录表达最新一轮,明细却表达多轮混合。Trace 不再代表某一次诊断,Feedback 也不知道评价的是哪一轮结果。
### 当时的选择
系统没有只在旧表上补一个轮次字段,而是拆分两种生命周期:
```mermaid
flowchart TB
S["chat_session<br/>多轮对话目录"] --> R1["diagnosis_run 1<br/>第一轮执行"]
S --> R2["diagnosis_run 2<br/>第二轮执行"]
R1 --> D1["step / tool by run_id"]
R2 --> D2["step / tool by run_id"]
```
核心决策包括:
- `sessionId` 表示多轮会话;
- `runId` 成为正式 API 字段,表示一次可回放执行;
- `agent_step` 和 `tool_invocation` 增加 `run_id`;
- Trace、Feedback、Evaluation 和案例来源优先绑定 Run;
- 指定 `runId` 时必须校验它属于 path 中的 `sessionId`。
### 没有选择什么
- 没有继续让 `diagnosis_session` 同时承担会话与执行;
- 没有尝试根据时间戳把历史混合 Trace 伪造成多个真实 Run;
- 没有在这个阶段引入 `diagnosis_trace_event`;
- 没有立即删除旧表和旧客户端兼容。
“当时没有引入 Timeline”很重要。这个阶段只解决身份隔离,复用 `agent_step` 与 `tool_invocation` 表达 Trace;typed Timeline 是后续才增长出来的能力。
### 解决了什么
两轮 E2E 得到同一个 Session 下两个不同 Run:
- run1 exact Trace 只返回 run1;
- run2 exact Trace 只返回 run2;
- step/tool mixed row check 为 0;
- 多轮对话上下文仍然连续。
审计终于拥有稳定的最小单位:**一次 Run 可以独立回放、评分、反馈和沉淀。**
### 新暴露的问题
Run 隔离修复了“记录属于谁”,但没有回答“Tool 的完整事实由谁保管”。旧 JPA ToolInvocation 更像长期 preview,无法成为 EvidenceGuard 的独立验真来源。
兼容策略也留下了债务:不传 `runId` 时读取 latest run、旧表保留、历史 mixed trace 只能映射为 compatibility run。
## 5. 第四阶段:长期 Trace 不能同时充当证据真理源
### 当时的问题
Single ReAct + Harness 设计要求 EvidenceGuard 独立验证 Agent 引用。如果 Guard 只能读取 Agent 已经看过的裁剪结果,等于让模型用自己的输入证明自己。
旧 `tool_invocation` 又不能直接升级为完整事实存储:长期保存 raw response 会扩大敏感数据、存储体积和保留治理范围。
### 当时的选择
2026-07-21 的 ToolBoundary 设计把一次 Tool 调用拆成不同用途的数据面:
```mermaid
flowchart TB
RAW["Tool Raw Result"] --> CAN["Canonical Invocation<br/>Redis 短期完整真相"]
CAN --> OBS["Agent Observation<br/>有界投影视图"]
CAN --> GUARD["EvidenceGuard<br/>独立验真"]
CAN -.-> DUR["Durable Audit<br/>MySQL 长期元数据"]
```
Canonical Invocation 保存当前 Run 所需的 request、raw response、Agent projection、状态和时间,并拥有 `PROJECTING / READY / ERROR` 生命周期。Agent 只能获得投影结果,不能访问 Redis 或完整 raw。
### 没有选择什么
- 没有让 JPA `tool_invocation` 保存全部 raw;
- 没有让 Agent 获取 Redis key 或 canonical record;
- 没有把无证据和调用失败合并;
- 没有让 raw 超限时静默截断后继续充当完整真相。
### 解决了什么
系统第一次明确区分:
- **当前 Run 的验真事实**:完整、短期、Harness-only;
- **Agent 的推理观察**:有界、稳定、可消费;
- **长期审计记录**:适合复盘,但不复制完整 raw。
这也奠定了后来的失败语义:canonical store 缺失时无法验真,需要 fail-closed;durable audit 写入失败不应改变 Tool observation,可以 fail-open。
### 新暴露的问题
新 ToolBoundary 有了 canonical store,却尚未接回长期 `tool_invocation` 审计。与此同时,旧 recorder 依赖 ThreadLocal 和旧 Tool 副作用,不能直接带进新 Harness。
换句话说,新架构已经有了“事实”,但一度失去了“长期记录”。
## 6. 第五阶段:Single ReAct 后,审计必须重新确定所有权
### 当时的问题
旧系统的审计依附于多 Agent、`@Tool`、ThreadLocal 和旧 `AgentLoggingHook`。Single ReAct 清理删除 Planner、Executor、Verifier、Composer 和独立 AIOps 入口后,继续保留这些记录路径会出现三个问题:
- 审计逻辑仍依赖已经退出生产链的结构;
- Tool backend 可能通过旧 annotation/recorder 产生重复副作用;
- Agent hook 可能长期保存正文和 Thought,违反新的数据边界。
### 当时的选择
2026-07-22 的收尾没有给旧 recorder 增加更多兼容分支,而是重建 Harness-native 审计:
| 旧方式 | 新方式 |
|---|---|
| 多 Agent 名称和旧 Hook 决定步骤语义 | `HarnessAgentAuditHook` 记录单体 Diagnosis Agent 步骤 |
| ThreadLocal 回退传递身份 | exact RunContext / run-scoped tracker |
| Tool 自身带 recorder 副作用 | ToolBoundary 统一触发 durable audit port |
| Agent 输入输出正文与 Thought 混入普通记录 | 普通 Step 以 roles、count、Tool names、bytes 等 metadata 为主 |
| JPA 审计与 canonical truth 概念混合 | Redis canonical fail-closed,JPA durable audit fail-open |
| Chat 与 AIOps 两条公开诊断入口 | 统一 `/api/chat` + Intent Router |
### 没有选择什么
- 没有把 `/api/ai_ops` 迁移成第二个 Harness use case;
- 没有保留旧 `@Tool` 与新 ACI 双注册;
- 没有让 audit DB 故障直接改变 Agent Observation;
- 没有为了兼容继续使用旧多 Agent 身份。
### 解决了什么
审计责任终于与当前执行架构一致:Agent Step 由 Agent Hook 负责,Tool durable audit 由 ToolBoundary 负责,最终 Run 由应用和 Release 收口。
这一步也说明演进不是只做加法。07-04 保留 AIOps 兄弟入口是当时的合理方案;07-22 删除它,则是“唯一入口、唯一 Harness”新目标下的必要替换。
### 新暴露的问题
metadata-only 解决了普通 Trace 的泄漏风险,却无法满足“为什么 Agent 选择这个 Tool”的深度审计需求。Run 虽然有 Step 和 Tool,也仍缺一条统一的决策 Timeline 和完整模型成本账本。
## 7. 第六阶段:从执行记录扩展到决策、成本与 Reasoning
### 新架构暴露的新问题
真实 E2E 出现过一条失败 Run:Diagnosis Agent 重复调用 `lookup_knowledge`,累计 12 次 Tool、45,087 Token 后才进入 `BUDGET_EXHAUSTED`;Evidence Repair 还出现过结构解析失败。
旧 Trace 可以看见许多 Step 和 Tool,却不容易直接回答:
- Agent 为什么继续查询,什么时候没有信息增益;
- Token 消耗来自 Router、Agent、Repair 还是 SemanticGuard;
- Evidence、Semantic 和 Release 决策按什么顺序发生;
- Provider 是否真的返回 Reasoning,还是系统只保存了 assistant text。
### 当前选择
2026-07-23 之后,审计继续分化为三个正交能力:
1. **typed Decision Timeline**:用 RUN、ROUTING、AGENT、TOOL、EVIDENCE、SEMANTIC、RELEASE 阶段记录决定顺序;
2. **Model Token Ledger**:按组件和轮次记录 usage,在 Run 结束执行总额对账;
3. **Reasoning Audit**:独立保存 Provider Reasoning 与 assistant text,普通 Trace 只暴露可用性和 bytes 等 metadata。
```mermaid
flowchart TB
RUN["exact Run"] --> TL["Decision Timeline<br/>决定如何发生"]
RUN --> STEP["Agent Step<br/>模型轮次"]
RUN --> TOOL["Tool Invocation<br/>外部行为"]
RUN --> TOKEN["Token Ledger<br/>组件成本"]
RUN --> RSN["Reasoning Audit<br/>敏感正文"]
TL --> TRACE["普通 Trace"]
STEP --> TRACE
TOOL --> TRACE
TOKEN --> TRACE
RSN -.-> RAPI["独立受限查询"]
```
### 没有选择什么
- 没有把 Reasoning 塞回普通 Trace 或 SSE;
- 没有在 Provider 不返回 Reasoning 时伪造思考过程;
- 没有只在 Run 结束写一个无法解释的 Token 总数;
- 没有把 Timeline 变成用于重建业务状态的完整 Event Sourcing。
### 解决了什么
当前 Trace 不仅能展示“发生过哪些调用”,还可以解释决定顺序、组件成本和发布依据。`tokens_reconciled` 能检查分项与总额,`usage_available=false` 能区分真实零消耗与供应商未返回 usage。
### 仍未完成什么
- Reasoning 访问控制、保留期限和加密尚未闭环;
- 非 DeepSeek Provider 和 reasoning unavailable live 样本仍需补充;
- `agent_step.thought` 仍存在兼容镜像语义;
- latest-run 与 legacy Trace fallback 仍然存在;
- durable audit fail-open 意味着业务成功仍可能伴随审计缺口。
演进到这里,系统不再把“记录越多”当作审计完成,而开始治理哪些记录应该存在、谁能读取、缺失时如何表达。
## 8. 哪些设计被保留,哪些被替换
| 早期设计 | 当前结果 | 判断 |
|---|---|---|
| Trace 与 Chat 响应分离 | 保留独立只读 Trace API | 核心边界正确,持续保留 |
| 聚合持久化 Step/Tool | 保留,并增加 Run、Timeline 和对账 | 基础能力被扩展 |
| Session 作为 Trace 身份 | 改为 exact Run,Session 只作目录 | 被真实串线问题替换 |
| AIOps 作为兄弟入口 | 删除,统一 `/api/chat` | 阶段性合理,后续架构收敛后退出 |
| ToolInvocation 同时承担事实与审计 | 拆为 canonical truth 与 durable audit | 责任过载,被数据分层替换 |
| 旧 ThreadLocal/Tool 副作用 recorder | 改为 Harness-native Hook、Tracker 和 Audit Port | 与当前执行架构重新对齐 |
| 普通 Step 保存 Thought/正文 | 转向 metadata + 独立 Reasoning | 安全边界收紧,但兼容镜像尚未完全清除 |
| 一个 Token 总数 | 组件轮次账本 + Run 对账 | 从统计值升级为可解释成本 |
演进中真正稳定下来的不是某张表,而是三条原则:执行身份要精确、数据责任要分层、缺失和失败要诚实可见。
## 9. 为什么不能一开始就设计成现在这样
站在今天回看,很容易认为最初就应该有 Run、Timeline、Reasoning 和 canonical store。但当时缺少三个关键事实:
1. 没有多轮 E2E,就无法证明 Session 身份会造成怎样的跨轮污染;
2. 没有 EvidenceGuard,就无法看清长期 Tool audit 与当前验真 truth 的责任冲突;
3. 没有真实 Token 和 Reasoning 样本,就无法确定哪些字段应该对账、哪些正文必须隔离。
过早一次性设计完整审计平台,很可能得到通用事件总线、万能 JSON 和全量 raw 存储,却没有解决 MVP 当时最重要的问题。
这套演进采用的是另一种方式:先围绕可观察故障收紧最小契约,再让新的真实运行暴露下一层边界。
## 10. 用一张图记住设计为什么变成现在这样
```mermaid
flowchart TB
P1["答案无法解释"] --> D1["独立 Trace API"]
P2["Tool 状态语义不一致"] --> D2["Evidence / Quality 契约"]
P3["同一 Session 多轮串线"] --> D3["exact Run"]
P4["长期审计不能独立验真"] --> D4["Canonical Truth / Durable Audit 分层"]
P5["旧审计依赖旧多 Agent 与 ThreadLocal"] --> D5["Harness-native Audit"]
P6["决策顺序、成本和 Reasoning 不可解释"] --> D6["Timeline / Token / Reasoning 分治"]
D1 --> NOW["当前可回放、可对账、有数据边界的审计系统"]
D2 --> NOW
D3 --> NOW
D4 --> NOW
D5 --> NOW
D6 --> NOW
```
一句话概括:
> 审计系统最初只是把已有 Session 记录聚合出来;真实 E2E 先迫使它建立稳定 Evidence 语义,再用 exact Run 修复多轮身份,随后 Single ReAct 重构又把 Tool 真理源与长期审计分开,最终才发展出统一 Timeline、Token 对账和独立 Reasoning 治理。
## 11. 事实来源与延伸阅读
- `devflow/projects/2026-07-03-mvp-demo-trace-acceptance/`:独立只读 Trace API 的起点;
- `devflow/projects/2026-07-04-evidence-trace-hardening/`:Evidence 状态和 degraded path 契约加固;
- `devflow/projects/2026-07-04-aiops-traceable-diagnosis-entry/`:早期 AIOps 兄弟入口与共享 Trace;
- `devflow/projects/2026-07-09-interview-demo-quality-audit/`:Prompt/Rule version 与确定性质量审计;
- `devflow/projects/2026-07-10-session-run-trace-isolation/`:多轮串线、exact Run 决策和两轮 E2E;
- `devflow/projects/2026-07-21-single-react-tool-invocation-store/`:canonical Tool truth 与 durable audit 分层;
- `devflow/projects/2026-07-22-single-react-cleanup-e2e/`:Single ReAct 审计所有权重建;
- [ISS-015 诊断运行质量与 Reasoning 审计收敛](../../issues/active/ISS-015-diagnosis-runtime-quality-and-reasoning-audit.md):Timeline、Token、Reasoning 和当前缺口;
- [审计主设计](审计系统设计-从调试日志到可回放的决策证据.md):当前设计不变量与关键取舍;
- [真实 Run 案例](从一次诊断Run看审计系统如何记录决策.md):当前审计能力的具体回放方式。