# 140行代码实现 Open Claw 的 Memory 架构

> Site index: <https://blog.suxiong.me/llms.txt>

Original: [140行代码实现 Open Claw 的 Memory 架构](https://blog.suxiong.me/blog/ai-agent-memory-architecture)

Author: Suxiong

Date: 2026-02-15

本文介绍一种极简的 Memory 架构：基于 Markdown 文件的两层记忆模型。代码约 140 行，无外部依赖。

Tags: AI / Agent / Memory / 架构

## 引子

Context 解决"Agent 知道什么"，Memory 解决"Agent 如何学习"。

LLM 是无状态的。每次 API 调用都是独立的，模型不记得上一轮对话说了什么。要让 Agent 表现出记忆能力，需要一套持久化机制。

本文介绍一种极简的 Memory 架构：基于 Markdown 文件的两层记忆模型。代码约 140 行，无外部依赖。

## Memory 的独特挑战

Memory 和静态配置有本质区别：配置是人类写好让 Agent 读取，Memory 是 Agent 在对话中自己写入。

这带来三个设计目标：

| 目标  | 含义                       |
| --- | ------------------------ |
| 自主性 | Agent 判断什么值得记住，不依赖用户显式指令 |
| 时效性 | 区分持久信息和临时笔记，避免混淆         |
| 一致性 | 多轮对话中的数据同步，避免读写冲突        |

同时有两个约束：

- **无外部依赖**：不引入数据库、向量存储、Redis 等基础设施
- **人机可编辑**：用户能直接打开文件修改记忆内容

## 两层记忆模型

### 长期记忆

文件路径：`memory/MEMORY.md`

存储内容：用户画像、持久偏好、项目上下文。

```markdown
# User Preferences

## 关于用户

- 名称：苏雄
- 角色：资深前端工程师
- 技术栈：TypeScript、React、Node.js、Bun
- 偏好：简洁方案，关注架构与可维护性
```

写入时机：

- 用户明确表达偏好（"我喜欢用 Zod 做校验"）
- 用户反复纠正同一类行为（多次要求简洁 → 记录"偏好简洁"）
- 用户主动要求记住某件事

生命周期：跨会话持久，只追加不删除（除非用户明确要求）。

### 日记忆

文件路径：`memory/YYYY-MM-DD.md`（如 `memory/2026-02-15.md`）

存储内容：当日任务、临时上下文、会话笔记。

```markdown
# 2026-02-15 日记

## 今日任务

- [ ] 完成登录功能
- [x] 重构 memory 模块
```

写入时机：任务记录、临时决策、待办事项。

生命周期：按日滚动。今天的笔记不会污染昨天的文件。支持回溯查看历史。

### 为什么是两层

单层记忆的问题：长期记忆被临时信息污染。

如果把"今天要完成登录功能"和"用户偏好 TypeScript"放在同一个文件里，几天后"今天"就失去意义了。时效性不同的信息需要物理隔离。

两层模型的类比：

- 长期记忆 = 档案室，存放持久有效的信息
- 日记忆 = 草稿本，记录当天的临时笔记

## Agent 自主写入机制

### 工具复用

Memory 系统不新增专门的 API。Agent 通过已有的 `write_file` 工具写入记忆。

```typescript
// Agent 调用示例
write_file((path = "memory/MEMORY.md"), (content = "..."));
write_file((path = "memory/2026-02-15.md"), (content = "..."));
```

优势：

- 零额外实现。Agent 本身就有文件操作能力。
- 统一心智模型。对 Agent 来说，写记忆就是写文件，无需学习新概念。

### 写入指南注入

Agent 怎么知道自己有记忆能力？通过 system prompt 注入写入指南。

```typescript
getMemoryGuide(): string {
  return `## Memory System

You have a two-layer memory system:

- **Long-term memory**: \`${this.memoryFile}\`
  - Store: user profile, preferences, important context
  - Use \`write_file\` to append/update

- **Daily notes**: \`${this.todayFile()}\`
  - Store: today's tasks, temporary context, session notes
  - Use \`write_file\` to record

**When to remember:**
- User shares personal info (name, preferences, habits)
- Important decisions or context worth persisting
- Tasks or notes for today`;
}
```

指南包含三部分信息：

1. 文件路径（往哪里写）
2. 写入时机（什么时候写）
3. 格式约定（怎么写）

这是"Prompt 驱动行为"的典型应用：不通过代码硬编码逻辑，而是通过自然语言指令让 Agent 自主执行。

### 无状态读取

每轮对话从磁盘重新加载记忆内容：

```typescript
async getMemoryContext(): Promise<string | null> {
  const [longTerm, today] = await Promise.all([
    this.readLongTerm(),
    this.readToday(),
  ]);

  const parts: string[] = [];
  if (longTerm) parts.push(`## Long-term Memory\n\n${longTerm}`);
  if (today) parts.push(`## Today's Notes (${this.todayStr()})\n\n${today}`);

  return parts.length ? parts.join("\n\n---\n\n") : null;
}
```

为什么不在内存中缓存？

Memory 文件可能被两方修改：Agent 通过 `write_file` 写入，用户直接编辑文件。如果在内存中缓存，会出现数据不一致。

无状态读取的代价是每轮多一次磁盘 IO。对于本地文件系统，这个开销可以忽略。

## 学习规则设计

Agent 需要规则来判断什么值得记住。没有规则会导致两个极端：

- **过度记忆**：把所有对话内容都存下来，噪音淹没关键信息
- **遗漏记忆**：用户反复重复同一偏好，Agent 始终不记

### 何时写入

三种触发条件：

1. **显式表达**：用户直接说出偏好

   - "我喜欢用 Zod 做校验"
   - "别用 class 组件"

2. **重复纠正**：用户多次纠正同一类行为

   - 连续三次要求"简洁点" → 记录"偏好简洁回复"

3. **主动要求**：用户明确要求记住
   - "记住这个项目用的是 pnpm"

### 何时不写入

三种应跳过的情况：

1. **一次性指令**："这次用 JavaScript 写"（下次可能又要 TypeScript）

2. **已存在信息**：重复写入只会增加噪音

3. **不确定时**：先询问用户，不要擅自判断

### 格式约定

长期记忆采用结构化 Markdown：

```markdown
## 关于用户

- 名称：...
- 角色：...
- 技术栈：...

## 偏好

- ...

## 项目上下文

- ...
```

约定原则：

- **一行一条**：便于追加和去重，避免复杂解析
- **只追加不删除**：除非用户明确要求移除
- **去重合并**：语义重复的信息更新已有条目而非新增

## 实现细节

### MemoryStore 核心接口

完整实现约 140 行，核心接口：

| 方法                     | 作用             |
| ---------------------- | -------------- |
| `ensureDir()`          | 确保 memory 目录存在 |
| `getMemoryContext()`   | 合并长期记忆 + 今日记忆  |
| `readLongTerm()`       | 读取长期记忆         |
| `readToday()`          | 读取今日记忆         |
| `getRecentMemories(n)` | 回溯最近 N 天的记忆    |
| `getMemoryGuide()`     | 生成写入指南         |

### 日期滚动

日记忆文件按日期命名：

```typescript
private todayFile(): string {
  return resolve(this.memoryDir, `${this.todayStr()}.md`);
}

private todayStr(): string {
  return new Date().toISOString().slice(0, 10); // YYYY-MM-DD
}
```

效果：

- 2026-02-15 的对话写入 `memory/2026-02-15.md`
- 2026-02-16 的对话写入 `memory/2026-02-16.md`
- 自动隔离，无需手动清理

### 回溯能力

支持查看最近 N 天的记忆：

```typescript
async getRecentMemories(days: number = 7): Promise<string | null> {
  const memories: string[] = [];
  const today = new Date();

  for (let i = 0; i < days; i++) {
    const date = new Date(today);
    date.setDate(date.getDate() - i);
    const dateStr = this.formatDate(date);
    const content = await this.safeRead(
      resolve(this.memoryDir, `${dateStr}.md`)
    );
    if (content) {
      memories.push(`### ${dateStr}\n\n${content}`);
    }
  }

  return memories.length ? memories.join("\n\n---\n\n") : null;
}
```

用途：需要回顾近期上下文时，可以加载最近一周的日记忆。

### 容错处理

文件不存在时静默返回 null：

```typescript
private async safeRead(path: string): Promise<string | null> {
  try {
    const content = await readFile(path, "utf-8");
    return content.trim() || null;
  } catch {
    return null;
  }
}
```

这意味着：

- 新用户第一次使用时，记忆文件不存在，系统正常运行
- 用户删除某个记忆文件，不会导致崩溃

## 局限与演进

### 当前局限

**无语义检索**：记忆内容全文注入 system prompt。当记忆量增长后，会占用大量 token。无法按相关性检索。

**无压缩**：长期记忆只追加不删除，文件会无限增长。缺少自动摘要或清理机制。

**无冲突处理**：Agent 和用户可能同时编辑同一文件。当前实现没有锁机制。

### 演进方向

**记忆摘要**：定期对旧记忆做摘要压缩，保留关键信息，释放 token 空间。

**向量化**：将记忆内容向量化存储，支持语义相似度检索。按相关性加载，而非全量注入。

**遗忘机制**：自动识别过时信息（如"今天要做 X"在一周后已无意义），主动清理。

## 小结

本文介绍的 Memory 架构做了三件事：

1. **两层分离**：长期记忆和日记忆物理隔离，解决时效性混淆问题
2. **工具复用**：通过 `write_file` 实现写入，零额外 API
3. **Prompt 驱动**：通过写入指南让 Agent 自主学习，而非硬编码逻辑

核心理念：让 Agent 从"被动配置"走向"主动学习"。
