每个会话有一本会话日志(Session Log):只追加(append-only)的事件流水账,只能往后记新账、不能改旧账。每条记录是一个 SessionEvent——你发的话、模型的流式输出、工具调用和结果、轮次边界,全按时间顺序追加。像超市收银小票:打出来的每一行都不能撕掉重打,"修改"只能再打一行冲正——审计、对账、重放因此可信。
模型看到的对话历史不另存一份,由 deriveMessages() 把日志投影(project)成模型历史。"投影"= 从同一份源数据按需算出的视图,像 Excel 透视表不另存数据。
| 概念 | 定义 | 通俗理解 |
|---|---|---|
| step(步) | 一次模型请求 + 它调用的工具 | 一次"想 + 做" |
| turn(轮) | 从你的输入被认领,到智能体不再欠任何东西 | 一次完整交办:"办到不需要我再动为止" |
一轮(turn)包含零步或多步(step),完整时序:
turn/start ── 轮次开始
认领输入 + 队列消息
组装提示词段落 + 工具清单
agent/pre-step ── 拦截点:可改写或拒绝本次输入
step/start ── 一步开始
输入入账(user/message)
从日志推导模型历史
agent/request → llm/stream ── 调模型,流式返回
assistant/chunk* → assistant/message ── 流式片段 → 完整回复入账
tool/call* → 工具执行管线 → tool/result* ── 调工具、结果入账
step/end
工具结果要求续答 / 新输入到达 → 下一步 ── 循环:回到 step/start
agent/turn-stopping ── 拦截点:可强制再走一步
turn/end ── 轮次结束
缩进表示嵌套;带 * 的事件发生多次;标"拦截点"的是插件可挂载的扩展点
一切都在这本账上,很多产品功能天然成立,不用各建存储:
| 功能 | 怎么从日志来 |
|---|---|
| 恢复(resume) | 重放日志,会话回到中断前的状态 |
| 分叉(fork) | 复制日志到某个边界点,从那里长出新分支——"如果当时……"的平行宇宙 |
| 转录(transcript) | 把日志渲染成可读的对话记录 |
| 回放(replay) | 逐事件重演,UI 还原当时的流式过程 |
| 遥测(telemetry) | 统计 token 用量、工具成功率等 |
| 持久化(persistence) | 把日志落到 JSONL / SQLite(会话包管这个) |
单一事实源的经典收益:存一份、用一生——六个功能共享同一份数据,永不互相打架。
| 域 | 代表事件 | 什么时候用 |
|---|---|---|
| 会话事件(durable,进日志) | user/message、assistant/*、tool/*、turn/*、step/* | 事实必须活过重载、能被恢复/回放——记进账本 |
| 智能体事件(live) | agent/pre-step、agent/request、agent/turn-stopping… | 观察或拦截进行中的工作 |
| 能力事件 | fs/*、tools/*、telemetry/* | 给某个能力缝挂策略/适配,不 import 循环 |
tool/call、tool/result、turn/* 这些带斜杠的名字是会话事件(账本里的记录);要观察它们得监听 session/event 再检查 event.type,别和同名的 Cordis 事件搞混——L15 会再强调。docs/agent-lifecycle.zh.md 看时序图,对照本课伪码,标出哪些是"入账事件"、哪些是"拦截点"。