课程总纲 / 阶段 1 · 理念
L07

核心理念四:会话日志是唯一事实源

turn / step 概念,以及"模型可见即可日志"这条铁律。

预计阅读 20 分钟难度 ★★★☆☆理念课收官

一、一本只许追加的账本Append-Only Log

每个会话有一本会话日志(Session Log)只追加(append-only)的事件流水账,只能往后记新账、不能改旧账。每条记录是一个 SessionEvent——你发的话、模型的流式输出、工具调用和结果、轮次边界,全按时间顺序追加。像超市收银小票:打出来的每一行都不能撕掉重打,"修改"只能再打一行冲正——审计、对账、重放因此可信。

模型看到的对话历史不另存一份,由 deriveMessages() 把日志投影(project)成模型历史。"投影"= 从同一份源数据按需算出的视图,像 Excel 透视表不另存数据。

二、铁律:模型可见即可日志Model-Visible Means Logged

凡是进入模型请求的内容,必须能从会话日志重建;不在日志里的东西永远到不了模型。仓库有运行时不变量(invariant)断言这一点,违反即报错,不是君子协定。想给模型一种新输入(如注入上下文),必须发明一种新的日志事件让它留痕。像法庭取证:陪审团(模型)看到的每份证据都必须入卷宗(日志),庭外塞材料无效。

三、turn 与 step:一次干活的形状Turn & Step

概念定义通俗理解
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                                   ── 轮次结束

缩进表示嵌套;带 * 的事件发生多次;标"拦截点"的是插件可挂载的扩展点

两个细节:① 输入被拒绝也会记一个"零步的完整轮"——账本连"尝试过"都记;② 输入通过同一个收件箱(inbox)到达:有的消息立刻唤醒智能体,注入的上下文静静躺着等下一条消息一起被认领。

四、一本账,处处用One Log, Many Features

一切都在这本账上,很多产品功能天然成立,不用各建存储:

功能怎么从日志来
恢复(resume)重放日志,会话回到中断前的状态
分叉(fork)复制日志到某个边界点,从那里长出新分支——"如果当时……"的平行宇宙
转录(transcript)把日志渲染成可读的对话记录
回放(replay)逐事件重演,UI 还原当时的流式过程
遥测(telemetry)统计 token 用量、工具成功率等
持久化(persistence)把日志落到 JSONL / SQLite(会话包管这个)

单一事实源的经典收益:存一份、用一生——六个功能共享同一份数据,永不互相打架。

五、事件分三个域,按用途选Three Event Domains

代表事件什么时候用
会话事件(durable,进日志)user/messageassistant/*tool/*turn/*step/*事实必须活过重载、能被恢复/回放——记进账本
智能体事件(live)agent/pre-stepagent/requestagent/turn-stopping观察或拦截进行中的工作
能力事件fs/*tools/*telemetry/*给某个能力缝挂策略/适配,不 import 循环
易混点:tool/calltool/resultturn/* 这些带斜杠的名字是会话事件(账本里的记录);要观察它们得监听 session/event 再检查 event.type,别和同名的 Cordis 事件搞混——L15 会再强调。
append-only 只追加
只能往后记、不能改旧账的记录方式。
derive 推导
从日志计算出的视图(如模型历史),不另存。
turn / step
轮(完整交办)/ 步(一次模型请求+工具)。
fork 分叉
复制日志到边界点,长出平行分支。
✏️ 动手练习
  1. 打开 docs/agent-lifecycle.zh.md 看时序图,对照本课伪码,标出哪些是"入账事件"、哪些是"拦截点"。
  2. 思考:为什么"注入上下文"(agent.inject)不立即唤醒空闲的智能体?(提示:注入内容要等下一次认领才被模型看见,而认领需要一条消息。)
📝 自测(点击展开答案)
1. 模型的对话历史存在哪?
不单独存,由 deriveMessages() 从会话日志投影出来——日志是唯一事实源。
2. "模型可见即可日志"违反了会怎样?
运行时不变量断言它,违反直接报错。动机:审计与复现——模型知道的每件事都能从日志重建,无暗箱输入。
3. 一次 turn 最少有几步?这个情况什么时候发生?
零步。输入被 agent/pre-step 拒绝或改写后为空时,仍关闭一个完整的零步 turn——账本连"尝试过但没干活"都记。
4. fork、resume、回放为什么能共享同一套实现?
都是对日志的操作:resume=重放全部,fork=复制到边界点另存新枝,回放=逐事件重演给 UI。单一事实源,无需各建存储。
权威出处:architecture.zh.md(Session log / Turn flow 节) · agent-lifecycle.zh.md(时序图) · subsystems/session.zh.md