会话事件存储的可替换挂点(designs/agui-borrow.md §8;对照 Keelson
IAguiThreadEventStore / IAguiThreadSequenceRecorder 与卫星包
Keelson.Agui.Bitcask)。
本库只带一个实现:ring-store,即原来 runtime 内置的会话环形缓冲
(有界、进程内、不落库)。缺省行为与以前逐字相同。
为什么要有这个协议(2026-11-08 用户拍板):事件持久化本库不做——
docs/agent-runtime-design.md §8.1 / §8.2 的结论不变(对话真相在 ChatMemory,
暂停真相在 PauseStore,事件缓冲是传输设施)。但事件存储的逻辑要能整个换掉:
将来外部库(同 Keelson 的 Agui.Bitcask)实现本协议、经 runtime 的
:event-store 插进来,就能让 /threads/:id/events、/threads/:id/state 在
重启后仍有内容,:since / Last-Event-ID 跨进程续传。本库不需要为此改一行。
即便插了持久化实现,它仍然不是对话真相::since 补不上时 runtime 照旧以
ChatMemory 的消息快照 resync(§8.1),事件存储只是回放设施。
:seq 由 runtime 分配:会话内单调递增、跨 run 连续。存储按原样保存,
不改、不重排。es-append! 在会话锁内同步调用:要快,不许抛(抛了 runtime 记日志吞掉,
不打断 run,但这条事件就没进存储)。慢存储请自己做写缓冲。es-last-seq:会话新建条目时 runtime 用它接上序号(重启后 :seq 不从 0
重来——否则客户端拿着旧的 Last-Event-ID 会漏收)。没有记录返回 nil。es-since:返回 {:events [...] :gap? bool};since 为 nil 返回空且不报
gap;gap? 为真表示存储起点已越过 since,中间那段补不回来,runtime 会走 resync。es-release!:会话从 runtime 内存里被驱逐 / forget 时调。进程内实现借机
释放内存;持久化实现什么都不做(数据要留着给重启后的续传)。es-delete!:线程被删除时调(rt/purge!)。持久化实现在这里真删。事件是中立的 Clojure map(agui.event 的事件形状,含 :type :seq :ts 等),
EDN 可往返;持久化实现自己决定编码(EDN / Transit / JSON)。
会话事件存储的**可替换挂点**(designs/agui-borrow.md §8;对照 Keelson
`IAguiThreadEventStore` / `IAguiThreadSequenceRecorder` 与卫星包
`Keelson.Agui.Bitcask`)。
**本库只带一个实现**:`ring-store`,即原来 runtime 内置的会话环形缓冲
(有界、进程内、不落库)。缺省行为与以前逐字相同。
**为什么要有这个协议**(2026-11-08 用户拍板):事件持久化本库**不做**——
`docs/agent-runtime-design.md` §8.1 / §8.2 的结论不变(对话真相在 ChatMemory,
暂停真相在 PauseStore,事件缓冲是传输设施)。但事件存储的逻辑要**能整个换掉**:
将来外部库(同 Keelson 的 `Agui.Bitcask`)实现本协议、经 runtime 的
`:event-store` 插进来,就能让 `/threads/:id/events`、`/threads/:id/state` 在
重启后仍有内容,`:since` / Last-Event-ID 跨进程续传。本库不需要为此改一行。
即便插了持久化实现,它仍然**不是**对话真相:`:since` 补不上时 runtime 照旧以
ChatMemory 的消息快照 resync(§8.1),事件存储只是回放设施。
## 契约(实现方必须满足)
- **`:seq` 由 runtime 分配**:会话内单调递增、跨 run 连续。存储按原样保存,
不改、不重排。
- **`es-append!` 在会话锁内同步调用**:要快,**不许抛**(抛了 runtime 记日志吞掉,
不打断 run,但这条事件就没进存储)。慢存储请自己做写缓冲。
- **`es-last-seq`**:会话新建条目时 runtime 用它接上序号(重启后 `:seq` 不从 0
重来——否则客户端拿着旧的 Last-Event-ID 会漏收)。没有记录返回 nil。
- **`es-since`**:返回 `{:events [...] :gap? bool}`;`since` 为 nil 返回空且不报
gap;`gap?` 为真表示存储起点已越过 `since`,中间那段补不回来,runtime 会走 resync。
- **`es-release!`**:会话从 runtime 内存里被驱逐 / forget 时调。进程内实现借机
释放内存;持久化实现**什么都不做**(数据要留着给重启后的续传)。
- **`es-delete!`**:线程被删除时调(`rt/purge!`)。持久化实现在这里真删。
事件是中立的 Clojure map(`agui.event` 的事件形状,含 `:type :seq :ts` 等),
EDN 可往返;持久化实现自己决定编码(EDN / Transit / JSON)。(es-append! s conv-id event)追加一条(已带 :seq)。会话锁内同步调用:要快、不许抛。
追加一条(已带 `:seq`)。会话锁内同步调用:要快、不许抛。
(es-delete! s conv-id)线程被删除:真删。
线程被删除:真删。
(es-events s conv-id)现存的全部事件(只读端点用)。没有返回 []。
现存的全部事件(只读端点用)。没有返回 []。
(es-last-seq s conv-id)该会话最后一条事件的 :seq,没有返回 nil。
该会话最后一条事件的 `:seq`,没有返回 nil。
(es-release! s conv-id)会话被 runtime 驱逐 / forget:进程内实现释放内存,持久化实现不动数据。
会话被 runtime 驱逐 / forget:进程内实现释放内存,持久化实现不动数据。
(es-since s conv-id since)since 之后的事件:{:events [...] :gap? bool}。
`since` 之后的事件:`{:events [...] :gap? bool}`。
(ring-store)(ring-store {:keys [buffer-size] :or {buffer-size 512}})进程内环形缓冲:每会话最多 :buffer-size 条(缺省 512),会话驱逐即丢、进程停即丢。
这就是 runtime 不配 :event-store 时用的那个。:buffer-size 必须是正整数(装配期抛)。
进程内环形缓冲:每会话最多 `:buffer-size` 条(缺省 512),会话驱逐即丢、进程停即丢。 这就是 runtime 不配 `:event-store` 时用的那个。`:buffer-size` 必须是正整数(装配期抛)。
(since-of events since)对一串按 :seq 升序的事件做 es-since 的语义(实现方可直接复用)。
对一串按 `:seq` 升序的事件做 `es-since` 的语义(实现方可直接复用)。
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |