摘要式上下文压缩(designs/runtime-borrow.md §1;借 Keelson
Advisors/CompactionAdvisor.cs / CompactionBudget.cs,判据又照 Reactor / Codex)。
长会话迟早撞窗。本 filter 按预算比例触发:
挂法::chat filter,注册在 memory filter 之后(看到的是展开后的完整历史)。
与 ChatMemory 的关系(缺省不写回):store 仍是完整历史的唯一真相——AG-UI 的
消息快照、timeline、审计都读它。压缩只改这一次发给模型的视图;为了不每轮重摘,
filter 按会话记一份摘要缓存 {:prefix-n :prefix-hash :summary}:历史前 n 条没变
就直接换成缓存的摘要(零 LLM 调用)。再超阈值时在视图上继续压(旧摘要进新摘要的
素材,逐次合并)。要「有界存储」就给 :write-back-store:摘要后用压缩结果改写 store
(mem-clear + mem-add,消息 id 保留;前端看到的历史随之变短,代价自负)。
两个可替换挂点(2026-11-08 用户拍板:缺省不改写、不落盘,但逻辑要能整个换掉,
同 Keelson Agui.Bitcask 那样由外部库提供实现):
:store:CompactionStore 协议,存每会话的可持久部分 {:cache :failures}
(摘要缓存 + 摘要器连续失败数)。缺省 in-memory-store(LRU,进程停即丢)。
外部实现可落盘,重启后不必重摘。缓存对不上(前缀 hash 变了)只会触发重摘,不会出错。:write-back:(fn [conversation-id compacted original]),摘要成功后调用,决定怎么
处置历史(改写 / 归档原文再改写 / 只记审计…)。缺省 nil = 不写回;
:write-back-store 是它的语法糖(= (store-write-back store))。摘要式上下文压缩(designs/runtime-borrow.md §1;借 Keelson
`Advisors/CompactionAdvisor.cs` / `CompactionBudget.cs`,判据又照 Reactor / Codex)。
长会话迟早撞窗。本 filter 按预算比例触发:
1. **微压缩**:更早的工具结果正文换成占位符、保留的截到上限(不调模型);
2. 仍超 → 在**安全切点**(不劈开 assistant 调用与它的结果)把前缀换成一条**摘要**;
3. 没有摘要器 / 无可摘前缀 / 摘要失败 → **丢最老**(按安全段丢);
4. provider 报**上下文超长** → 强压后重发一次。
**挂法**:`:chat` filter,注册在 memory filter **之后**(看到的是展开后的完整历史)。
**与 ChatMemory 的关系(缺省不写回)**:store 仍是完整历史的唯一真相——AG-UI 的
消息快照、timeline、审计都读它。压缩只改**这一次发给模型的视图**;为了不每轮重摘,
filter 按会话记一份**摘要缓存** `{:prefix-n :prefix-hash :summary}`:历史前 n 条没变
就直接换成缓存的摘要(零 LLM 调用)。再超阈值时在视图上继续压(旧摘要进新摘要的
素材,逐次合并)。要「有界存储」就给 `:write-back-store`:摘要后用压缩结果改写 store
(`mem-clear` + `mem-add`,消息 id 保留;前端看到的历史随之变短,代价自负)。
**两个可替换挂点**(2026-11-08 用户拍板:缺省不改写、不落盘,但逻辑要能整个换掉,
同 Keelson `Agui.Bitcask` 那样由外部库提供实现):
- `:store`:`CompactionStore` 协议,存每会话的**可持久部分** `{:cache :failures}`
(摘要缓存 + 摘要器连续失败数)。缺省 `in-memory-store`(LRU,进程停即丢)。
外部实现可落盘,重启后不必重摘。缓存对不上(前缀 hash 变了)只会触发重摘,不会出错。
- `:write-back`:`(fn [conversation-id compacted original])`,摘要成功后调用,决定怎么
处置历史(改写 / 归档原文再改写 / 只记审计…)。缺省 nil = 不写回;
`:write-back-store` 是它的语法糖(= `(store-write-back store)`)。死循环提醒(designs/runtime-borrow.md §4;借 Keelson Advisors/DeadLoopAdvisor.cs,
它又借 keel dead-loop-filter)。
模型在两三个调用之间打转(读同一个文件、查同一个状态),每轮都成功,所以失败保护
(filter.tool-guard)管不到。本 filter 记最近 :window 轮的调用指纹,最近 :recent
轮里不超过 :distinct 种、且这几轮没有真进展 → 给下一轮挂一条临时提醒
(react 的 :notes 挂点:只附在那一次请求末尾,不进 ChatMemory)。
⛔ 只提醒、不阻断:误阻断会把正在做的事拦腰截断,漏检只是多烧几轮 token (上面还有 max-iterations 与 tool-guard)。提醒过就清空窗口。
挂法::iteration + :turn(后者在新 turn 开始时清零状态)。状态按 conversation-id
放在 filter 闭包里——只是提醒,不进暂停快照,丢了无害。
死循环提醒(designs/runtime-borrow.md §4;借 Keelson `Advisors/DeadLoopAdvisor.cs`, 它又借 keel `dead-loop-filter`)。 模型在两三个调用之间打转(读同一个文件、查同一个状态),每轮都**成功**,所以失败保护 (`filter.tool-guard`)管不到。本 filter 记最近 `:window` 轮的调用指纹,最近 `:recent` 轮里不超过 `:distinct` 种、且这几轮没有真进展 → 给**下一轮**挂一条临时提醒 (react 的 `:notes` 挂点:只附在那一次请求末尾,不进 ChatMemory)。 ⛔ **只提醒、不阻断**:误阻断会把正在做的事拦腰截断,漏检只是多烧几轮 token (上面还有 max-iterations 与 tool-guard)。提醒过就清空窗口。 挂法:`:iteration` + `:turn`(后者在新 turn 开始时清零状态)。状态按 conversation-id 放在 filter 闭包里——只是提醒,不进暂停快照,丢了无害。
Memory Filter - 按 conversation-id 读写历史
以 around-chat filter 形态挂进 chat-client 的 filter 链:
conversation-id 从 ChatRequest 的 :context(扁平 map)读取;为空时整体 no-op (保留一次性/无记忆调用)。store 是 filter 的私有闭包,chat-client 对其无感知。
用法: (build-chat-client {:chat-model cm :tools tools :filters [(memory-filter store)]})
Memory Filter - 按 conversation-id 读写历史
以 around-chat filter 形态挂进 chat-client 的 filter 链:
- req 段:把本次入参(delta)存入 store,再用完整历史替换发给 LLM 的 messages
- resp 段:把 LLM 的 assistant 回复(可能含 tool-calls)存入 store
conversation-id 从 ChatRequest 的 :context(扁平 map)读取;为空时整体 no-op
(保留一次性/无记忆调用)。store 是 filter 的私有闭包,chat-client 对其无感知。
用法:
(build-chat-client {:chat-model cm
:tools tools
:filters [(memory-filter store)]})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 |