Phase 2 / Memory & Context / s06

Workspace Memory

CLAUDE.md / AGENTS.md——跨会话的持久化知识

动机:为什么需要跨会话记忆

Session management(s04)让 agent 记住单次对话的历史。Context 压缩(s05)防止 context window 溢出。但有一类信息既不属于单次对话,也不应该被压缩丢弃:

这类信息需要永久存储,在每次新会话开始时自动注入到 system prompt 中。

OpenClaw 的做法非常简单:在项目根目录放一个 CLAUDE.md 文件。agent 在每次会话开始时读取它,注入 system prompt。用户和 agent 都可以编辑这个文件。

Ground Truth:真实的 Workspace Memory

Workspace Memory——跨代码库对比

跨会话记忆在不同代码库中的实现

pythonagent/memory.py + context.py
1# memory.py
2class MemoryStore:
3 """两层记忆:
4 MEMORY.md — 长期事实 (可被更新覆写)
5 HISTORY.md — 时间线日志 (只追加)"""
6
7 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"
11
12 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 ""
15
16# context.py
17class ContextBuilder:
18 BOOTSTRAP_FILES = [
19 "AGENTS.md", "SOUL.md", "USER.md",
20 "TOOLS.md", "IDENTITY.md",
21 ]
22
23 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)
pythonagent/memory.py consolidate()
1async def consolidate(self, session, provider, model):
2 """LLM 总结旧消息, 更新长期记忆"""
3
4 # 格式化旧消息供 LLM 处理
5 for m in old_messages:
6 lines.append(
7 f"[{m['timestamp'][:16]}] "
8 f"{m['role'].upper()}: {m['content']}"
9 )
10
11 # 构造压缩 prompt
12 prompt = f"""Process this conversation:
13## Current Long-term Memory
14{current_memory or "(empty)"}
15## Conversation to Process
16{messages}"""
17
18 # 让 LLM 调用 save_memory tool
19 response = await provider.chat(
20 messages=[{
21 "role": "system",
22 "content": "You are a memory consolidation agent."
23 }, {
24 "role": "user", "content": prompt
25 }],
26 tools=_SAVE_MEMORY_TOOL,
27 )
28
29 # 保存结果
30 args = response.tool_calls[0].arguments
31 self.append_history(args["history_entry"])
32 self.write_long_term(args["memory_update"])

关键观察:

构建: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 "写一个函数把列表中的字符串转大写"

可以尝试的提示:

  1. 写入 MEMORY.md 然后问 agent 相关规则
  2. 删除 MEMORY.md 再运行,对比 agent 行为
  3. 让 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 的记忆。

其他差距

第一性原理思考

Agent memory 本质上有两种——事实型(当前有效的信息)和叙事型(发生过什么)。这对应数据库中”状态”(最新的行)vs”日志”(WAL/binlog)的区分。nanobot 把这两种分开存储是正确的设计:如果混在一起,过时的事实会和历史事件纠缠,LLM 很难判断哪些信息还有效。这也解释了为什么简单的 “把所有对话追加到一个文件” 的方案在实践中效果不好——它把两种性质完全不同的信息混为一谈。