跨会话还记得你:给 Agent 一块可写的 MEMORY.md
系列回顾:主循环 · 代码库工具 · REPL · 项目上下文 · Skills · 权限 + Write · MCP 概念 · MCP 实现 · Context Budget · Bash · compact 2.0 · autocompact · Hooks
项目上下文 让 Agent 读懂仓库规矩;Skills 按需加载工作流说明书。
但还有一类东西放哪儿都不合适:跨会话的个人/团队偏好——「提交用中文」「别动 auth」「默认用 bun」。
写进AGENTS.md太重、要改仓库规范;写进对话历史,一/clear或新开 REPL 就没了。
这篇讲v6-memory:一块可写的.agents/memory/MEMORY.md,走system prompt通道注入——仍然不改query()。
三类「塞进模型的东西」差在哪?
| AGENTS / CLAUDE | Skills | Memory(本篇) | |
|---|---|---|---|
| 典型内容 | 仓库协作规范 | 可复用工作流说明 | 偏好、约定、会话沉淀 |
| 谁维护 | 人(文档) | 人(SKILL.md) | 人或模型经 Write/Edit |
| 生命周期 | 相对稳定 | 启动扫描目录 | 可跨会话持久;会话中可改 |
| 注入方式 | system 常驻 | 目录常驻 + 正文按需 | system始终有指引段;正文可空 |
/clear | 仍在 | 仍在 | 仍在 |
一句话:
AGENTS 是「这个仓库怎么干活」;Memory 是「我还记得你上次说的偏好」。
它也不替代autocompact:compact 压的是对话历史;Memory 在 system 通道,两边各管各的。
存哪?为什么不扫根目录 MEMORY.md
约定路径只有一个:
.agents/memory/MEMORY.md相对当前工作区cwd。文件不存在时启动照样成功;不会因为缺文件报错退出。
但不等于「完全不告诉模型」——system 里仍会注入一段Memory 指引(见下一节),避免模型自己发明docs/notes/之类路径来记偏好。
不在项目根到处找MEMORY.md,是为了避免和普通文档重名、误注入。
预算:正文最多32KB(UTF-8 字节);超出截断并追加:
[memory truncated at 32KB]启动时还会ensureMemoryDirExists:尽量建好.agents/memory/,方便模型直接 Write,而不必先 mkdir。
怎么进模型?指引始终在,正文可有可无
启动时loadSessionContext拉齐三块,拼进systemPrompt:
1. AGENTS.md / CLAUDE.md(项目说明,可缺) 2. Memory 段(始终有)——路径 + remember 写法 ± MEMORY.md 正文 3. Skills 目录摘要(有技能才有)关键变化:即使 MEMORY.md 缺失或为空,Memory 段也会进 system。
有正文就附上;没有就标明 currently empty。模型因此始终知道:
- 持久记忆写在哪个路径(
.agents/memory/MEMORY.md) - 用户说「记住…」时,用 Write/Edit 更新这一份文件
- 不要另开
docs/notes/之类路径当跨会话记忆
对应组装(formatMemoryPromptSection始终 push):
buildSystemPrompt(projectContext,skills,memorySnapshot.content,cwd)// 内部:parts.push(formatMemoryPromptSection(cwd, memory))空文件时,模型看到的大致是:
## Agent Memory You have a persistent, file-based memory at `.agents/memory/MEMORY.md` … If the user explicitly asks you to remember something, save it immediately … Do not invent other note paths (for example `docs/notes/`) … ## MEMORY.md Your MEMORY.md is currently empty. When you save new memories, they will appear here.有正文时,最后一段换成截断后的 MEMORY.md 内容。
L1 CLI 启动 → ensure 目录 + loadSessionContext(含 memory 快照) L2 QueryEngine → 持有 systemPrompt + memoryRefresh L3 runTurn 前 → 按 mtime 刷新 Memory,变了才重建 systemPrompt L4 query() → 照旧透传 systemPrompt(骨架不变)会话中改了文件怎么办?mtime 刷新
Memory 允许在会话里被改(模型 Write/Edit,或你手动改文件)。若启动只读一次,改完模型还看不见。
所以QueryEngine.runTurn开头会:
refreshMemoryIfNeeded() → 看 .agents/memory/MEMORY.md 的 mtime → 没变:复用缓存 → 变了 / 删了:重读(或清空)→ 重建 systemPrompt这样:
- 本轮写进 MEMORY.md 的偏好,下一轮system 就能带上
- 没改文件时零多余读盘成本(只 stat)
怎么写?没有专用 Memory 工具
刻意不做「旁路写入」:
| 方式 | 行为 |
|---|---|
| Write / Edit | 目标路径指向.agents/memory/MEMORY.md,走既有门卫(REPLy/N/ headlessALLOW_WRITE) |
| Read | 模型可直接读该路径核对内容 |
/memory | 只读展示:路径 + 字符数;不callModel |
REPL:
/memory # Memory: D:\…\.agents\memory\MEMORY.md(128 chars) # 或不存在: # Memory: …\MEMORY.md(文件不存在)/help已列出/memory。
写入仍要过权限——Memory 可写,但不等于「静默可写」。
30 秒试一把
# 甚至可以不先建文件:启动也会 ensure 目录,并注入「currently empty」指引bun run dev# 试:「记住:包管理器默认 bun」# 模型应 Write/Edit `.agents/memory/MEMORY.md`,而不是 docs/notes/# 或预先写好正文:mkdir-p.agents/memorycat>.agents/memory/MEMORY.md<<'EOF' # Agent Memory - 回复尽量用中文 - 包管理器默认 bun,不要擅自改成 npm EOFbun run dev有正文时问:「我们默认用什么包管理器?」——应能直接根据 Memory 回答,不必先 Read。
再让它改 Memory(会走 Write/Edit + 确认),或你本地改文件后继续聊——下一轮应吃到新内容。
/memory # 看路径与长度 /clear # 清空对话;Memory 指引(及正文)仍在 system 里和主循环的关系
L1 CLI / REPL → ensure 目录;加载快照;/memory 只读状态 L2 QueryEngine → runTurn 前 mtime 刷新 + 重建 systemPrompt L3 query() → 不变(system 里始终有 Memory 段) L4 services/memory → load / truncate / refresh / formatMemoryPromptSectionMemory 改的是「system 里常驻什么」,不是 ReAct 怎么转,也不是对话历史怎么压。
和 Skills / Hooks 的分工:
| 机制 | 一句话 |
|---|---|
| Skills | 按需说明书(短 tool_result + 正文注入) |
| Hooks | 工具前后拦 / 记 |
| Memory | 跨会话偏好,system 常驻 + 可写 |
刻意没做什么?
| 没做 | 意味着什么 |
|---|---|
| session memory compact | 不把整段对话摘要进 Memory 文件;那是另一套管线 |
| 云端 memory store / 多 vault | 只认本地一个文件 |
| topic 文件 + MEMORY 索引(双步) | 本版单文件;指引里直接写 MEMORY.md |
| 专用 Memory 工具 | 少一个表面;Read/Write/Edit 够用 |
根目录扫描MEMORY.md | 避免文档噪音 |
| 子代理共享 / 清理语义 | 留给后续子代理篇 |
这一刀验证的是「可写记忆」的最小面:
约定路径 → 始终注入指引(±正文)→ 预算截断 → ensure 目录 → 轮次前 mtime 刷新 → 写入走既有权限 → /memory 可观测系列拼图
| 篇 | 能力 |
|---|---|
| 项目上下文 | 仓库说明书进 system |
| Skills | 工作流按需加载 |
| compact 三连 | 对话历史预算 |
| Hooks | 工具生命周期扩展 |
| 本篇 | 跨会话可写记忆 |
Harness 再多一根柱子:循环、工具、会话、项目上下文、技能、权限、MCP、预算、hooks、memory。
你可以从这里带走什么?
- Memory ≠ AGENTS ≠ 对话历史——偏好持久化,规范归文档,聊天归 messages。
- 指引始终在,正文可空——缺文件也告诉模型写哪儿、怎么 remember,避免乱开笔记路径。
- 走 system,不走 messages——
/clear清不掉;和项目上下文同一通道。 - 要有预算——正文 32KB 硬顶,防止一块记忆撑爆上下文。
- 刷新靠 mtime——会话中改文件,下一轮自动生效。
- 写入不旁路——仍过 Write/Edit + 门卫;启动 ensure 目录只是为了好写。
- 不替代 compact——Memory 管偏好;autocompact 管长对话。
仓库与相关文档
- GitHub:https://github.com/jimchou-h/react-agent-mini
- 上一篇:Hooks · 项目上下文
- 源码:src/services/memory
- 术语表:CONTEXT.md
- 拼装:systemPrompt.ts
欢迎 Star、Issue 和 PR。
本文基于 react-agent-mini 变更v6-memory(MEMORY.md + 始终注入路径/remember 指引 + ensure 目录 + mtime 刷新 +/memory)撰写。
