
Agent Memory 很容易被解释成“保存聊天记录”。
我不太接受这个解释。聊天记录当然可以保存,但保存下来以后,Agent 下次真的知道该看哪几句吗?它能分清临时猜测、最后结论、用户长期偏好和当天没做完的 TODO 吗?
所以这篇不按“记忆功能介绍”来写。我更关心源码里怎么处理五件事:写入什么、放在哪里、怎么检索、怎么注入、怎么把错的记忆修掉。
“记住这件事”不是存聊天记录
AI Coding 用久以后,几乎所有人都会遇到同一个问题:
这个项目上次怎么决定的?那个坑之前是不是踩过?用户到底偏好哪种写法?这次 compact 之前做到了哪一步?最粗暴的解决办法,是保存聊天记录。
但聊天记录不是记忆。
聊天记录通常太长、太吵、太临时。里面混着试错、废话、错误假设、被推翻的方案、工具输出和中间状态。
有用的 Agent Memory,应该回答更工程的问题:
什么事实值得长期保留?什么只是今天的进展?什么属于某个长期主题?什么是未完成待办?当前任务应该召回哪些旧信息?召回内容如何控制 token?写入以后如何索引?过期和冲突怎么处理?这篇文章我用三个仓库做样本,但主线放在 jayzeng/agentmemory:
jayzeng/agentmemory:本地 Markdown + qmd 检索 + skill 注入clawmem-ai/clawmem-claude-code-plugin:hook auto-recall + repo-backed memoryGentleman-Programming/engram:SQLite + FTS5 + MCP memory tools先说结论:
项目记忆不是“把过去都塞回来”。项目记忆是一套选择、压缩、检索、注入和维护机制。
agentmemory 的第一选择:用文件系统当记忆层
agentmemory 的 README 说得很清楚:默认目录是 ~/.agent-memory/。
里面有:
~/.agent-memory/ MEMORY.md SCRATCHPAD.md daily/ 2026-02-15.md topics/ auth.md四类文件职责不同:
MEMORY.md:长期高信号事实SCRATCHPAD.md:跨 session 待办daily/:每天的工作日志topics/:跨天演进的主题记录这个设计很朴素,但很适合工程师。
文件可读、可编辑、可 diff、可备份、可 git 管理。
它没有一上来引入复杂向量库,而是让记忆先落成 Markdown。
src/core.ts 里也能看到这个设计:
const DEFAULT_MEMORY_DIR = process.env.AGENT_MEMORY_DIR ?? path.join(process.env.HOME ?? "~", ".agent-memory");let MEMORY_FILE = path.join(MEMORY_DIR, "MEMORY.md");let SCRATCHPAD_FILE = path.join(MEMORY_DIR, "SCRATCHPAD.md");let DAILY_DIR = path.join(MEMORY_DIR, "daily");let TOPICS_DIR = path.join(MEMORY_DIR, "topics");Agent Memory 的底层存储不一定要神秘。
很多项目记忆,其实就是几个不同生命周期的文本集合。
复杂的是后面的检索和注入。
记忆分层:长期事实、当天进展、主题和待办不能混在一起
skills/codex/SKILL.md 里有一句话很值得看:
Default to daily. Long-term is rare.这句话比很多“记忆系统介绍”都有价值。
因为记忆系统最容易坏在写入侧。
如果每件事都写进长期记忆,MEMORY.md 很快变成垃圾桶。
如果所有进展都只写 daily,下一次启动又很难直接看到高信号事实。
agentmemory 的决策表大概是:
进展、调查、修 bug -> daily跨天跟踪的主题 -> topic用户明确说 remember 的长期偏好 -> long_term重复出现的模式 -> long_termTODO / follow-up -> scratchpadmemory hygiene 的意思就是:记什么和不记什么,同样重要。
一个好的记忆插件,不只是提供 write()。
它还要告诉 Agent:
什么不该写进长期记忆。这对 AI Coding 特别要命。
模型很容易“过度记忆”。
用户说一句临时偏好,它可能当成永久规则。
今天为了一个 bug 做的权宜方案,它可能写进长期架构。
所以记忆系统必须有分层。
注入上下文,不是全部读回来
src/core.ts 的 buildMemoryContext() 是 agentmemory 里最值得读的函数之一。
它按固定顺序组装上下文:
1. open scratchpad items2. recent topic entries3. today's daily log4. search results5. MEMORY.md6. yesterday's daily log每一类都有独立预算:
scratchpad:2K charstopics:2K charstoday daily:3K charssearch results:2.5K charsMEMORY.md:4K charsyesterday daily:3K charstotal cap:16K chars它不是简单 cat ~/.agent-memory/*。
它在做上下文预算。
为什么 scratchpad 第一?
因为未完成待办是当前最该被看到的东西。
为什么 MEMORY.md 反而排在 search results 后面?
因为长期记忆可能和当前任务无关,而检索结果更可能相关。
为什么 daily 用 tail?
因为当天最新进展通常在文件后面。
为什么 MEMORY.md 用 middle truncate?
因为长期文件开头和结尾都可能重要,简单截头或截尾都容易偏。
这些细节都说明一件事:
记忆注入不是存储问题,是排序和裁剪问题。这段顺序其实透露了 agentmemory 的取舍。
它没有把长期记忆放在最前面,而是先给 Agent 看 scratchpad、topic、today,再看 search results 和 MEMORY.md。这和人类做项目的习惯很像:先看现在没做完什么,再看最近围绕这个主题发生了什么,最后再翻长期原则。
如果反过来,把 MEMORY.md 永远放第一位,Agent 会很容易被长期规则带偏。长期规则通常更抽象,当前任务的细节反而在 daily 和 topic 里。
所以记忆注入最难的不是“召回更多”,而是“谁先出现”。上下文窗口里,先出现的东西往往会改变模型后面的判断。
检索可以增强,但不能拖垮主流程
agentmemory 可选接入 qmd。
qmd 提供 keyword、semantic、deep 三种搜索模式。agent-memory search 会根据模式调用不同 qmd 子命令:
keyword -> qmd searchsemantic -> qmd vsearchdeep -> qmd querysearchRelevantMemories(prompt) 里有几个很工程化的保护:
去掉控制字符查询截断到 200 chars检查 collection 是否存在只取 top 33 秒 timeout失败静默降级这段逻辑的重点不是“用了语义搜索”。
而是:
记忆检索不能拖垮主流程。如果 qmd 没装,基础读写仍然可用。
如果 qmd 超时,注入搜索结果这一段直接缺席。
如果语义搜索缺 embeddings,会提示用户跑 qmd embed。
一个插件就该这样降级。
记忆是增强层,不应该变成 Agent 每次启动的单点故障。
写入后的索引:异步、去抖、不中断
agentmemory 在写 daily、topic、long_term、scratchpad 后,会触发 qmd update。
scheduleQmdUpdate() 的逻辑是:
if (getQmdUpdateMode() !== "background") return;if (!qmdAvailable) return;if (updateTimer) clearTimeout(updateTimer);updateTimer = setTimeout(() => { updateTimer = null; execFileFn("qmd", ["update"], { timeout: 30_000 }, () => { scheduleQmdEmbed(); });}, 500);这里有几个很实用的取舍:
background 模式才自动更新qmd 不可用就跳过500ms debounceupdate 后再触发 embedtimer unref,不阻止进程退出记忆写入应该快。
索引更新应该尽量自动。
但索引失败不能影响当前工具调用。
这和日志系统类似:主业务不能因为日志后端慢就卡死。
distil:长期记忆不是追加,是周期性整理
distilMemories() 也值得单独看。
它会读取 daily 和 topics,解析 timestamp、tag、link、file path、command,然后按 tag 生成一个新的 MEMORY.md 索引。
它还会保留原有 ## Pinned 区域。
这很像人类整理笔记:
日记是流水账主题是事件线长期记忆是整理后的索引Pinned 是永远不该丢的高优先级内容有用的长期记忆,不应该靠 Agent 每次随手 append。
随手 append 很快会失控。
更好的方式是:
大部分内容先写 daily/topic定期 distil把高频 tag 和关键结论整理进 MEMORY.md这差不多就是“记忆”和“日志”的分界线。
日志负责保真。
记忆负责可用。
Skill 层:让 Agent 主动用记忆,而不是等用户提醒
skills/codex/SKILL.md 规定了几个行为:
session start 先运行 agent-memory context --no-search相关任务先 searchsession end 后写 daily完成 scratchpad 项要标 done长期记忆要少写MEMORY.md 要控制在约 50 行高信号内容注意,它不是只给用户提供 CLI。
它把使用记忆这件事变成 Agent 行为合同。
如果记忆系统需要用户每次提醒“你先查一下之前怎么做的”,它就不是真正的 Agent Memory。
好的记忆系统应该让 Agent 在这些时机主动行动:
session start:加载基础上下文遇到旧主题:检索相关记忆做完重要工作:写 daily/topic发现长期偏好:谨慎写 long_term结束 session:总结和清理 scratchpadmemory plugin 和普通笔记工具的差别,就在这里。
笔记工具服务人。
Agent Memory 服务 Agent 的上下文生命周期。
ClawMem:hook auto-recall + repo-backed memory
clawmem-ai/clawmem-claude-code-plugin 走的是另一条路线。
它是 Claude Code plugin,它的 manifest 有 configSchema,可以配置:
baseUrldefaultRepotokenconsoleBaseUrlmemoryRecallLimitmemoryAutoRecallLimithooks/hooks.json 挂了:
UserPromptSubmitPostToolUseStopSessionEndREADME 里写得很清楚:
UserPromptSubmit hook runs recall query sanitization and injects additionalContext.Stop hook mirrors turns into a type:conversation issue.SessionEnd closes the issue.这和 agentmemory 的差别很明显:
agentmemory 更像本地文件系统记忆。
ClawMem 更像“带 hook 的远程/仓库后端记忆”。
它会在用户 prompt 提交时自动 recall,把结果注入 hookSpecificOutput.additionalContext。
同时,它会把会话镜像成 issue/comment。
这类方案的优势是协作和远程可见性更强。
风险是网络、权限、token、后端可用性和隐私边界都变重。
所以 ClawMem README 里也强调了:
collaboration_* write operations require confirmed=truememory_store computes sha256(detail) for idempotent duplicate merge记忆系统一旦进入远程协作层,就必须考虑确认、幂等和权限。
Engram:SQLite + FTS5 + MCP 工具
engram-agent-memory 走的是第三条路线。
它用 Go binary + SQLite + FTS5,并通过 MCP 暴露记忆工具:
mem_savemem_searchmem_contextmem_session_summarymem_get_observationmem_suggest_topic_keymem_updatemem_deletemem_statsmem_timelineinternal/mcp/mcp.go 里还有 tool profile:
agent:AI agents 编码时需要的工具admin:TUI / dashboard / manual curation 工具all:全部工具这个分层很值得借鉴。
不是所有记忆工具都应该暴露给 Agent。
mem_search、mem_save、mem_context 属于 agent 常用工具。
mem_delete、mem_stats、mem_timeline 更偏管理和维护。
Engram 的 OpenCode adapter 还会注入一段 memory protocol,要求 Agent 在 bugfix、architecture decision、discovery、config change 等时机主动 mem_save。
这和 agentmemory 的 skill 思路一致,只是底层存储和调用方式不同:
agentmemory:Markdown files + qmd + skill/CLIClawMem:hooks + backend issue/memory toolsEngram:SQLite/FTS5 + MCP tools + agent protocol三者共同点是:都不是简单存聊天。
还有一层更容易被忽略:冲突关系。
如果一条新记忆和旧记忆说法不一样,系统不能只做追加。追加看起来最安全,其实最危险,因为下一次 mem_search 可能同时召回两条互相打架的结论,Agent 又会自己脑补谁对谁错。
Engram 在这件事上做得更显性。mem_save 会按 normalized content hash 做精确去重;如果传了 topic_key,同一个 project + scope + topic_key 下的新观察会更新到同一条演进记忆上,并增加 revision_count。这说明它不希望“同一主题每次都长出一条新记忆”。
更关键的是 mem_judge。
当 mem_save 发现候选冲突时,会返回 candidates[] 和 judgment_required: true。Agent 需要读候选记忆,再调用 mem_judge 标记两条记忆之间的关系:
relatedcompatiblescopedconflicts_withsupersedesnot_conflict这个关系不是写在 prompt 里的临时判断,而是落到 memory_relations 表里。后续 mem_search 结果会直接带上关系注解,比如某条记忆 supersedes 了哪条、被哪条 superseded,或者和哪条存在 judged conflict。重新裁决时,旧关系会被覆盖。
这比“发现错了就删掉旧记忆”更稳。
因为工程记忆经常不是简单对错,而是版本演进:以前的接口规则在老模块仍然有效,新模块已经废弃;某个 workaround 曾经成立,但现在被正式方案替代。scoped、supersedes、conflicts_with 这些关系,能让 Agent 在召回时看到上下文里的时间和适用边界,而不是只看到两段互相矛盾的文字。
一套记忆系统至少要有五条路径
一个成熟的 Agent Memory 系统,至少有五个路径:
写入路径:session/work/tool result -> daily/topic/long-term/store检索路径:user prompt -> query -> search -> ranked snippets注入路径:scratchpad/topic/today/search/MEMORY -> bounded context维护路径:daily/topic -> distil -> curated MEMORY恢复路径:compaction/session start -> context/session summary -> continue如果缺其中任何一个,记忆都会变形。
只有写入,没有检索,会变成日志。
只有检索,没有注入,Agent 还是不会主动用。
只有注入,没有预算,会污染上下文。
只有长期记忆,没有 daily/topic,会丢掉演进过程。
只有 daily,没有 distil,会越积越吵。
我更愿意把它看成一个小状态机:
raw event:工具输出、用户偏好、排查结论、TODO -> daily:当天进展,允许粗糙 -> topic:跨天主题,开始形成上下文 -> scratchpad:还没完成的动作 -> distil:从流水里提炼长期事实 -> MEMORY.md:少量高信号规则 -> context:按预算重新注入这个状态机里,最容易出错的是从 daily/topic 到 MEMORY.md 的跃迁。
因为 daily 可以容纳过程,topic 可以容纳不确定性,但 MEMORY.md 一旦写进去,就会反复进入后续 session。临时 workaround、错误假设、一次性偏好,都不应该轻易升级成长记忆。
所以一个好的 memory plugin,必须让“写入长期记忆”变得有点难。不是权限意义上的难,而是判断上的难:默认先写 daily,只有反复出现、被确认、确实能指导后续任务的事实,才值得进 MEMORY.md。
一个具体场景:退款 Agent 下次怎么不重新踩坑
还是用退款系统举例。
今天我们发现一个坑:
部分退款时,优惠券抵扣不能按订单级别直接均摊。要按退款商品行重新计算可退优惠金额。如果只是留在聊天记录里,下次 Agent 很可能又按订单级均摊。
用 agentmemory,第一次修复后应该写 daily:
agent-memory write --content "Fixed partial refund coupon allocation. Refundable discount must be recalculated by refunded item lines, not averaged at order level. #refund #coupon #bugfix"如果后面又多次遇到退款优惠问题,再写 topic:
agent-memory write --target topic --topic "refund-coupon" --content "Partial refund coupon logic depends on item-line allocation. Regression tests should cover mixed coupon + balance payment. #refund #coupon"如果这变成长期规则,再整理进 MEMORY.md:
#refund [[refund-coupon]] Partial refund coupon amount must be recalculated by refunded item lines; do not average order-level coupon across refund requests.下次用户说:
帮我改一下部分退款逻辑Agent 启动时会有 scratchpad、topic、daily、MEMORY.md。
如果 qmd 可用,还可以搜索 部分退款 优惠券 或 refund coupon,把相关记忆注入上下文。
这样它记住的不是聊天,而是可复用的工程事实。
最大的风险:把错的东西记得太牢
Agent Memory 最危险的地方,不是忘记。
是错误记忆。
比如:
某次临时 workaround 被写成长期架构规则。某个已废弃接口仍被记忆引用。用户一时偏好被当成永久偏好。bug 排查中的错误假设被保存成结论。所以记忆系统必须有 hygiene:
long-term 要少写daily 默认承接大多数记录topic 记录演进,不急着下结论distil 时保留 pinned 和高信号内容删除 / 更新 / soft delete 要可用远程写操作要幂等和确认这就是我不喜欢“自动保存所有对话”这个方向的原因。
全量保存看起来安全,实际会把噪声永久化。
Agent 需要的不是更多历史。
是更可靠的可用上下文。
记忆是上下文工程,不是存储功能
看完这些记忆插件源码,我的判断是:
Agent Memory 的竞争点不在数据库选型,而在上下文生命周期设计。文件、SQLite、远程 issue、向量检索、BM25、MCP 都只是实现选择。
最后决定体验的是:
写什么什么时候写怎么找回来按什么优先级注入怎么控制 token怎么降级怎么整理怎么更新和删除错误记忆AI Coding 走到后面,记忆大概率会成为标配。
但好的记忆系统不会像一个无底洞,把所有聊天都吞进去。
它更像一个项目级工作台:短期有 scratchpad,当天有 log,长期有 curated memory,主题有演进线,检索有工具,注入有预算,错误有修正入口。
这才是项目记忆。
不是聊天记录归档。
资料来源
• jayzeng/agentmemory,源码快照:b7267bb6b9fdaca29b9a9ea7f822681441493516,2026-04-05。• agentmemory/README.md:文件布局、CLI、qmd、context injection、memory hygiene。• agentmemory/design.md:filesystem memory、selective injection、16K context budget、qmd 自动设置。• agentmemory/src/core.ts:路径、context builder、qmd search/update、distil、scratchpad、topic parsing。• agentmemory/src/cli.ts:CLI 命令入口。• agentmemory/skills/codex/SKILL.md:Codex 侧记忆使用协议。• clawmem-ai/clawmem-claude-code-plugin,源码快照:6f4d74f02736a527c9f18be5a4ebe303f3e9f7bc,2026-05-11。• clawmem-claude-code-plugin/README.md、.claude-plugin/plugin.json、hooks/hooks.json:auto-recall、conversation mirroring、MCP 工具与配置。• Gentleman-Programming/engram/engram-agent-memory,源码快照:07445ba99eac95083d7c6d56c5d795bb68e90834,2026-05-29。• engram-agent-memory/README.md、DOCS.md、internal/mcp/mcp.go、plugin/opencode/engram.ts:SQLite + FTS5、MCP tools、tool profiles、OpenCode plugin adapter、memory relations、mem_judge。
夜雨聆风