← 返回目录

第 2 卷

第 2 / 6 卷日期:

“没有需要保持同步的并行持久化 schema”)。

纸选好了,接着看怎么往上写。这一步分前后两下:先是接口里的“建”(create),它只登记这个会话的元数据,一个字节都不写;真正把事件写到磁盘的,是后面的“写”

(append)。所以“建了但一次都没用过”的会话,磁盘上什么都不会留下。

“写”的规矩很硬:后端 append 返回成功,就意味着数据真的写到磁盘了,不是“收到”,而是“写完”(append resolves only after durability)。这条规矩直接决定崩溃时

能找回多少:返回之后进程才死,那条事件一定还在;返回之前就死,就当它没发生过。注意这是持久化层的 append,不是往会话里追加事件那个,后者攒够一批才会调用到它。

不过规矩归规矩,什么时候真的往磁盘写,由持久化插件说了算,每来一条就写一次太慢,所以真实的写路径是批处理的:

第一个待处理事件开一个固定窗口(默认 200ms),后续事件加入,但不重置窗口;

窗口到期,把这一批写下去;这一批写入期间新来的事件,自己另起一个窗口;

session/flush 取消等待、立刻排空。

这么看,flush 就不只是“催一下”了,它是持久化检查点:排空之后,磁盘上的日志才是完整的、能接得上的。少了它,最后那一批还压在窗口里,进程一死就什么都没留下。

循环在领下一轮之前会 flush 一次,既保证顺序,也在这一个点上观察写入错误。写失败时事件被保留、自动重试暂停,通过 agent/error(框架统一的报错通道)报出来,绝不会把

失败记成一条会话事件(那等于在账上写假话)。

等下 npm run start:ch09:crash 跑出来的第一段,磁盘 3 条,turn 没闭合,就是 flush 之后留下的。少了那句 flush,磁盘上什么都不会有。

分不分、从哪儿分,都是你说了算,这三件里,它是你主动去做的那一种。

为什么需要它?一个长任务跑到第十轮,你想试试“如果第六轮换个问法会怎样”;或者要派一个子 agent 去干一件事,又不想让它看见主线的全部历史,这些都需要从历史的某个点另开

一个会话,原来那个不受影响。

底层原语还是造会话的那个 create,只是多带两个参数:ctx.sessions.create(id, { seed, meta }),seed 是一串事件,造出来的会话一出生就带着这些历史。

日常用的是包在上面的一层策略 API:

复制代码 1fork(source: SessionForkSource, boundary?: number, childSessionId?: SessionId): S

三个参数:从哪儿分(可以是会话对象,也可以是它的 id)、分到第几条(含)、新会话叫什么。分叉是这样一件事:

图 5-1
图 5-1

复制代码 1--- ① 父会话:两轮对话,8 条事件 ---

2seq0turn/start
3seq1step/start
4seq2user/message
5seq3step/end
6seq4turn/end
7seq5turn/start
8seq6user/message
9seq7turn/end

10 11--- ② fork 一份出去(不指定 boundary)---

12子会话9条,seedLength = 8,parent = parent
13最后一条:seq8 session/end-seed ←种子边界
14消息历史2条(全是继承来的)

15 16--- ③ 只要第一轮(boundary = 4)---

17子会话 6 条,seedLength = 5 18消息历史 1 条

19 20--- ④ boundary 落在开放轮次内(seq 2 正卡在第一轮中间)---

21被拒:fork boundary 2 in session "parent" ends inside open turn 1

  • 里有个多出来的一条。父会话 8 条,子会话却是 9 条,多的是 seq 8 那条

session/end-seed。

它的载荷是空的,不产生消息,位置本身就是它的全部意义:它之前的事件来自 seed,从它往后才是这个会话自己的写入。官方管它叫 firstLiveSeq 的持久投影,firstLiveSeq

是给拿着对象的消费方看的,这条事件是给只拿到磁盘字节的消费方看的,两者回答同一个问题:本次生命周期的写入从哪儿开始。

为什么要专门留这么一条?因为继承来的历史和本会话的实时工作,在日志里是逐字节相同的。

举个具体的例子:日志里有一个“开了没关”的标记(下一讲要讲的上下文压缩,用的就是一对

全站分卷索引 · 6 卷
  1. 1第 1 卷
  2. 2第 2 卷
  3. 3第 3 卷
  4. 4第 4 卷
  5. 5第 5 卷
  6. 6第 6 卷