Agent Platform · Detail Design

可观测与 Replay 设计

Trace 记录事实,Replay 复现事实,Debug 解释差异。把每一次 Run 沉淀成可追踪、可回放、可比对的审计资产。

返回文档目录平台首页

统一 Trace 协议

RunTrace / StepTrace / ToolTrace / PolicyTrace / HITLTrace / CostTrace / ErrorTrace 共享字段框架,run_id 串起所有事件

不可篡改写入

仅追加 + 哈希链 + 异步批写,写 trace 不阻塞主流程,归档冷存放在 OSS

三模式 Replay

Pure Trace 只读复盘 / Mock Replay 重跑新逻辑 / Live Sandbox Replay 副作用走专用环境

详细设计说明

DESIGN DOCUMENT · OBSERVABILITY & REPLAY

让每一次 Agent 运行都可追踪、可回放、可对比

这份文档把 Trace、Audit、Replay 与合规查询统一成一个可观测协议。重点不是多写日志,而是把"发生了什么、为什么这么决策、再跑一次会有什么差异"三个问题变成可工程化的接口和数据。

设计主张

可观测性不是 SRE 的附属品,是企业 Agent Platform 的合规基础设施。Trace 必须不可篡改,Replay 必须不产生副作用,查询必须按租户隔离 —— 这三件事缺一项,Agent 就不能进生产。

7 类 TraceHash ChainAppend Only三模式 ReplaySSE 实时流

评审关注点

  • Trace 字段是否能跨类型关联到同一 Run
  • 不可篡改证据链是否真能挡住事后改写
  • 三种 Replay 的副作用边界是否清楚
  • 合规查询是否覆盖审计常见路径
D1统一 Schema7 类 Trace 共字段框架
D2仅追加Hash chain 不可篡改
D3异步不阻塞Stream + 批写
D4Replay 三模式Pure / Mock / Live
D5租户隔离查询行级过滤

1. 定位与概念

SECTION GOAL

说明 Trace / Replay / 审计在三层架构里的位置,回答 Run 各步骤的事实如何被收集、谁来读、为什么需要 Replay 才能闭环。

1.1 总体架构图

下图把 Trace 的写入路径、存储分层与消费面叠在同一张图:左侧是 Run 八步管道(每一步都写 Trace),中间是 Trace Recorder 收集 + Redis Stream 热缓冲 + MySQL 冷表的两段式持久化,右侧是三个消费场景 —— 合规查询接口、Replay 引擎、审计 dashboard。三种消费方都靠同一份 trace_id 链路上溯。

Observability Stack · Trace 写入与消费全景
T1 · 消费面 查询 / 审计 / Replay CONSUMERS T2 · 聚合 / 路由 Recorder + Stream BUFFER & STORE T3 · Run 内核 八步执行管道 EMIT TRACE ① 合规查询接口 按 run_id / user / time 查 REST: 单 Run 全链路 / 聚合视图 租户行级过滤 · 跳本章 §5 → 见第五章 ② Replay 引擎 三模式 / 基于 trace_id Pure · Mock · Live Sandbox 副作用按模式分级隔离 → 见第四章 ③ 审计 Dashboard SSE 实时 + 历史回看 运行中事件流 + 终态归档 配 hash chain 校验徽章 → 见第六章 Trace Recorder 收集 + 字段补全 + 哈希链 parent_trace_id 串树 异步批量 / 不阻塞 Run RECORDER · §3 Redis Stream 热缓冲 · 24h TTL SSE 推流来源 实时调试入口 HOT BUFFER MySQL Trace 表 按 trace_type 分表 仅追加 / hash 链锁定 30 天热表 · 6 月归档 WARM OSS 归档 冷存 · 6 个月+ 合规导出 不可删除策略 COLD RUN 八步管道 · 每一步都写 trace(emit 后立即返回,不阻塞) ①Run 创建RunTrace ②③④准备StepTrace ⑤PolicyPolicyTrace ⑥ToolToolTrace + Cost ⑦HITLHITLTrace ⑧Trace 收口RunTrace 终态 异常路径ErrorTrace PATH LEGEND Run → Recorder(emit) 存储链(Stream→MySQL→OSS) 消费面读取
关键约束:Run 内核只 emit 不查询;Recorder 完成 hash 链 + 字段补全后批量进 Stream;MySQL 仅追加;OSS 归档不可删除。三类消费方都靠 run_id / trace_id 反查。

1.2 时序图:Trace 写入与 Replay 重放

下面两张时序图分别展示正向写入与反向重放的交互闭环。点击 lifeline 头部色块跳到对应章节。

Scenario A · 一次成功 Run 的 Trace 全程写入

Run 八步管道每一步都向 Trace Recorder emit 一条 trace,Recorder 异步链入 Stream 并批量落 MySQL,最后 SSE 推给 dashboard。

T3 · Run 内核RUN PIPELINE Trace RecorderHASH CHAIN · §3 Redis StreamHOT BUFFER MySQL Trace 表BY TYPE · §2 SSE 推流器REALTIME · §6 查询 / DashboardCONSUMER · §5 — PHASE A · ① Run 创建 + ②③④ 准备 — 1 emit RunTrace · status=RUNNING 2 补 trace_id + hash · 入流 3 emit StepTrace × 3(②③④) 4 ↻ parent_trace_id 串树 5 3 条批量入流 — PHASE B · ⑤Policy + ⑥Tool + ⑦HITL — 6 emit PolicyTrace(PASS) 7 入流 · seq+1 8 emit ToolTrace + CostTrace 9 2 条入流 10 批写 ToolTrace + CostTrace 11 emit HITLTrace(L0_AUTO) — PHASE C · SSE 实时推流 + ⑧ 终态收口 — 12 XREAD → 转 SSE 帧 13 实时推到 Dashboard 14 ⑧ emit RunTrace · SUCCEEDED 15 flush 终态 + 关 hash 链 → MySQL
关键不变量:emit 后 Run 立即继续(不阻塞);Recorder 内部保证字段补全 + hash 链;MySQL 仅追加;终态写完后 hash 链关闭、归档可启动。
Scenario B · 基于 trace_id 的 Replay 调试

开发者基于 run_id 起一次 Mock Replay:拉历史 trace 重建 Run 上下文,新代码逻辑跑一遍,所有外部副作用走 mock。

开发者 / 审计CALLER Replay 引擎RUNNER · §4 Trace LoaderREAD MYSQL · §2 Mock LLM/ToolNO SIDE EFFECT Run 内核(mock 注入)REPLAY MODE Diff 报告NEW vs OLD 1 POST replay/<run_id> · mode=mock 2 load run + 子 trace 树 RunTrace + StepTrace × N 3 ↻ 校验 hash 链 + tenant 隔离 4 起 Run · replay_mode=mock · 注入 MockLLM/Tool 5 ⑥Tool 调 Mock 6 查 ToolTrace.output_hash → 返回历史响应 7 新 RunTrace(标 replay_of_run_id) 8 对比新旧 trace · 生成 Diff 报告 9 返回 Diff JSON · 哪些 Step 决策不同
隔离保证:Mock Replay 全程不调外部 API,不写生产 trace 表(写 replay namespace),不消耗真实 LLM token;新 trace 带 replay_of_run_id 标记便于审计回溯。

1.3 为什么需要 Replay:缺口与问题

仅靠日志和 trace 还原现场只能解决"看见",不能解决"理解"和"对比"。下表列出生产事故中常见的三类追问,及只有 Replay 才能闭环的能力差距。

追问场景仅 Trace 能给的答案Replay 才能闭环的能力
"上周这个 Run 走错 Skill,是模型问题还是 Plan 步骤问题?"看到 LLM 输入输出 hash + Plan 决策记录用新版 Plan 提示词重跑这次 input,对比走出的步骤序列
"如果上线新 Policy 规则,过去一周哪些 Run 会被拒?"看不到,Policy 逻辑是代码不是数据对历史 Run 批量 Mock Replay,统计被拒计数
"客户问当时 Agent 为什么发了那条飞书,能复盘吗?"看到工具调用记录但无法重现交互全貌Pure Trace Replay 在 UI 上按时序回放每一步
"灰度新 Harness 内核,是否影响某个 tenant 的成功率?"看到旧路径 traceLive Sandbox Replay:用历史 Request 在新代码起 Run 对比

1.4 三层职责

整个可观测体系按消费方/数据流分三层。颜色:T1 蓝=审计看板(人在看)、T2 紫=跨 Run 聚合(程序在分析)、T3 金=单 Run 写入(Run 内核 emit)。

层身份读写关系核心职责不做的事
T1 · 审计看板合规 / 客服 / 客户只读按 run_id 时间线展示;hash 链校验徽章;导出审计包不发起 Replay;不改写 trace
T2 · 跨 Run 聚合SRE / 平台 / Replay 引擎读多写少多维聚合(Skill / Tenant / 时间窗);Replay 三模式调度;Diff 报告生成不直接写 RunTrace;写 replay namespace 不污染生产
T3 · Run 内核 emitHarness 八步管道只写每步 emit RunTrace / StepTrace / ToolTrace 等;hash 链由 Recorder 接管不查 Trace;不感知 Replay 模式(mock 由 LLM/Tool 注入点切换)

2. TraceSchema · 七类 Trace 字段定义

SECTION GOAL

定义 Run 内核需要 emit 的全部 trace 类型与字段,保证每条 trace 都能从 run_id 反查上下文。

2.1 共享字段框架

所有七类 trace 共享一组通用字段,类型差异落在 metadata 子字段。共享字段是查询和聚合的统一入口,类型字段是各类 trace 的语义边界。

字段类型说明
trace_idstr当前 trace 唯一 ID(snowflake / ulid),全局唯一
parent_trace_idstr?上游 trace ID,形成树状结构(RunTrace → StepTrace → ToolTrace)
trace_typeenum七类之一:Run / Step / Tool / Policy / HITL / Cost / Error
run_idstr关联 Run,必填
session_id / user_id / agent_idstr会话 / 用户 / Agent 标识
tenant_idint租户隔离的核心字段,DAO 层强制注入
start_time / end_time / duration_msiso8601 / int时序
input_hash / output_hashstr?输入输出指纹,原文不入 trace(PII 安全)
seq_noint同 Run 内单调递增,用于重排序与 hash 链
prev_hash / self_hashstr不可篡改链字段,详见第三章
metadatadict类型特定字段,下文按类展开

2.2 RunTrace · 一次 Run 生命周期

语义:一次 Run 从创建到终态的总览记录,是其余六类 trace 的根。

写时机:Run start(status=RUNNING)、每次 state transition 增量、Run end(最终聚合)。

metadata 字段说明
job_id关联旧 Job 表,迁移期兼容投影
trigger_kindchat / inbound_event / scheduled / api
state_historyRunState 流转列表(PREPARING → AWAITING_HITL → RUNNING → SUCCEEDED 等)
terminal_stateSUCCEEDED / CLOSED_REJECTED / CLOSED_FAILED
total_tokens / total_cost聚合自子 CostTrace
error终态失败时填错误摘要(不存敏感原文)

2.3 StepTrace · 八步管道每步

语义:Harness 八步管道每一步的执行记录,parent_trace_id 指向 RunTrace。

写时机:每个 Step 进入和退出。

metadata 字段说明
step_ids1..s8
step_namerun_create / context_assemble / skill_load / plan / policy / tool / hitl / trace
state_before / state_afterRunState 转换
error步骤异常时填,关联 ErrorTrace

2.4 ToolTrace · 工具调用

语义:每一次 Tool / Skill 调用的具体记录,parent_trace_id 指向对应 StepTrace(s6)。

写时机:Tool call 开始 / 结束 / 每次重试。

metadata 字段说明
tool_call_id模型分配的调用 ID
tool_name / skill_id工具与 Skill 标识
statussuccess / failed / timeout / cancelled
retry_count重试次数
error_code / error_class失败时填
cost_tokensLLM Tool 时填,详细成本另写 CostTrace

2.5 PolicyTrace · 自动规则裁决

语义:Policy 引擎每一次裁决记录,对应 Step ⑤。

写时机:每条规则评估完毕。

metadata 字段说明
rule_set四层之一:结构性 / 权限 / 成本 / 安全护栏
rule_id具体规则 ID
decisionPASS / REJECT / WARN
reason决策原因(结构化字段,不存自由文本)
context_snapshot_hash当时上下文指纹,用于 Replay 核对

2.6 HITLTrace · 人工审批

语义:HITL 审批流程的全部状态变化,对应 Step ⑦。

写时机:审批请求 / 审批结果 / 超时升级。

metadata 字段说明
risk_levelHIGH / MEDIUM / LOW
approval_levelL0_AUTO / L1_SELF_CONFIRM / L2_REVIEWER / L3_DUAL_REVIEW
approver_id审批人 user_id
decisionAPPROVE / REJECT / SKIP / TIMEOUT
requested_at / decided_at / wait_duration_ms时序与等待时长
timeout_action超时默认行为

2.7 CostTrace · 单次成本

语义:单次 LLM / Tool 调用的成本流水,独立成表方便聚合查询。

写时机:每次 LLM call / Tool call 完成时。

metadata 字段说明
model模型 ID
input_tokens / output_tokens / cached_tokens / reasoning_tokenstoken 统计
unit_price_input / unit_price_output单价(变化时归档当时单价)
total_cost / cost_currency单次成本

2.8 ErrorTrace · 异常上下文

语义:任意 Step 中发生异常时的额外上下文记录,关联具体 StepTrace。

写时机:异常发生时(先于 StepTrace 关闭)。

metadata 字段说明
error_class异常类名
error_code业务错误码
error_message摘要(敏感原文不入)
stack_trace_hash栈指纹(原栈写归档)
recovered_action重试 / 降级 / 熔断 / 终止
retry_count / fatal重试次数 / 是否不可恢复

3. 不可篡改写入

SECTION GOAL

说明 Trace 写入如何同时满足"不阻塞主流程"与"事后无法改写"两条硬约束,让审计在法律和合规层面站得住。

3.1 三个不可妥协的约束

约束含义实现要点
仅追加Trace 表只允许 INSERT,不允许 UPDATE / DELETEDB 层关闭对应权限;只有专用归档迁移账号能 INSERT 到 cold 表
哈希链锁定同 Run 内每条 trace 的 self_hash 包含 prev_hash,链尾在终态固化Recorder 串行计算 hash;终态写入时把 chain_root 落 RunTrace
异步不阻塞Run 内核 emit 后立即返回;写盘失败不能让 Run 失败内存收集 + Stream 缓冲 + 批量 ColdWriter;失败重试 + 旁路告警

3.2 Hash chain 形成方式

每条 trace 的 self_hash 由两部分输入:当前 trace 的归一化字段(除 hash 字段外)、上一条 trace 的 self_hash(同 Run 内按 seq_no 取上一条;Run 首条以 run_id 起种)。Run 终态时 RunTrace 记录 chain_root_hash = 链尾 hash,作为整个 Run 的不可篡改锚。

位置字段来源
每条 traceprev_hash同 Run 内 seq_no - 1 的 self_hash;首条用 sha256(run_id) 起种
每条 traceself_hashsha256(归一化 payload + prev_hash)
RunTrace 终态chain_root_hash该 Run 最后一条 trace 的 self_hash
OSS 归档归档清单chain_root_hash + run_id 摘要列表,定期签名

3.3 异步批写链路

从 Run 内核到 MySQL 中间分三段缓冲,每段都有降级策略:

3.4 存储分层与 TTL

层介质TTL用途不可变保证
HotRedis Stream24h实时 SSE 推流 / 调试读 only API
WarmMySQL trace 表30 天业务运营查询 / 一般审计仅追加 + 行级 hash 校验
ColdOSS 归档6 个月+合规审计 / 长期存证对象不可删除策略 + chain_root 签名清单

4. Replay 三模式

SECTION GOAL

说明 Pure / Mock / Live Sandbox 三种 Replay 的能力边界、副作用控制和适用场景,让评审者一眼判断"在生产能不能跑"。

4.1 三模式总览

模式是否重跑代码外部副作用典型用途能在生产跑吗
Pure Trace Replay不重跑,仅按时序重建 UI零副作用客户复盘 / 客服解释 / 审计追溯可以,仅读
Mock Replay重跑 HarnessPipeline,LLM/Tool 走 mock不调外部、不扣费、不写生产表新代码回归 / Policy 改动批量验证可以,写 replay namespace
Live Sandbox Replay重跑全套,外部走 sandbox endpoint调专用沙箱(飞书测试号 / 测试多维表)新旧 Harness A/B / 端到端基线不可以,仅在 Replay 专用环境

4.2 Pure Trace Replay · 只读回放

用途:复盘 / 审计 / 给客户讲清"当时发生了什么"。

行为:从 Redis Stream 或 MySQL 拉 RunTrace + 全部子 trace,按 seq_no 在 UI 还原八步管道每一步的输入指纹、输出指纹、决策结果;不重新执行任何代码、不调任何外部系统。

输出:可视化时间线 + 关键决策节点 + hash 链校验徽章。

4.3 Mock Replay · 重跑但 mock 外部依赖

用途:用历史数据 debug 新代码。回答"如果我现在改了 Step 5 Policy 规则,这个历史 Run 还会通过吗"这一类问题。

外部依赖Mock 数据来源
LLM 响应对应 ToolTrace(type=llm_call)的 output_hash 反查归档
Tool 响应ToolTrace.output_hash 反查存储
HITL 决策HITLTrace.decision 直接复用
外部 API(飞书 / 多维表 / 邮件)不调,返回历史 hash 对应载荷

禁止行为:真调任何外部 API;真消耗 LLM token;写生产 trace 表(必须写 replay_* 前缀的 namespace)。

4.4 Live Sandbox Replay · 重新执行新数据

用途:A/B 对比新旧 Harness 内核、新旧模型、新旧 Memory 策略;端到端回归基线。

行为:用历史 RunRequest 在新代码起一个全新 Run,外部副作用全部走专用 sandbox endpoint:

仅在专用 Replay 环境运行,生产环境禁止。

4.5 模式切换在 Run 内核上的实现

Replay 引擎调用 HarnessPipeline 时传入 replay_mode,Run 内核本身不感知三模式语义,由 LLM Router / Tool Invoker / HITL Coordinator 在注入点根据 mode 切换实现:Pure 不会进入到此(直接 UI 渲染);Mock 注入 MockHandlers;Live Sandbox 注入指向 sandbox 的 client。StateMachine 不变,合法性约束在 Replay 模式下仍生效。

4.6 安全约束

类型PureMockLive Sandbox
外部 API 调用不调不调调 sandbox endpoint
LLM 调用不调不调(mock)真调 + 配额护栏
数据库写入不写写 replay_* namespace写 replay_* namespace
凭证消费不消费不消费消费 replay 专用凭证
权限要求replay:readreplay:executereplay:execute + sandbox 环境标识
跨 tenant 访问需 replay:cross-tenant,仅平台运维同上同上

任何 Replay 都写一条 AuditLog 关键事件 replay_executed,含 mode / actor / 原 run_id / 新 run_id 四元组。


5. 合规查询接口

SECTION GOAL

定义对外暴露的查询接口,让审计、客服、SRE 都用同一组 API,并保证租户行级过滤是接口层硬约束。

5.1 查询场景与接口映射

查询场景接口主要参数返回
单 Run 全链路视图GET /api/v1/traces/run/<run_id>run_idRunTrace + 全部子 trace 按 seq_no 排序,hash 链校验状态
按 trace_type + 时间窗GET /api/v1/tracestype, from, to, tenant_id分页 trace 列表
按用户 / agent 过滤GET /api/v1/tracesuser_id 或 agent_id, scope, time同上
Skill 维度聚合GET /api/v1/traces/aggdim=skill_id, metric=p95_latency聚合点序列
User / Workspace 成本GET /api/v1/costuser_id 或 workspace_id, time基于 CostTrace 的成本聚合
审计追溯包导出GET /api/v1/audit/exportrun_id 或 时间窗 + tenant含 chain_root 签名的离线包

5.2 租户隔离与权限

层规则
API Gateway从 Token 解出 tenant_id 注入 request scope
Service 层所有查询参数显式带 tenant_id;不允许 tenant_id IS NULL
DAO 层SQL 强制 WHERE tenant_id = :tid,违反编译期禁用
跨租户读需 admin:cross-tenant 权限,且写一条 AuditLog

5.3 查询性能与缓存


6. SSE 事件流

SECTION GOAL

说明运行中事件如何通过 SSE 推到前端,以及历史回看怎么和实时流共用一条协议,让 dashboard / 调试 UI 都用同一个连接处理。

6.1 事件类型

事件触发时机载荷要点
run.startedRun 创建写入 RunTracerun_id, agent_id, trigger_kind
step.entered / step.exited每个 Step 进出step_id, state_before/after, duration_ms
tool.invoked / tool.resultTool 调用前后tool_call_id, status, output_hash, cost_tokens
policy.decidedPolicy 评估结束rule_id, decision, reason
hitl.requested / hitl.decidedHITL 请求与回复risk_level, approver_id, decision
thinkingLLM 流式 token(可选订阅)delta token,仅热路径,不入冷存
error异常发生error_code, error_class, fatal
run.finishedRun 终态terminal_state, total_cost, chain_root_hash

6.2 实时流与历史回看共用协议

6.3 多租户与权限

校验说明
tenant_id 校验SSE 建连时必须确认 caller 与 run_id 同租户,否则 403
事件过滤thinking / 内部决策事件按权限分级,普通用户默认不订阅
速率护栏每连接 QPS 限制;同 Run 同用户连接数上限
断线收敛30 分钟无心跳自动断开,避免连接堆积

6.4 与查询接口的边界

SSE 给"运行中要看进度"和"事后按 seq 顺序回放事件流"这两个场景;§5 查询接口给"按多维度筛选 trace"和"做聚合统计"这两个场景。两者都基于同一份 trace 数据,只是消费形态不同:SSE 是事件推送,查询是数据集合。Replay 引擎的 Pure 模式在 UI 上其实就是历史 SSE 回看 + UI 渲染层。