Session Log:仅追加事件流与模型上下文派生
Agent 跑起来后,用户话、工具调用、模型回复、遥测、会话标题会同时膨胀。若对话进一张表、工具日志进另一张、标题再存一份,很快出现「库里说调了工具、日志里没有」这类对不上的情况;崩溃后也难判断走到哪一步。dsh 只保留一本仅追加(append-only)的会话日志(Session Log):每发生一事就往末尾追加一条事件,不改旧记录、不删旧记录;模型历史、transcript、遥测、标题等都从这条事件流派生。
模型可见即已记录
铁律写得很死:任何到达模型请求的内容,都必须能从日志重建(Model-visible means logged)。若再单独存一份对话表,两份事实很容易漂移;在 dsh 里发给模型的 system / user / assistant / tool 历史,一律由 deriveMessages() 对日志事件投影得到。
日志里是原始事件,例如 system/message、user/message、assistant/message、tool/result。只要日志在,模型看到的上下文就能重算;调试时打印事件流,比拼多份存储更直接。银行流水的类比也成立:余额和月结都是账本的投影,账本才是真相。
三类事件怎么分
表层事件会进入模型可见消息:system/message、user/message、assistant/message、tool/result。它们构成模型看到的「表面」。追加表层事件时必须带 surfaceOp,最常见是 'append'。
仅日志事件只记账、不进对话,例如 turn/start / turn/end、step/start / step/end、assistant/attempt(超时或取消的失败尝试)、session/title、request/header。调试、遥测、恢复靠它们,模型侧看不到。
投影事件通过 Message Projection 改派生内容,而不一定新增一条对话。例如 image/offload:图片卸到外部存储后,派生消息里的图片会换成引用。插件用 @messageProjection 声明,经 ctx.sessions.registerMessageProjection() 注册纯函数解释这类事件;投影逻辑由插件拥有。这样大二进制不必长期塞在事件 payload 里,派生结果仍保持可读引用。
创建、追加、派生
1 | const session = ctx.sessions.create('my-session', { |
create 在内存里得到事件溯源的 Session 实例;默认不落盘,持久化靠后续持久化插件。append 的三个参数分别是事件类型、payload、表层选项。payload 经 snapshotJsonValue() 只读一遍完成校验与复制,必须是无损 JSON:BigInt、循环引用、稀疏数组、-0、特殊原型等会直接导致追加失败,日志、派生状态、事件流都不变。这样避免「校验时读到 A、落盘时 getter 又吐出 B」的漏洞。
deriveMessages() 会缓存深度冻结的投影结果,每次调用返回新数组;内容可相同,引用不同。新事件追加或投影决策变化会使缓存失效,频繁调用不必每次全量重算。
surfaceOp:append 与 replace
append 往对话历史末尾加一条新消息,是默认路径。需要压缩或清理历史时用 replace:指定 startSeq / endSeq,在表层用新消息盖住一段区间。原始事件仍留在日志里,之后的 deriveMessages() 不再返回被盖住的那段。可以遮蔽,不能抹除;审计和复现仍然翻得到旧账。
崩溃恢复与 request/header
若日志停在带 tool-call 的 assistant/message 之后、没有对应 tool/result,冷启动扫描会合成错误结果,让模型看到工具调用被中断,避免半截调用悬在上下文里。两种恢复码含义不同:TOOL_NOT_STARTED 表示尚未记为开始,可安全重试;TOOL_OUTCOME_UNKNOWN 表示已开始但结果未知,只读或幂等可重试,有副作用的要先核实外部状态。合成结果里的文案会把这两种情况说清楚,模型才能决定要不要再调一次。
每次循环准备模型请求时,调用配置、适配器默认值、工具 schema 会以 request/header 完整快照写入日志。之后可用 foldRequestHeader(session.snapshotEvents()) 重建当时请求头,单个对话请求成为日志上的纯函数。系统提示词例外:它是 system/message 表层节点,便于用 replace 管理提示词变更;提示词不进 header 状态,避免和「配置快照」搅在一起。
追加路径上,Session 还会校验 surfaceOp 形状、替换范围是否完整覆盖被引用的源序号,然后构造 SessionSeq 写入日志,并通知订阅 session/event 的观察者(持久化插件就挂在这里)。packages/core/session/ 里,index.ts 提供 SessionStore、生命周期、fork、flush;surface.ts 管表层投影与替换校验;request-header.ts 负责 header 折叠与重建;repair.ts 做冷修复;invariant.ts 检查序号、轮次/步骤闭合、工具调用与结果配对。会话真相只有事件流一份;报表、历史、恢复线索都是它的投影。
Session Log:仅追加事件流与模型上下文派生