Skip to content

如何为 AI Agent 构建一套真正能用的记忆系统

一份从零到生产的实战指南,基于 5 个 Agent 协作团队 30 天的实际运行经验。

第一部分:为什么 Agent 需要记忆系统

1.1 Context Window 不是记忆

每个 LLM Agent 都有一个致命弱点:上下文窗口(Context Window)不等于记忆。

上下文窗口是临时的。当对话过长,系统会自动压缩(Compaction),丢弃早期内容。当 session 结束,一切归零。Agent 醒来时,对之前发生的事情一无所知 -- 除非它写进了文件。

Session 1: 做了重要决策 A → 没写进文件 → Compaction
Session 2: Agent 完全不知道决策 A 的存在 → 重复讨论 → 浪费时间

这不是理论问题。在实际运行中,我们观察到:

  • Agent 在不同 session 中重复提出相同建议
  • 跨频道(Telegram、Discord)的信息完全隔离
  • Compaction 后 Agent 丢失了关键上下文

1.2 设计哲学

我们的核心原则只有一条:

文件 = 事实来源。你不写进文件的东西 = 你从来不知道的东西。

Agent 的记忆不在它的"脑子"里,而在磁盘上。Context window 是工作台,文件才是仓库。

这意味着:

  • 所有重要信息必须实时写入文件
  • Agent 每次启动都从文件读取状态
  • 不依赖"记得去检查",而是靠系统触发(cron、heartbeat)

1.3 本方案的定位

我们选择了 Markdown 文件 作为记忆的主要载体,而不是纯数据库方案。

TIP

我们的答案是混合方案:Markdown 作为事实来源(可读层),QMD 向量数据库作为检索加速层。写入 Markdown,索引自动同步。

第二部分:三层架构

2.1 短期层:NOW.md

用途: Agent 的"工作台",记录当前状态、优先级、阻塞项。

特点:

  • 每次 heartbeat 覆写(Write),不追加
  • 只保留当天的完成项
  • 是 Compaction 的"救生筏" -- Agent 压缩上下文后首先读这个文件

示例结构:

markdown
NOW.md - Workbench

> Mission: 你的核心目标

## Today (MM/DD)
- [x] 已完成的重要事项
- [!] 需要关注的问题

## P0 Priorities
| # | Item | Status | Owner |
|---|------|--------|-------|
| P0 | 最重要的事 | 进行中 | Agent A |
| P1 | 次重要的事 | 待开始 | Agent B |

## Agent Status
| Agent | Focus |
|-------|-------|
| Agent A | 当前任务 |
| Agent B | 当前任务 |

---
Updated: YYYY-MM-DD HH:MM

WARNING

NOW.md 是唯一允许覆写的记忆文件。其他记忆文件只追加,不覆写。

2.2 中期层:每日日志

用途: 事件流水,记录当天发生的一切。是记忆系统的"原始数据"。

文件名格式: memory/YYYY-MM-DD.md

写入格式:

markdown
## 14:30 — 完成了数据库迁移

迁移 PostgreSQL 从 v14 到 v16,耗时 45 分钟。
关键步骤:先备份 → 升级 → 验证数据完整性。
无数据丢失。

### 15:15 — Agent B 提交了周报

Agent B 在 Discord #reports 频道提交了本周业务报告。
要点:用户增长 12%,留存率稳定在 65%。

写入工具: 推荐用一个带自动时间戳的脚本(下文详述),避免手动编码时间。

关键设计:

  • 追加式(append-only),永不覆写
  • 不同 session 看不到彼此的对话,所以重要信息必须立刻写进日志
  • 格式统一为 ### HH:MM — Title,便于扫描和检索

2.3 长期层:INDEX.md → 结构化子目录

用途: 经过提炼的、结构化的知识库。不是原始事件,而是从事件中抽取的可复用知识。

设计: INDEX.md 是导航枢纽,指向各个子目录。Agent 启动时扫描 INDEX.md,按需加载具体文件。

markdown
Memory Vault Index

> Agent 启动时先扫这个文件,按需加载具体内容。

## Lessons
| 文件 | 优先级 | 状态 | 最后验证 | 说明 |
|------|--------|------|----------|------|
| [[cron-discipline]] | P0 | active | 2026-02-26 | 自动化调度经验 |
| [[infrastructure]] | P1 | active | 2026-02-20 | 基础设施运维 |
| [[api-integration]] | P2 | stale | 2026-01-15 | API 集成(可能过时) |

## Decisions
| 文件 | 说明 |
|------|------|
| [[2026-02-14-architecture-v2]] | 架构升级决策 |

## People
| 文件 | 优先级 | 状态 | 说明 |
|------|--------|------|------|
| [[user-profile]] | P0 | active | 用户画像和偏好 |

关键设计:

  • INDEX.md 包含健康度指标(优先级、状态、最后验证日期),Agent 扫描时能立即判断哪些知识可信
  • P0 优先级的文件 = 核心知识,永不归档
  • stale 标记 = 超过 30 天未验证,信息可能过时

2.4 三层之间的信息流动

┌──────────────┐     实时写入      ┌──────────────────┐
│   对话/事件   │ ──────────────→  │  每日日志 (中期)   │
└──────────────┘                   └────────┬─────────┘

                                    每晚 23:45 反思
                                    (提炼 + CRUD)


┌──────────────┐     每次 heartbeat  ┌──────────────────┐
│  NOW.md (短期) │ ◄──── 覆写 ─────  │ 知识库 (长期)     │
└──────────────┘                    │ lessons/          │
                                    │ decisions/        │
                                    │ people/           │
                                    └──────────────────┘

                                    每周日 GC 归档


                                    ┌──────────────────┐
                                    │  .archive/ (冷存储) │
                                    └──────────────────┘

信息从上到下逐层提炼:

  1. 原始对话 → 写入日志(保留细节)
  2. 日志 → 提炼到知识库(抽取可复用的教训/决策/画像)
  3. 过期数据 → 归档到冷存储(释放检索空间)

第三部分:目录结构与文件规范

3.1 完整目录树

workspace/
├── NOW.md                  # 短期:状态仪表盘(覆写式)
├── MEMORY.md               # 可选:指向 INDEX.md 的指针
├── AGENTS.md               # Agent 操作手册
├── HEARTBEAT.md            # Heartbeat 执行流程

└── memory/
    ├── INDEX.md            # 知识导航(启动时必读)
    ├── YYYY-MM-DD.md       # 每日日志(追加式)

    ├── decisions/          # 战略决策记录
    │   └── YYYY-MM-DD-slug.md
    ├── lessons/            # 可复用教训(按主题)
    │   └── TOPIC.md
    ├── people/             # 人物/Agent 画像
    │   └── NAME.md
    ├── projects/           # 项目状态追踪
    │   └── PROJECT.md
    ├── preferences/        # 用户偏好与边界
    │   └── user-preferences.md
    ├── reflections/        # 每日自省记录
    │   └── YYYY-MM-DD.md
    ├── actions/            # 任务生命周期
    │   ├── open/
    │   ├── in-progress/
    │   └── done/           # >14 天自动归档

    └── .archive/           # 冷数据(以 . 开头,搜索引擎不索引)
        ├── YYYY-MM-DD.md   # 归档的旧日志
        └── reflections/    # 归档的旧反思

3.2 知识文件 Frontmatter 规范

所有 lessons/people/decisions/ 下的文件必须带 YAML frontmatter:

yaml
title: "Cron 调度纪律"
date: 2026-02-13                    # 创建日期
category: lessons                    # lessons | person | decision
priority: P0                        # P0 核心 | P1 重要 | P2 参考
status: active                       # active | superseded | conflict
last_verified: 2026-02-26           # 最后确认内容仍然正确的日期
tags: [cron, automation, reliability]

状态流转:

active ──→ superseded    (被更新版本取代)
active ──→ conflict      (发现矛盾信息,待人工裁决)
conflict ──→ active      (人工裁决后恢复)

3.3 .archive/ 冷存储设计

为什么用 . 开头的目录?

我们使用 QMD 作为语义搜索引擎。QMD 用 Bun.Glob 扫描文件时,硬编码跳过以 . 开头的目录。这意味着:

路径QMD 行为
memory/2026-01-15.md会索引
memory/.archive/2026-01-15.md自动跳过
memory/archive/2026-01-15.md仍会索引(没有点号前缀)

这是一个零配置的隔离方案 -- 不需要修改搜索引擎的配置文件或排除规则,只靠目录命名约定就实现了冷热分离。

TIP

归档的文件不会被删除,仍可通过文件系统直接访问。如果使用 Obsidian,可以在设置中开启"显示隐藏文件"来浏览归档内容。

第四部分:写入机制

4.1 写入工具:memlog.sh

bash
#!/usr/bin/env bash
# memlog.sh — 自动时间戳的日志追加工具
# 用法: memlog.sh "Title" "Content body"

set -euo pipefail

MEMORY_DIR="${MEMORY_DIR:-/path/to/workspace/memory}"
TODAY=$(TZ=Asia/Shanghai date +%Y-%m-%d)
NOW=$(TZ=Asia/Shanghai date +%H:%M)
FILE="$MEMORY_DIR/$TODAY.md"
TITLE="${1:?Usage: memlog.sh \"Title\" \"Body\"}"
BODY="${2:-}"

# 如果文件不存在,创建带日期标题的文件
if [[ ! -f "$FILE" ]]; then
  printf "# %s\n" "$TODAY" > "$FILE"
fi

# 追加带时间戳的条目
{
  printf "\n### %s — %s\n" "$NOW" "$TITLE"
  [[ -n "$BODY" ]] && printf "\n%s\n" "$BODY"
} >> "$FILE"

echo "Logged to $TODAY.md at $NOW"

关键设计决策:

  • 时间戳从系统时间自动获取,不需要 Agent 自己编码(避免幻觉)
  • 追加式写入,永远不会覆盖已有内容
  • 使用 set -euo pipefail 确保错误不会静默失败

4.2 路由规则

当 Agent 获得新信息时,需要判断这条信息应该写到哪里:

新信息到来

  ├─ 是一个重大决策? → decisions/YYYY-MM-DD-slug.md (新建)

  ├─ 是一条可复用的经验? → lessons/TOPIC.md (追加)

  ├─ 是关于某个人的新信息? → people/NAME.md (追加)

  ├─ 以上都不是,但值得记录? → memory/YYYY-MM-DD.md (日志)

  └─ 无实质内容 → 不写 (NOOP)

TIP

经验法则:日志可以随便写(宁多勿少),但知识文件要谨慎写(先读再写)。日志是临时的便签本,知识文件是蒸馏后的精华。

4.3 CRUD 验证

写入知识文件(lessons/people/decisions/)时,必须遵循 "先读再写" 原则:

准备写入 lessons/cron-discipline.md

  ├─ Step 1: 读取目标文件当前内容

  ├─ Step 2: 比较新知识与已有内容
  │     │
  │     ├─ 已有内容完全覆盖了新知识 → NOOP (跳过)
  │     │
  │     ├─ 新知识是对已有内容的更新 → UPDATE
  │     │   旧版标记: > [Superseded 2026-02-26]
  │     │   追加新版本
  │     │
  │     ├─ 新知识与已有内容矛盾 → CONFLICT
  │     │   两版都保留
  │     │   加标记: > CONFLICT (2026-02-26): 与上方内容矛盾
  │     │
  │     └─ 全新的知识 → ADD (追加新段落)

  └─ Step 3: 更新 frontmatter 中的 last_verified 日期

为什么需要 CRUD 验证?

不做验证的后果是记忆幻觉 -- Agent 写入了错误的、重复的、或矛盾的信息,然后在未来的检索中把这些错误信息当作事实使用。学术界称之为 HaluMem(Memory Hallucination)。

CRUD 验证是防线:每次写入前强制阅读已有内容,把冲突暴露出来而不是埋进去。

实施时机:

  • 日间 heartbeat: 做轻量级去重(检查目标文件末尾有没有相同内容)
  • 夜间反思(23:45): 做深度 CRUD 验证(完整比对 + 分类 + 标记)

第五部分:检索机制

5.1 三级检索策略

查询到来

  ├─ L1: 扫 INDEX.md → 定位目标文件 (0 cost, 纯文件读取)
  │   适用:知道要找什么类别的信息

  ├─ L2: 直接读取目标文件 (0 cost, 纯文件读取)
  │   适用:已知具体文件路径

  └─ L3: QMD 语义搜索 (有延迟, 适合模糊查询)
      适用:不确定信息在哪个文件

TIP

优先走 L1/L2,L3 作为兜底。

5.2 QMD 混合检索

QMD 是一个本地混合搜索引擎,结合了两种检索方式。

配置示例:

json
{
  "memory": {
    "backend": "qmd",
    "qmd": {
      "searchMode": "query",
      "update": {
        "interval": "5m",
        "onBoot": true
      },
      "limits": {
        "timeoutMs": 15000
      }
    }
  }
}

QMD 每 5 分钟自动重新扫描文件系统,新增/修改/删除的文件会自动更新索引。

5.3 中文搜索的已知限制

WARNING

QMD 的 FTS5 使用 unicode61 tokenizer,不支持中文分词。

Workaround:

  1. query 模式(推荐): 跳过 FTS,走向量语义搜索,对中文有效但较慢
  2. 写入时加空格: 在中文文档中有意识地用空格分隔关键词
  3. 未来方向: 等 QMD 支持 trigram tokenizer 或 ICU 中文分词

5.4 INDEX.md 健康度标记

INDEX.md 不仅是导航表,还是知识库的健康度仪表盘。

第六部分:记忆生命周期

6.1 日间:实时写入

用户对话 ──→ 做了决策/完成任务/获得新信息


         memlog.sh 写入日志


         路由判断:是否需要写入知识库?

         ┌──────┴──────┐
         │  是         │  否
         ▼             ▼
    先读再写         完成
    (轻量级去重)

6.2 每晚 23:30:日志同步

日志同步(daily-log-sync)是当天的最后一道防线,确保没有信息遗漏。

6.3 每晚 23:45:夜间反思

夜间反思(nightly-reflection)是记忆系统最核心的整合环节,相当于人脑睡眠时的记忆整合(Consolidation)。

执行流程:

Step 1: 读取上下文
Step 2: 写反思 (memory/reflections/YYYY-MM-DD.md)
Step 3: 清理日志
Step 4: CRUD 回写知识库 ← 核心步骤
Step 5: 过时扫描

6.4 每周日 00:00:冷数据归档

每周一次的 GC(Garbage Collection)负责把冷数据归档到 .archive/

6.5 生命周期总览

时间轴(一天)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

07:00  Agent 启动 → 读 NOW.md → INDEX.md → 今日日志
23:30  日志同步 — 全天 session 补漏
23:45  夜间反思 — 提炼 + CRUD + 过时扫描
00:00  (周日) GC — 冷数据归档

第七部分:遗忘与衰减

7.1 为什么需要主动遗忘

一个只增不减的记忆系统最终会被自己淹没。人脑的艾宾浩斯遗忘曲线告诉我们:遗忘不是 bug,是 feature。

7.2 温度模型

Temperature(file) = w_age x age_score + w_ref x ref_score + w_pri x priority_score

age_score     = exp(-0.03 x days_since_creation)    # 半衰期约 23 天
ref_score     = min(recent_references / 3, 1.0)      # 近 7 天被引用次数
priority_score = { P0: 1.0, P1: 0.5, P2: 0.0 }

权重: w_age=0.5, w_ref=0.3, w_pri=0.2

第八部分:可靠性保障

8.1 记忆幻觉问题

Agent 的记忆系统面临四种"幻觉"风险(来源:HaluMem 研究)。

8.2 写入时验证:CRUD 四分类

详见第四部分 4.3 节。

8.3 过时检测

通过 last_verified 日期和温度模型自动标记过时内容。

8.4 冲突处理流程

WARNING

设计原则:宁可暴露冲突,不可静默覆盖。

8.5 INDEX.md 作为健康度仪表盘

INDEX.md 不只是目录 -- 它是整个知识库的健康状态一览表。

第九部分:多 Agent 协作中的记忆

9.1 每个 Agent 独立 memory/ 目录

每个 agent 拥有自己的 workspace 和 memory 目录,互不干扰。

9.2 主 Agent 如何聚合子 Agent 产出

主 Agent 可以读取子 Agent 的 memory 文件(需要绝对路径),聚合跨 agent 的信息。

9.3 跨 Session 信息同步原则

TIP

解决方案:文件是唯一的跨 session 通道。

第十部分:快速上手

10.1 最小可用配置

最少需要 3 个文件:NOW.mdAGENTS.mdmemory/YYYY-MM-DD.md

10.2 渐进式搭建路径

阶段时间目标
阶段 0立刻基本的跨 session 记忆
阶段 1第 1 周结构化知识积累
阶段 2第 2 周知识质量保障
阶段 3第 3 周检索效率 + 主动遗忘
阶段 4第 4 周+完整的生产级记忆系统

TIP

建议:不要一次性搭全。从阶段 0 开始,每周加一层。

10.3 Obsidian 集成

我们的记忆系统天然兼容 Obsidian,因为它就是一堆 Markdown 文件。直接用 Obsidian 打开 workspace 目录即可浏览和编辑所有记忆文件。

附录:设计灵感

这套系统从一个只有 3 个文件的最小配置起步,经过 30 天的实际运行迭代到了当前状态。它不是一次性设计出来的,而是在解决真实问题的过程中逐步演进的。

如果你也在给自己的 Agent 搭记忆系统,从阶段 0 开始。遇到问题时再加层。

基于 MIT 许可发布 | OpenClaw 入门到精通 | 内容自动更新