ai-memory 开源解读:Rust 单二进制给编码 Agent 装上跨 CLI 持久记忆

开源AgentAI

在 Claude Code 里调试到一半,切到 Codex 继续同一个任务,新会话对代码库一无所知,架构说明、踩过的坑、被否掉的方案全部归零。编码 agent 的记忆只活在一个会话里,会话结束记忆就消失。开源项目 ai-memory 针对这个问题给出了一个完整方案:单个 Rust 二进制加一个本地服务器,把生命周期钩子捕获的观察记录编译成持久 wiki,让任何 MCP 客户端在同一个目录里共享记忆并完成交接。

ai-memory 项目 logo

项目由 Fabio Akita(GitHub ID akitaonrails)开发,2026 年 5 月 21 日创建,MIT 协议,目前 2,683 star、53 位贡献者,日增 star 约 730,处于快速上升期。核心卖点是跨厂商交接:退出 Claude Code 后启动 Codex,新 agent 在第一个提示词之前就会收到一份「你上次做到哪里了」的交接块。

整体架构:一条钩子驱动的数据流

ai-memory 的运行形态是一个绑定在本机回环地址(默认 127.0.0.1:49374)的 HTTP 服务器,加上散布在各 agent 配置里的两类组件:MCP 服务器注册(提供 18 个 memory_* 工具)和生命周期钩子(负责捕获事件)。

数据流的起点是 agent CLI 在运行中自然发出的生命周期事件:SessionStart、UserPromptSubmit、PostToolUse、PostCompaction、SessionEnd 等。Shell 脚本钩子用 curl 把事件 JSON 以短超时投递到 POST /hook;原生 ai-memory hook --event ... 命令则先把事件写入本地 spool(带稳定的幂等键),会话结束时由分离的 hook-drain 辅助进程投递。这个设计的关键约束是 agent 热路径绝不阻塞在网络 IO 上——服务器过载时返回 HTTP 429,直接丢弃而非无限排队。

服务器收到事件后经过三步:钩子路由器对载荷做净化(这是不可信文本进入存储的唯一路径),赋予一个 ObservationKind 枚举值,然后向单写者 actor 投递 WriteCmd。单写者模式保证 SQLite 的写连接始终只有一个所有者,读操作走可克隆的只读连接池。

真正让记忆跨会话生效的是 SessionEnd 处理:服务器用纯规则(不调 LLM)合成一份 sessions/<id>.md 会话摘要页,同时开启一条 Handoff 记录给下一个 agent。这两步在同一个 SQLite 事务里完成,崩溃恢复不会出现半个状态。配置了 LLM provider 之后,memory_consolidate 可以把摘要重写为更丰富的持久页面,或扇出到 concepts/、decisions/、gotchas/ 目录下的多页面批次。

两层存储:Markdown 是源头,SQLite 是索引

存储分两层,职责划分很明确:

目录角色
<data_dir>/wiki/Markdown 真相源。由内嵌 git2 仓库管理,每次整合和每个会话结束都产生提交。可以直接用 Obsidian 或 vim 手工编辑,文件监视器会把外部编辑对账回来
<data_dir>/db/memory.sqlite派生索引。WAL 模式,FTS5 全文索引 + 实体索引 + 链接邻居检索 + 可选向量
<data_dir>/raw/不可变的净化后 JSONL 段,来自托管工作流
<data_dir>/logs/滚动日志

这个选择继承了 Karpathy 式 LLM wiki 的思路:记忆的最终形态是一棵可以用 grep 搜、可以用 git 回滚、可以手工修正的 Markdown 树,向量数据库退化为可选加速层。页面的版本管理采用原地取代(supersession)链:新版本写入后旧版本标记 supersedes 关系,语义概念累积叠加,情景日志按策略衰减。

检索端 memory_query 用 FTS5 + 实体匹配 + 链接邻居三路 RRF 融合,配置 embedder 后向量余弦相似度加入第四路。查询结果在最终截断前还有一个有界的权威度乘数,按页面类型、层级、是否置顶、正负标签调整相关性——维护中的规则、决策、程序、陷阱页在势均力敌时优先,情景页和历史页保持可搜但不抢位。页面命中会提升 access_count 并更新 last_accessed_at,构成 M8 强化项;memory_feedback 工具补充显式的每页显著性信号。

记忆分层与遗忘曲线

ai-memory 显式实现了认知科学式的四层记忆策略(M8 策略):

生命周期衰减规则
工作记忆当前会话会话结束硬丢弃(observations 表保留取证副本)
情景记忆30 天热 → 180 天冷 → 逐出salience · exp(−λΔt) + σ · log(1+access_count) · exp(−μ · days_since_access)
语义记忆无限期不衰减,仅可通过 M7 LLM 重写被取代
程序记忆无限期不再被观察到则频率衰减

遗忘清理按需触发,也按服务器 [maintenance] 计划运行:超过 frontmatter expires_at: TTL 的页面被硬删除;retention 低于冷阈值的页面被逐出并留下衰减墓碑;墓碑超过 hard_delete_after_days 后连同完整版本祖先一起清除。置顶页面(pinned: true)豁免所有衰减路径。

衰减公式的关键设计:它把「最近访问」和「访问频次」分开编码,log(1+access_count) 压平高频访问的边际收益,exp(−μ · days_since_access) 保证长期不碰的页面即使曾经很热也会退出。对编码 agent 的实际记忆分布来说,这比简单的 LRU 更贴合「上周的调试结论这周还相关,上个月的架构决策下个月才需要重新确认」的使用模式。

跨 harness 交接:Handoff 机制与托管工作流

Handoff 是 ai-memory 解决跨厂商问题的核心抽象。每条 Handoff 记录有 open/accepted/expired 三种状态,按目录匹配投递:

  • memory_handoff_begin 开启一条 owner 作用域的交接,shared=true 可以发布给整个项目
  • memory_handoff_accept 取走并确认最新的自有或共享交接(自动交接按 cwd 匹配)
  • 插入新交接时同 cwd 的旧自动交接过期,避免反复 SessionEnd 造成的堆积

托管工作流(ai-memory run)在此基础上再进一层:为当前仓库/工作树开一个租约,解析目标 harness 或选择最新可用的本地会话,用 invocation 级 run id 标记生命周期调用。ai-memory run claude 结束后接 ai-memory run codex --yolo,宿主在子进程退出时导入原生 transcript 尾部和 Git 检查点,下一个 harness 的 SessionStart 注入未见过的有界事件区间。每个注入的包都带版本化的来源标记,Claude transcript 归一化器会排除「Claude 自己持久化并读回」的包,防止已交付历史递归地再次进入台账。

对没有可靠 SessionEnd 钩子的客户端(Codex、Antigravity CLI),ai-memory finalize-session 提供显式收尾命令,走与原生钩子相同的 SessionEnd 路径。

支持矩阵:一个工具覆盖二十多种 harness

Support Matrix 是这个项目工程量的直接体现。第一梯队完整支持(MCP 配置 + 生命周期钩子 + 原生命令)的包括 Claude Code、Codex、Cursor、Gemini CLI、OpenCode、Devin CLI、Oh My Pi、Pi、OpenClaw、Command Code、Kimi Code、Kiro CLI、Grok Build CLI、Antigravity CLI 等;Claude Desktop、VS Code Copilot、Zed 因宿主未暴露生命周期钩子而只有 MCP 支持。平台层面 Linux/macOS(原生二进制)/Windows(WSL2 或实验性原生)全覆盖,Docker 镜像提供 linux/amd64 和 linux/arm64。

README 的 Support Matrix 里出现了 Hermes Agent,标注为社区支持:核心钩子摄取识别 agent=hermes 和 Hermes 文档化的 shell-hook 载荷,社区维护的 ai-memory-hermes-plugin 提供插件,无第一方安装器。

LLM 和 embedding provider 方面支持 Anthropic、OpenAI、OpenAI OAuth/Codex、GitHub Copilot、Gemini、OpenCode Zen/Go、OpenAI 兼容端点、通用 OIDC 设备授权;embedding 支持 OpenAI、Voyage、Gemini 以及 Ollama、LM Studio、vLLM 等免密钥 OpenAI 兼容端点。

安装:三条路径

Arch Linux 用户走 AUR:yay -S ai-memory-bin(预编译)或 yay -S ai-memory(源码编译),自带 systemd 单元。

其他平台的 Docker 路径分三步:安装一个跑在 Docker 里的 CLI wrapper(下载时校验 sha256)、启动绑定回环地址的容器、跑两条 install 命令接入 agent:

bash
docker run -d --name ai-memory \
    --restart unless-stopped \
    -p 127.0.0.1:49374:49374 \
    -v ai-memory-data:/data \
    -e AI_MEMORY_LLM_PROVIDER=anthropic \
    -e ANTHROPIC_API_KEY=sk-ant-... \
    akitaonrails/ai-memory:latest

ai-memory install-mcp   --client claude-code --apply
ai-memory install-hooks --agent  claude-code --apply

不配置任何 API key 也能跑:零 LLM 模式下 FTS5 检索照常工作,会话摘要和自动交接完全由规则生成,LLM 整合和 auto-improve 调度器是可选增强。macOS Apple Silicon 官方推荐原生二进制(ai-memory-macos-aarch64.tar.gz)。

install-mcp 和 install-hooks 都是幂等的:重跑只替换 ai-memory 自己的条目,保留其他服务器和钩子配置,修改前写带时间戳的 .bak 备份。仓库子目录或链接工作树场景加 --project-strategy repo-root 让捕获折叠到主 git 仓库名。

安全边界:把 agent 输出当不可信数据

安全设计贯穿了整个架构文档。钩子路由器的 sanitizer 是不可信文本进入存储的唯一路径,所有 LLM 提示把仓库文本、观察记录、wiki 页面、既往提案当作不可信数据处理,用显式信任边界和分隔符包裹自动注入的交接、项目简报、托管工作流包。

内容上限逐层设防:用户提示和压缩后摘要上限 16 KiB,通知和工具摘录上限 2 KB,每条观察体有 16 KiB 兜底;HTTP 请求整体上限 10 MiB。原生钩子命令在本地 spool 前应用事件级上限,服务器在解析每个请求时重复校验,老客户端无法绕过。

项目隔离按构造保证:每个项目住在 <wiki_root>/<workspace_id>/<project_id>/ 下,由稳定 UUID 键控,两个项目可以有相同页面路径而不冲突。共享服务器可开启 [slots] per_user = true,引擎和 MCP 槽位写入进入从认证身份派生的有界命名空间——这个边界限制的是提示注入面,页面访问本身刻意不变。备份用 SQLite 在线备份 API(ai-memory backup --to <tarball>),源库保持可写。

MCP 工具面:18 个工具的窄面纪律

工具清单覆盖查询(memory_query/memory_recent/memory_read_page/memory_status/memory_briefing/memory_explore)、交接(begin/accept/cancel)、整合(memory_consolidate/memory_auto_improve)、写入(memory_write_page/memory_feedback)、维护(memory_delete_page/memory_forget_sweep/memory_lint/memory_install_self_routing)和会话观察回读(memory_read_session_observations)。架构文档明确记录了从最初 10 个工具到 17 个(现为 18 个)的扩张过程,每加一个工具都要论证自己挣到这个位置——「窄面纪律」在工具数量膨胀后依然被当作设计原则反复重申。

写在最后

agent 记忆系统今年密集涌现,向量数据库加 write_note 仪式是常见形态。ai-memory 选了另一条路:真相源是一棵可 grep、可 git 回滚、可手工编辑的 Markdown 树,SQLite 和向量只是派生加速层;捕获完全由生命周期钩子驱动,用户无需手写任何记忆条目;衰减、逐出、墓碑、TTL 构成完整的遗忘机制,而不是只进不出的堆料。对在多个编码 CLI 之间切换的开发者,这是一个可以直接 docker run 起来试的方案。

GitHub:https://github.com/akitaonrails/ai-memory

ai-memory GitHub 仓库