Skip to content

enhancement: cross-session long-term memory — model-driven memory bank as compression byproduct (nudge-gated fork write, model-decided sedimentation) #92

Description

@Tyan66666

跨会话长期记忆:上游(acp-kernel)提案

给 acp-kernel / billion-context-pi 作者的方案稿。想法来自 billion-context-dsh 的 P1-4 缺口:会话内压缩我们做得很好了,但会话结束即失忆——跨会话记忆这块,希望 kernel 来拥有(算法归 kernel,宿主只做集成)。

一、问题

ACP 解决的是"一个会话内上下文无限膨胀"。但它不解决"下一个会话不知道上个会话发生了什么":

  • 今天聊定的设计决策、踩的坑、用户偏好,明天开新会话全没了
  • 业界(mem0 / Letta / Claude Code Auto Memory / Cline Memory Bank)都有跨会话记忆,但都独立于压缩体系

核心认知:每次压缩生成的块(如 tier-1 块 b1)本身就是适合长期存储的内容——压缩把已消费对话提炼成精炼摘要,这个摘要天然就是长期记忆的候选。所以跨会话记忆不该是另起炉灶的独立系统,而应该是压缩体系的自然延伸:记忆沉淀复用压缩产物,模型决定哪些块/内容值得沉淀(不搞引擎自动归档——沉淀决策始终是模型自决)。

一句话:把"跨会话记忆"和"压缩"做成同一套机制的两面——压缩是会话内折叠,记忆是跨会话沉淀;都复用 kernel 的蒸馏/ref/搜索基础设施。

二、核心机制:nudge 表态 + fork 提取(模型自决写入)

当模型判断"这段有值得跨会话记住的内容"(无论是对话内容,还是某个压缩块 b1 的摘要值得沉淀)时:

nudge 触发(kernel 已有)
  → 模型回顾上下文(本来就在做)
  → 模型判断:这段有值得跨会话记住的吗?
     有 → 调用轻量工具 memory_commit(type, note)  【纯表态,不写内容】
  → 引擎 fork 一个同模型子对话(同文本 + 一句指令"提取当前需要的记忆,重点:{note}")
  → fork 会话最后:模型用 memory_write 把提炼结果写入记忆库
  → fork 使命结束(可归档/删除),主对话继续压缩

三个工具,权限分离

工具 谁可见 干什么
memory_commit(type, note) 主对话 表态"这段值得记"(type 枚举 + note 聚焦提示);不写内容
memory_recall(query) 主对话 读回:检索记忆库取全文(见 §三)
memory_write(content) 仅 fork 会话 真正写记忆库;主对话物理上无写权限

主对话只有"请求写"的能力、没有"写"的能力——写记忆库只能通过 fork 会话的 memory_write 完成,权限天然隔离(fork = 带 memory_write 工具定义的一次性 LLM 调用,宿主把工具输出落库)。这比"fork 直接落文件"更符合工具面架构,也让三工具语义进 kernel 接口定义。

为什么这样设计

  • 时机是现成的——nudge 时模型正在做全局回顾,对"这段讲了什么、什么值得留"认知最佳(比 Claude Code 的"每轮结束自动提取"更省,比 mem0 的"应用层另起 LLM"更模型自决)
  • 决策与执行分离——模型只做轻量"要不要记"的判断(一次工具调用,note 是聚焦提示),繁重的提炼下放给 fork(不打断主对话、不占主对话上下文)
  • fork 缓存命中——同模型同文本(+一句指令),prompt cache 共享

三、读回:常驻索引 + 自主取回(两时机,不搞多层)

时机① 常驻索引:记忆库目录(一句话/条 + 类型 + 上次读取时间)注入 system prompt,每次 turn 都在
  → 模型"知道有什么"(存在性提示,解决"想不起来去翻")
时机② 自主取回:模型看到相关条目 → 调 memory_recall(query) 取全文
  → 细节按需取,不一股脑全塞

这正好是 Claude Code 生产实践的形态(MEMORY.md 常驻 + FileRead 取全文),两大主流系统(Claude Code / Letta)独立收敛到同一形态,不是我们发明。

四、选择与衰减:一个公式两用

问题:纯"被读频率"优先 → 富者愈富(被读 → 频率升 → 优先被读 → 更被读;没被读的永远不见天日)。

机制(字段全是记忆条目自带的,不需要 embedding/向量):

score = importance + min(距上次读取天数 / 30, 5) + uniform(0, 0.5)
  • importance(1-10):模型 memory_commit 表态时顺带评分(一次性评估,Generative Agents 做法)
  • anti-recency越久没被读优先级越高——刚读过的降权让位,把正反馈变负反馈
  • random jitter:随机打破"永远同一批"的锁定
  • top-N = 5(对齐 Claude 5-slot);会话累计 surfacing 上限对齐 Claude 60KB

这一个公式同时是"可见性调度"和"保质期":不被读的记忆不是消失,而是权重回升等机会——Ebbinghaus 遗忘曲线反过来用,一个机制两用。

五、记忆条目 = kernel 的 tier-3 蒸馏块(不造新格式)

  • 记忆条目数据模型直接复用 kernel 分层蒸馏产物(Agentic Memory 论文明确:tier-1/2/3 蒸馏 ≡ short→long 迁移,tier-3 超浓缩就是天然长期记忆条目)
  • 检索复用 kernel searchBlocks hybrid 基础设施(扩展一个记忆库文档源即可)
  • 记忆库本身 = 人类可读 markdown 目录(人能看能改 = 外部纠错通道)+ 每条带 citation(来源会话+seq,延续可回溯哲学)

六、kernel 要做什么(A 类)/ 宿主要做什么(B 类)

归 kernel(上游实现) 归宿主(DSH 集成)
memory_commit 的决策时机挂钩(nudge 文案加记忆引导) 工具注册(DSH 工具面)
fork/一次性 LLM 提取的接口定义 实际 LLM 调用(宿主运行时)
记忆条目数据模型(= tier-3 蒸馏块扩展) markdown 文件存储 + citation(seq 方言)
score 选择机制 + Ebbinghaus 衰减 常驻索引注入 system prompt
memory_recall 检索(searchBlocks 扩展) /acp memory list 命令
nudge → memory_commit 的时机接线

实施顺序:kernel 先行,宿主后随——kernel 发布记忆能力后,宿主 bump kernel 并全部调 kernel API(符合我们既有的"缺陷走 upstream、不在宿主 patch kernel"约定)。

七、一句话

把"跨会话记忆"做成压缩体系的自然延伸:nudge 时模型表态 → fork 提取 → 记忆库 = tier-3 蒸馏块 + 人类可读 markdown;读回 = 常驻索引 + 自主取回;选择/衰减 = importance + 反新鲜度 + 随机,一个公式两用。算法归 kernel,宿主只做集成。


附:完整设计文档与调研

希望 kernel 拥有的能力面(§六 A 类): 记忆条目数据模型(= tier-3 蒸馏块扩展)、score 选择 + Ebbinghaus 衰减、memory_recall 检索(searchBlocks 扩展)、nudge 文案记忆引导、三工具接口语义(commit/recall/write, write 仅 fork)。宿主侧只做 DSH 集成(B 类)。实施顺序: kernel 先行, 宿主后随。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions