事件、Journal 与执行证据
区分模型流事件与语义 Run 事件,持久化审计证据,并重建具有因果关系的执行树。
两类事件流
Runifold 刻意区分模型协议事件与 Run 事件。ModelStreamEvent 用于重建服务商响应:
内容块、Reasoning、Tool Call、用量、Warning、服务商扩展与终止完成。
RunEventKind 记录执行系统做了什么:生命周期变化、预算更新、Callable 边界、
Effect、子节点、取消、失败与完成。
模型事件可以成为 Prompt 可见内容;Run 事件是控制与审计证据,不能被意外注入模型 会话。
Run 事件模型
每个语义事件都具有稳定身份和因果位置。根 Run 可以为模型调用、Tool、委派 Agent 或 Workflow Step 创建子 Run。共享记账与取消不会抹去子节点自己的身份。
运维人员或恢复代码必须理解的事实,应该使用 Domain Event;尚未规范化但需要无损保存 的传输信息,应该使用 Provider Event。不要只用日志字符串表达关键状态跳转。
Journal 契约
Journal 是追加语义证据的边界。InMemoryJournal 适合测试;持久 Store 可以让证据
跨越进程丢失。RunRecorder 把结构化事件连接到当前 RunContext。
如果审计或恢复依赖 Journal,写入失败就不只是“日志失败”。应用必须明确决定继续、 关闭式失败,还是带着显式降级信号运行。
重建一次 Run
一次有用的重建应该回答:
- 哪个根请求启动了工作;
- 创建了哪些子节点,因果顺序是什么;
- 消耗了什么权限与预算;
- 请求并完成了哪些外部 Effect;
- 取消或失败源自哪里;
- 是否已经提交终止 Outcome。
OpenTelemetry 回答跨服务运维问题,Journal 回答单次执行的语义问题。通过 Run 与 Invocation 身份关联它们,不要互相替代。
采集策略
记录身份、规范状态、模型身份、用量、时间、错误种类与安全扩展元数据。Prompt、输出、 Tool 参数、凭证、原始文档与服务商 Body 默认保持脱敏。
Metric Label 必须保持低基数。Run ID 与请求专属事实应该进入 Trace 或 Journal,绝不能 成为 Prometheus Label。采集策略是一项安全和数据保留决策,而不只是调试偏好。
记录并检查语义 Event
use std::sync::Arc;
let journal = Arc::new(InMemoryJournal::new());
let run = RunContext::root(
BudgetTracker::new(Budget::default()),
CapabilitySet::new(),
)
.with_journal(journal.clone());
run.record(RunEventKind::Domain(DomainEvent {
namespace: "acme.orders".into(),
name: "order.validated".into(),
payload: json!({ "order_id": "ord_42" }),
}), None)?;
for event in journal.events() {
println!("{} {:?}", event.meta.sequence, event.kind);
}使用稳定、低 Cardinality 的 Event Name 和安全 Payload。如果 Payload 包含客户正文、 Credential、原始 Tool 参数或 Provider Body,应改为脱敏引用。Durable Store 按 Sequence 读取 Event;Consumer 必须幂等,因为 Delivery 或 Projection 可能重复。