动机:为什么需要跨会话记忆
Session management(s04)让 agent 记住单次对话的历史。Context 压缩(s05)防止 context window 溢出。但有一类信息既不属于单次对话,也不应该被压缩丢弃:
- 项目的编码规范(“用 Python 3.12,总是写 type hints”)
- 用户的偏好(“不要用 class-based views”)
- 上次会话发现的重要信息(“数据库连接字符串在 .env 里”)
这类信息需要永久存储,在每次新会话开始时自动注入到 system prompt 中。
OpenClaw 的做法非常简单:在项目根目录放一个 CLAUDE.md 文件。agent 在每次会话开始时读取它,注入 system prompt。用户和 agent 都可以编辑这个文件。
Ground Truth:真实的 Workspace Memory
Workspace Memory——跨代码库对比
跨会话记忆在不同代码库中的实现
1# memory.py2class MemoryStore:3 """两层记忆:4 MEMORY.md — 长期事实 (可被更新覆写)5 HISTORY.md — 时间线日志 (只追加)"""67 def __init__(self, workspace: Path):8 self.memory_dir = workspace / "memory"9 self.memory_file = self.memory_dir / "MEMORY.md"10 self.history_file = self.memory_dir / "HISTORY.md"1112 def get_memory_context(self) -> str:13 long_term = self.read_long_term()14 return f"## Long-term Memory\n{long_term}" if long_term else ""1516# context.py17class ContextBuilder:18 BOOTSTRAP_FILES = [19 "AGENTS.md", "SOUL.md", "USER.md",20 "TOOLS.md", "IDENTITY.md",21 ]2223 def build_system_prompt(self) -> str:24 parts = [self._get_identity()]25 # 加载工作区配置文件26 bootstrap = self._load_bootstrap_files()27 if bootstrap:28 parts.append(bootstrap)29 # 加载长期记忆30 memory = self.memory.get_memory_context()31 if memory:32 parts.append(f"# Memory\n\n{memory}")33 return "\n\n---\n\n".join(parts)
1async def consolidate(self, session, provider, model):2 """LLM 总结旧消息, 更新长期记忆"""34 # 格式化旧消息供 LLM 处理5 for m in old_messages:6 lines.append(7 f"[{m['timestamp'][:16]}] "8 f"{m['role'].upper()}: {m['content']}"9 )1011 # 构造压缩 prompt12 prompt = f"""Process this conversation:13## Current Long-term Memory14{current_memory or "(empty)"}15## Conversation to Process16{messages}"""1718 # 让 LLM 调用 save_memory tool19 response = await provider.chat(20 messages=[{21 "role": "system",22 "content": "You are a memory consolidation agent."23 }, {24 "role": "user", "content": prompt25 }],26 tools=_SAVE_MEMORY_TOOL,27 )2829 # 保存结果30 args = response.tool_calls[0].arguments31 self.append_history(args["history_entry"])32 self.write_long_term(args["memory_update"])
关键观察:
- nanobot 区分两种记忆文件:MEMORY.md(事实,可覆写)和 HISTORY.md(日志,只追加)
- Context Builder 在构建 system prompt 时自动加载 AGENTS.md 等工作区文件
- 压缩流程使用 LLM 自己做总结——形成”用 AI 管理 AI 记忆”的闭环
- Bootstrap files 的加载顺序有讲究:AGENTS.md 是项目级指令,SOUL.md 是个性配置
构建:Workspace Memory
class WorkspaceMemory:
"""工作区级别的持久化记忆"""
MEMORY_FILES = ["AGENTS.md", "CLAUDE.md"]
def __init__(self, workspace: Path):
self.workspace = workspace
self.memory_dir = workspace / "memory"
self.memory_dir.mkdir(exist_ok=True)
self.memory_file = self.memory_dir / "MEMORY.md"
def load_project_instructions(self) -> str:
"""加载项目级指令 (AGENTS.md / CLAUDE.md)"""
for filename in self.MEMORY_FILES:
path = self.workspace / filename
if path.exists():
content = path.read_text(encoding="utf-8")
return f"## 项目指令 ({filename})\n\n{content}"
return ""
def load_long_term_memory(self) -> str:
"""加载长期记忆"""
if self.memory_file.exists():
content = self.memory_file.read_text(encoding="utf-8")
return f"## 长期记忆\n\n{content}"
return ""
def save_memory(self, content: str):
"""保存/更新长期记忆"""
self.memory_file.write_text(content, encoding="utf-8")
def inject_into_prompt(self) -> str:
"""注入到 system prompt"""
parts = []
instructions = self.load_project_instructions()
if instructions:
parts.append(instructions)
memory = self.load_long_term_memory()
if memory:
parts.append(memory)
return "\n\n".join(parts)
变更内容
| 组件 | 之前 (s05) | 之后 (s06) |
|---|---|---|
| 工作区记忆 | 无 | WorkspaceMemory 类 |
| 项目指令 | 无 | MEMORY.md / AGENTS.md 加载 |
| 长期记忆 | 无 | 跨会话记忆保存 |
本课代码: agents/s06_workspace_memory.py — 227 行 (新增 17 行)
试一试
cd public/code
echo "## Rules\n- Always use type hints\n- Prefer list comprehensions" > MEMORY.md
python agents/s06_workspace_memory.py "写一个函数把列表中的字符串转大写"
可以尝试的提示:
- 写入
MEMORY.md然后问 agent 相关规则 - 删除
MEMORY.md再运行,对比 agent 行为 - 让 agent 自己写入一些记忆,下次运行看是否生效
距离生产
双层记忆架构: MEMORY.md vs HISTORY.md
nanobot 的 MemoryStore 把记忆分成两个文件:MEMORY.md 存储当前有效的事实(项目用 Python 3.12、数据库连接字符串在 .env 里),HISTORY.md 存储发生过的事件(2024-01-15 修复了登录 bug、2024-01-16 重构了 API 层)。关键区别:MEMORY.md 会被覆写(旧事实被新事实替换),HISTORY.md 只会追加(事件永远不会被删除)。
LLM 作为记忆压缩 agent
nanobot 的 consolidate() 方法把”压缩记忆”这件事交给另一个 LLM 调用来做。它把旧的对话消息和当前的长期记忆一起发给 LLM,让 LLM 调用 save_memory 工具来更新记忆。这形成了一个有趣的闭环:用 AI 来管理 AI 的记忆。
其他差距
- 无 memory 搜索——我们只全量注入,生产支持按需检索(OpenClaw 的
memory-tool.ts) - 无自动触发——我们需要手动保存记忆,生产系统在每次对话结束时自动压缩
第一性原理思考
Agent memory 本质上有两种——事实型(当前有效的信息)和叙事型(发生过什么)。这对应数据库中”状态”(最新的行)vs”日志”(WAL/binlog)的区分。nanobot 把这两种分开存储是正确的设计:如果混在一起,过时的事实会和历史事件纠缠,LLM 很难判断哪些信息还有效。这也解释了为什么简单的 “把所有对话追加到一个文件” 的方案在实践中效果不好——它把两种性质完全不同的信息混为一谈。