07:执行过程可解释:Events、Trace、Metrics 和 Manifest
拆解 Events、Trace、Metrics 与 Manifest 如何共享统一事件时间线,让一次 Agent Run 的执行过程可以排查、聚合、回放并安全展示。
07:执行过程可解释:Events、Trace、Metrics 和 Manifest
上一篇介绍了 RunLens 的结果验证。模型结束后,Stop Hook 会继续检查产物结构和 Evidence 来源,最终决定 Run 进入 completed 还是 failed。
有了明确结果,还需要回答另一个问题:这次 Run 为什么走到了这个结果?
Agent 运行包含模型、工具、权限、Hook、恢复和产物。只保留最终状态,很难定位问题发生在哪一层。
RunLens 使用统一事件时间线记录运行事实,再从事件生成 Metrics、Trace 和兼容投影。
业务产物回答“任务交付了什么”,运行事件回答“任务怎样走到这里”。

Agent 失败时只返回错误还不够
假设一个 Run 最终返回:
{
"status": "failed",
"error_message": "missing required files: artifacts/insight_report.md"
}
这条信息说明报告缺失,还缺少很多上下文:
- 模型是否成功读取 CSV。
- 两个 JSON 是否已经创建。
- Provider 调用了几次。
- 是否发生过参数纠正。
- Permission 对工具给出了什么裁决。
- Plan 停在第几步。
- 失败发生在模型循环还是 Stop Validation。
这些问题需要一条按时间排序的运行记录。
RunLens 将 runtime/events.jsonl 作为当前可观察性的权威来源。
一条统一事件记录了什么
每个 ObservabilityEvent 包含:
| 字段 | 作用 |
|---|---|
event_id | 唯一事件 ID |
run_id | 所属 Run |
sequence | Run 内连续序号 |
timestamp | UTC 时间 |
event_type | 事件类型 |
source | 产生事件的组件 |
data | 事件业务数据 |
duration_ms | 可选耗时 |
parent_event_id | 可选父事件 |
事件 Schema 会校验 ID、sequence、timestamp、duration 和 data 必需字段。
例如 tool.failed 要求 data 中至少存在:
tool_name
call_id
status
error_code
retryable
这样前端和 Metrics 可以直接依赖稳定字段,错误展示也有统一结构。
sequence 表达确定顺序
每个 Run 拥有独立 LocalEventStore。
事件从 sequence 1 开始连续递增:
1 run.created
2 run.state_changed
3 run.started
4 skill.match_evaluated
5 skill.selected
...
EventStore 构造时会读取已有文件并恢复最后序号。读取过程中还会检查:
- run_id 是否一致。
- sequence 是否连续。
- event_id 是否重复。
- JSON 和 Event Contract 是否有效。
事件文件损坏时,Events API 会返回明确错误,避免把不完整时间线当成正常记录。
parent_event_id 表达因果关系
sequence 告诉我们先后顺序,parent event 表达一次动作内部的关联。
例如 ToolExecutor 收到工具请求后,会先发布:
tool.requested
后续事件可以指向这个 parent:
permission.evaluated
permission.allowed
tool.started
tool.completed
这让前端可以把多个事件折叠成一次工具调用,也让排查者知道某次权限拒绝属于哪个 ToolCall。
LLM 请求和响应同样会建立父子关系,并记录 Provider、模型、response type、usage 和 duration。
RunLens 记录哪些运行事件
事件可以按领域归类。
Run 生命周期
run.created
run.started
run.state_changed
run.completed
run.failed
Skill 和 Prompt
skill.match_evaluated
skill.selected
skill.loaded
skill.activated
skill.validation_failed
prompt.assembled
LLM
llm.requested
llm.responded
llm.failed
Tool 和 Permission
tool.requested
tool.started
tool.completed
tool.failed
permission.evaluated
permission.allowed
permission.denied
Hook、Recovery 和 Artifact
hook.started
hook.completed
hook.blocked
hook.failed
recovery.started
recovery.completed
recovery.failed
artifact.created
artifact.validated
artifact.rejected
这些事件覆盖了一次 Run 的主要交接点。
EventBus 先持久化,再通知观察者
EventBus 的 publish 流程是:
构造并校验 Event
→ 写入 LocalEventStore
→ 通知内存 Observer
先持久化可以确保 Observer 收到的事件已经落盘。
某个 Observer 抛出异常时,EventBus 会把错误记录到 observer_errors,其余 Observer 和主 Run 继续执行。
当前 Runtime 主要直接使用 EventStore,Observer 机制为后续实时推送、告警或外部观测后端留下入口。
Metrics 怎样从事件聚合
Run 最终化时,aggregate_metrics() 读取完整事件列表并计算:
- 总事件数。
- LLM 调用次数。
- Token Usage。
- 工具请求、成功和失败数量。
- Permission Denials。
- Hook Blocks。
- Recovery Attempts。
- Artifact 创建、验证和拒绝数量。
- 各事件类型累计 duration。
结果写入:
runtime/metrics.json
Metrics 直接由事件生成,避免 Event 和统计维护两套事实。
例如工具调用成功时,系统记录 tool.completed。Metrics 统计这一类事件数量即可得到 succeeded tool calls。
Trace API 怎样兼容新旧 Run
RunLens 提供:
GET /api/runs/{run_id}/trace
新 Run 存在 runtime/events.jsonl 时,Trace API 直接返回统一事件,并标记:
source = events_projection
老 Run 缺少统一事件文件时,API 回退读取旧 agent_trace.jsonl。
这种兼容让前端可以继续使用 Trace 入口,内部权威来源已经切换到 Events。
Events API 支持增量查看
Events API 提供三个常用参数:
after_sequence:只读取某个序号之后的事件。event_type:按类型过滤。limit:限制单次返回数量。
返回中的 next_sequence 可以用于下一次轮询。
前端可以先加载已有事件,再周期性请求新增部分,完整时间线只需读取一次。
当前服务采用同步 Run,增量接口仍然适合历史查看和后续异步改造。
一个例子:从事件定位缺失报告
模型成功写入两个 JSON,随后返回 final answer,报告文件缺失。
关键事件可能是:
llm.responded: response_type=content
run.state_changed: model_thinking → validating
hook.started: RequiredOutputValidationHook
hook.blocked: missing insight_report.md
artifact.validated: feedback_analysis.json
artifact.validated: product_backlog.json
artifact.rejected: insight_report.md
run.state_changed: validating → failed
run.failed
从这条时间线可以得到明确结论:
- Provider 正常返回。
- AgentLoop 已经收束。
- 问题发生在最终交付检查。
- 两个 JSON 已经存在。
- Markdown 报告缺失。
相比“模型失败了”,事件能把问题定位到具体阶段和产物。
日志脱敏和路径可见性
事件写入前会经过 sanitize_event_data()。
它会处理:
- api_key、authorization、password、secret、token 等敏感字段。
- 超长字符串。
- 当前 Run 内绝对路径。
- Run 外部绝对路径。
.env路径。
当前 Run 内路径会转成相对路径,外部绝对路径会显示为:
[EXTERNAL_PATH]
敏感字段会显示为:
[REDACTED]
这让事件适合前端展示和本地排查,同时减少密钥和主机路径泄露。
Manifest 怎样控制公开文档
Run 完成后,Plugin Runner 会扫描工作区并生成 manifest.json。
Manifest 包含:
- Run ID 和状态。
- Plugin 名称和版本。
- Schema Version。
- 公开文件路径。
- Audience。
- Description。
- Sensitivity。
- Kind。
Documents API 读取 Manifest,把其中声明的文件作为 allowlist。
六类私有 Runtime 文件会被排除:
message_history.jsonl
llm_events.jsonl
tool_calls.jsonl
hook_events.jsonl
permission_events.jsonl
recovery_events.jsonl
这些文件可能包含更细的模型和工具信息,适合留在本地调试。
Legacy Projection 的作用
统一事件引入前,Runtime 已经拥有多个独立 JSONL:LLM、Tool、Permission、Hook、Recovery 和 Trace。
当前版本在最终化阶段根据 Events 重新生成这些兼容文件。
它们的定位是:
- 兼容旧接口和调试习惯。
- 提供按领域拆分的本地视图。
- 保持确定性输出。
排查最终 Run 时,events.jsonl 仍然是首选来源。
当前可观察性边界
RunLens 当前使用单 Run、单 JSONL 的本地事件存储。
它适合:
- 本地开发。
- 单进程同步执行。
- Demo 和架构验证。
- 运行复盘。
后续面向并发和生产部署时,可以扩展:
- 数据库 Event Store。
- WebSocket 或 SSE 实时事件流。
- OpenTelemetry Exporter。
- Run 级日志查询。
- Event Retention 和归档。
- 告警规则。
Manifest 当前会公开 request、Prompt、resolved config 和 Evidence Index 等非私有文件。外部部署前适合将公开规则进一步收窄为显式业务文档集合。
小结
RunLens 使用统一 Event Schema 记录一次 Run 的顺序和因果关系,再从事件生成 Metrics、Trace 和兼容投影。
Events 解释任务怎样执行,Metrics 提供聚合视图,Manifest 控制运行文件如何对外展示。模型、工具、权限、恢复和产物验证都进入同一条时间线后,Run 的成功和失败就有了可追踪证据。
下一篇从另一个方向看 Runtime 的价值:Feedback Analysis 和 File Summary 业务差异很大,它们怎样通过 Plugin Contract 共享同一套执行底座。