Skip to content

記憶ファイル

源码版本v2026.6.11

責務

「記憶ファイル」(memory files) は OpenClaw agent が長期永続化するユーザ偏好、行動指針、プロジェクトコンテキストです。session transcript とは異なります:session は一段の会話のメッセージごとの記録,記憶 (memory) は session を跨いで再利用される「このルールは永遠に有効」というノートです。OpenClaw はデフォルトで workspace ルートディレクトリの MEMORY.md をルート記憶ファイル (root memory file),memory/ サブディレクトリ以下のすべての .md を補助記憶として扱い,agent 起動時にそれらを system prompt に読み込み,以降の会話全体でこれらのルールに従います。

記憶システムはマルチモーダルもサポートします:memory/ 以下には .md だけでなく,MemoryMultimodalSettings が許可した拡張子の画像やオーディオも置け,memory-host-sdk が分類と索引をします。

設計動機

なぜすべてのルールを直接 system prompt に書き込まないのか?3 つの理由:

  1. session 跨ぎ再利用:system prompt は毎回のモデル呼び出しで token を消費しますが,記憶ファイルは agent 起動時に一度だけ読み込まれ,以降 session 全体で再利用されます;長期偏好は prompt よりファイルに書く方がお得です。
  2. ユーザ編集可能:記憶ファイルは普通の markdown で,ユーザが任意のエディタで編集すれば次回起動時に反映され,config 変更や gateway 再起動は不要です。openclaw doctor --fix は legacy ファイルの自動移行、分裂した canonical/legacy コピーのマージも行います。
  3. プラグインが索引化可能:memory-host-sdk は記憶ファイルを QMD collection に登録し,プラグインはベクトル索引を構築し意味検索できます——こうして長い記憶を丸ごと 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 {}

3 層: 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 **/*.mdmemory/ サブツリー全体を再帰します。scopeCollectionBase が各 collection name に agent id プレフィックスを付け,複数 agent が同じ workspace を共有する際にそれぞれの collection が名前衝突しません。

doctor の分裂ファイル検出ロジック(detectRootMemoryFiles:97)は exactWorkspaceEntryExistsMEMORY.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 は .openclaw-repair/root-memory/<timestamp>/memory.md にアーカイブされます。

読み書きチェーン全体:

境界と失敗

  • symlink は無視:resolveCanonicalRootMemoryFileentry.isFile() && !entry.isSymbolicLink() で二重判断(src/memory/root-memory-files.ts:41-54),memory/ ディレクトリも !dirStat.isSymbolicLink() で守門します。これは安全境界で,ユーザ(や悪意あるプラグイン)がシンボリックリンクで workspace 外のファイルを記憶索引に引きずり込むのを防ぎます。
  • 大小文字区別:ファイルシステムが大小文字不敏感(macOS HFS+/APFS デフォルト)の場合,MEMORY.mdmemory.md は同じファイルです;しかし OpenClaw は exactWorkspaceEntryExistsreaddir の精密エントリ名を取り,字面マッチします。つまり macOS で両方書くとファイル衝突し,doctor は legacy-only として扱います。
  • legacy 移行は内容を失わない:moveLegacyRootMemoryFileToArchive はまず rename し,失敗(EXDEV クロスデバイス)時は copy + unlink に降格(src/commands/doctor-workspace.ts:152-174)。アーカイブパスにはタイムスタンプが付き,doctor を複数回走らせても相互上書きしません。
  • extra paths は workspace 跨ぎ可能:config で宣言した extra paths は絶対パスか workspace 相対パスで,normalizeExtraMemoryPaths(packages/memory-host-sdk/src/host/internal.ts:91-101)が tilde 展開 + 重複排除をしますが,QMD collection からは外しません——プラグイン索引時には absolute path で重複排除し,同じファイルが二度索引されるのを防ぎます。
  • dreams.md 特殊パス:isMemoryPathdreams.md も記憶ファイルと見なします(packages/memory-host-sdk/src/host/internal.ts:108-110),この legacy 約定は古いユーザの互換のために残されています。canonical は常に MEMORY.md で,dreams.md はあくまで補助記憶です。
  • マルチモーダル拡張子:isAllowedMemoryFilePath はデフォルトで .md だけを受け付けますが,MemoryMultimodalSettings 有効時は画像/オーディオ拡張子が classifyMemoryMultimodalPath で分類されます。これはプラグイン拡張点で,core はマルチモーダル索引を内蔵しません。
  • readMemoryFile ページング:記憶ファイル読込は from / lines パラメータをサポート(packages/memory-host-sdk/src/host/read-file.ts:67)し,defaultLinesmaxCharsresolveAgentContextLimits が agent 設定に基づき決定し,1 回の読込で巨大な記憶ファイルを全部 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 ファイルの衝突を伝えるものです。
  • プラグイン SDK は core 内部を直接 export しない:openclaw-runtime-memory.ts(packages/memory-host-sdk/src/host/openclaw-runtime-memory.ts)は安定 seam(resolveCanonicalRootMemoryFile / shouldSkipRootMemoryAuxiliaryPath / buildActiveMemoryPromptSection)だけを re-export し,プラグインは SDK を経てこれらの関数を呼び,src/memory/ を直接 import しません——これで core リファクタ時にプラグインが壊れません。
  • 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 登録、ページング読込を担い,プラグインは SDK facade で接入;doctor コマンドが legacy ファイル移行と分裂修復を処理します。memory が context engine で圧縮時にどう同期されるかは Context Engine;記憶がどの session に読み込まれるかは Agent 主ループ を参照。

公式資料:Memory 文档 · README