Skip to content

记忆文件

源码版本v2026.6.11

职责

「记忆文件」(memory files) 是 OpenClaw agent 长期持久化的 user 偏好、行为指引、项目上下文。它和 session transcript 不一样:session 是一段对话的逐条消息,记忆 (memory) 是跨 session 复用的「这条规则永远生效」式笔记。OpenClaw 默认认 workspace 根目录下的 MEMORY.md 作为根记忆文件 (root memory file),memory/ 子目录下的所有 .md 作为辅助记忆,agent 启动时把它们读进 system prompt,之后整段对话都按这些规则走。

记忆系统同时支持 multimodal:memory/ 下不只有 .md,还可以放图片、音频这类被 MemoryMultimodalSettings 允许的扩展名文件,由 memory-host-sdk 做分类和索引。

设计动机

为什么不直接把所有规则写进 system prompt?三个理由:

  1. 跨 session 复用:system prompt 每次模型调用都消耗 token,记忆文件只在 agent 启动时加载一次,之后整个 session 复用;长期偏好写进文件比写进 prompt 划算。
  2. 用户可编辑:记忆文件就是普通 markdown,用户用任何编辑器改完下次启动就生效,不必改 config 或重启 gateway。openclaw doctor --fix 还能自动迁移 legacy 文件、合并分裂的 canonical/legacy 副本。
  3. plugin 可索引:memory-host-sdk 把记忆文件注册成 QMD collection,plugin 可以建向量索引、做语义检索——这样大段记忆不必整段塞 prompt,而是按 query 动态召回相关片段。

代价是「根记忆」文件名有 legacy 兼容历史,大小写敏感,且 symlink 会被忽略——这些细节都由 root-memory-files.ts 统一管理。

关键文件

数据流

listMemoryFiles(listMemoryFiles:150)是核心扫描入口:

typescript
export async function listMemoryFiles(
  workspaceDir: string,
  extraPaths?: string[],
  multimodal?: MemoryMultimodalSettings,
): Promise<string[]> {
  const result: string[] = [];
  const memoryDir = path.join(workspaceDir, "memory");

  const shouldSkipWorkspaceMemoryPath = (absPath: string): boolean =>
    shouldSkipRootMemoryAuxiliaryPath({ workspaceDir, absPath });

  const addMarkdownFile = async (absPath: string) => {
    try {
      const stat = await statRegularFile(absPath);
      if (stat.missing) {
        return;
      }
      if (!absPath.endsWith(".md")) {
        return;
      }
      result.push(absPath);
    } catch {}
  };

  const memoryFile = await resolveCanonicalRootMemoryFile(workspaceDir);
  if (memoryFile) {
    await addMarkdownFile(memoryFile);
  }
  try {
    const dirStat = await fs.lstat(memoryDir);
    if (!dirStat.isSymbolicLink() && dirStat.isDirectory()) {
      await collectMemoryFilesFromDir(memoryDir, result, multimodal, shouldSkipWorkspaceMemoryPath);
    }
  } catch {}

三层: canonical root 文件,resolveCanonicalRootMemoryFile 只返回真实文件(非 symlink),避免软链接把外部目录拖进来; memory/ 目录走 collectMemoryFilesFromDir 递归,descend 钩子拒绝 .openclaw-repair 目录,include 钩子用 isAllowedMemoryFilePath 过滤扩展名; extra paths 来自 config 声明,每个路径都过 shouldSkipRootMemoryAuxiliaryPath 防止把 legacy 文件重复算进来。

backend-config 把这些文件注册成 QMD collections(resolveDefaultCollections:409)给向量索引用:

typescript
function resolveDefaultCollections(
  include: boolean,
  workspaceDir: string,
  existing: Set<string>,
  agentId: string,
): ResolvedQmdCollection[] {
  if (!include) {
    return [];
  }
  const entries: Array<{ path: string; pattern: string; base: string }> = [
    { path: workspaceDir, pattern: CANONICAL_ROOT_MEMORY_FILENAME, base: "memory-root" },
    { path: path.join(workspaceDir, "memory"), pattern: "**/*.md", base: "memory-dir" },
  ];
  return entries.map((entry) => ({
    name: ensureUniqueName(scopeCollectionBase(entry.base, agentId), existing),
    path: entry.path,
    pattern: entry.pattern,
    kind: "memory",
  }));
}

memory-root 是精确文件名匹配,不递归;memory-dir 是 glob **/*.md,递归整个 memory/ 子树。scopeCollectionBase 给每个 collection name 加上 agent id 前缀,这样多 agent 共享一个 workspace 时各自的 collection 不会撞名。

doctor 扫描分裂文件的逻辑(detectRootMemoryFiles:97)用 exactWorkspaceEntryExists 同时检查 MEMORY.mdmemory.md:

typescript
export async function detectRootMemoryFiles(
  workspaceDir: string,
): Promise<RootMemoryFilesDetection> {
  const resolvedWorkspace = path.resolve(workspaceDir);
  const canonicalPath = resolveCanonicalRootMemoryPath(resolvedWorkspace);
  const legacyPath = resolveLegacyRootMemoryPath(resolvedWorkspace);
  const entries = await listWorkspaceEntries(resolvedWorkspace);
  const [canonical, legacy] = await Promise.all([
    entries.has(CANONICAL_ROOT_MEMORY_FILENAME)
      ? statIfExists(canonicalPath)
      : Promise.resolve<RootMemoryStatResult>({ exists: false }),
    entries.has(LEGACY_ROOT_MEMORY_FILENAME)
      ? statIfExists(legacyPath)
      : Promise.resolve<RootMemoryStatResult>({ exists: false }),
  ]);

注意 listWorkspaceEntriesreaddir 拿到精确目录条目(大小写敏感),macOS 默认文件系统大小写不敏感,但 OpenClaw 还是按大小写敏感处理:canonical 永远是大写 MEMORY.md,legacy 是小写 memory.md,两份同时存在时 canonical 优先,legacy 被 archive 到 .openclaw-repair/root-memory/<timestamp>/memory.md

整体读写链路:

边界与失败

  • symlink 被忽略:resolveCanonicalRootMemoryFileentry.isFile() && !entry.isSymbolicLink() 双重判断(src/memory/root-memory-files.ts:41-54),memory/ 目录也用 !dirStat.isSymbolicLink() 守门。这是安全边界,防止用户(或恶意 plugin)用软链接把 workspace 外的文件拖进记忆索引。
  • 大小写区分:文件系统大小写不敏感(macOS HFS+/APFS 默认)时,MEMORY.mdmemory.md 是同一个文件;但 OpenClaw 用 exactWorkspaceEntryExists 拿到 readdir 的精确条目名,按字面匹配。这意味着 macOS 上两份都写会撞文件,doctor 会把它们当 legacy-only 处理。
  • legacy 迁移不丢内容:moveLegacyRootMemoryFileToArchive 先 rename,失败(EXDEV 跨设备)退化为 copy + unlink(src/commands/doctor-workspace.ts:152-174)。archive 路径带时间戳,多次跑 doctor 不会互相覆盖。
  • extra paths 跨 workspace:config 声明的 extra paths 可以是绝对路径或 workspace 相对路径,normalizeExtraMemoryPaths(packages/memory-host-sdk/src/host/internal.ts:91-101)做 tilde 展开 + 去重,但不会把它们从 QMD collection 移出——plugin 索引时会按 absolute path 去重,避免同一文件被索引两次。
  • dreams.md 特殊路径:isMemoryPathdreams.md 也认作记忆文件(packages/memory-host-sdk/src/host/internal.ts:108-110),这个 legacy 约定保留是为了兼容老用户。canonical 永远是 MEMORY.md,dreams.md 只是辅助记忆。
  • multimodal 扩展名:isAllowedMemoryFilePath 默认只接受 .md,但 MemoryMultimodalSettings 启用后允许图片/音频扩展名走 classifyMemoryMultimodalPath 分类。这是 plugin 扩展点,core 不内置 multimodal 索引。
  • readMemoryFile 分页:读记忆文件支持 from / lines 参数(packages/memory-host-sdk/src/host/read-file.ts:67),defaultLinesmaxCharsresolveAgentContextLimits 按 agent 配置决定,防止单次读把超大记忆文件全塞进 prompt。
  • doctor 检测分裂:formatRootMemoryFilesWarning 在 canonical 和 legacy 同时存在时给出警告(src/commands/doctor-workspace.ts:128-140):「Dreaming 写 durable promotions 到 MEMORY.md,legacy 里旧事实会被 shadow」。这是 OpenClaw 内置的「dreaming」机制(自动 promote 记忆到 MEMORY.md)和 legacy 文件冲突的提醒。
  • plugin SDK 不直接导 core 内部:openclaw-runtime-memory.ts(packages/memory-host-sdk/src/host/openclaw-runtime-memory.ts)只 re-export 稳定 seam(resolveCanonicalRootMemoryFile / shouldSkipRootMemoryAuxiliaryPath / buildActiveMemoryPromptSection),plugin 通过 SDK 调用这些函数,不直接 import src/memory/——这样 core 重构时 plugin 不破。
  • workspace attestation:workspace 目录在首次使用时会被 attest(src/agents/workspace.ts:44 WORKSPACE_ATTESTATION_DIRNAME),attestation 里记录 inode/dev,防止用户在工作区中途换文件系统导致记忆文件路径漂移。

小结

记忆文件是 OpenClaw agent 跨 session 的持久化偏好层。MEMORY.md 是 canonical 根文件,memory/ 子目录是辅助记忆,extra paths 让用户跨 workspace 引用。memory-host-sdk 负责扫描、注册 QMD collection、分页读取,plugin 通过 SDK facade 接入;doctor 命令处理 legacy 文件迁移和分裂修复。memory 怎么被 context engine 在压缩时同步看 Context Engine;记忆加载进哪个 session 看会话生命周期 Agent 主循环

对照官方资料:Memory 文档 · README