Memory-Dateien
Verantwortung
„Memory-Dateien" (memory files) sind die langfristig persistenten Nutzer-Präferenzen, Verhaltensrichtlinien und Projektkontexte eines OpenClaw-Agenten. Sie unterscheiden sich vom Session-Transkript: Eine Session ist Nachrichten einer Konversation; Memory sind sessonübergreifend wiederverwendbare Notizen der Form „diese Regel gilt für immer". OpenClaw erkennt standardmäßig MEMORY.md im Workspace-Wurzelverzeichnis als Root-Memory-Datei (root memory file) und alle .md unter dem memory/-Unterverzeichnis als Hilfs-Memory; beim Agent-Start liest es sie in den System-Prompt, danach gilt die ganze Konversation nach diesen Regeln.
Das Memory-System unterstützt auch Multimodal: Unter memory/ liegen nicht nur .md, sondern auch Dateien mit Bild-/Audioerweiterungen, die MemoryMultimodalSettings erlaubt; memory-host-sdk kümmert sich um Klassifikation und Indizierung.
Designmotivation
Warum nicht alle Regeln direkt in den System-Prompt schreiben? Drei Gründe:
- Sessonübergreifend wiederverwendbar: Der System-Prompt verbraucht bei jedem Modelcall Token; Memory-Dateien werden nur einmal beim Agent-Start geladen und danach in der ganzen Session wiederverwendet. Langfristige Präferenzen in einer Datei sind günstiger als im Prompt.
- Nutzer editierbar: Memory-Dateien sind normales Markdown; ändert der Nutzer sie in einem Editor, sind sie beim nächsten Start wirksam — kein config-change oder gateway-restart.
openclaw doctor --fixmigriert zudem legacy-Dateien und vereinigt gespaltene canonical/legacy-Kopien. - Plugin indizierbar:
memory-host-sdkregistriert Memory-Dateien als QMD-Collection; Plugins können Vektorindizes bauen und semantische Retrieval machen — so muss ein großer Memory-Block nicht als Ganzes in den Prompt, sondern wird per Query dynamisch relevanten Fragmente abgerufen.
Der Preis: Der Dateiname der Root-Memory hat eine legacy Kompatibilitätsgeschichte, ist case-sensitiv, und Symlinks werden ignoriert — diese Details verwaltet root-memory-files.ts zentral.
Schlüsseldateien
CANONICAL_ROOT_MEMORY_FILENAME:6-7— Konstante"MEMORY.md"; legacy ist kleingeschrieben"memory.md".resolveCanonicalRootMemoryPath:13-15— Setzt${workspaceDir}/MEMORY.mdzusammen.resolveLegacyRootMemoryPath:17-19— Setzt${workspaceDir}/memory.mdzusammen.resolveRootMemoryRepairDir:22-24— doctor-Reparaturverzeichnis${workspaceDir}/.openclaw-repair/root-memory/.resolveCanonicalRootMemoryFile:41-54— Scannt Workspace-Einträge und liefert den Pfad einer echten (nicht symlink)MEMORY.mdzurück oder null.shouldSkipRootMemoryAuxiliaryPath:56-72— Überspringt beim Scan von Hilfsdateien legacy-Dateien und das repair-Verzeichnis.normalizeExtraMemoryPaths:91-101— Tilde-Ausrollung + Entdopplung + absolute Pfade für in config deklarierte extra paths.isMemoryPath:103-112— Prüft, ob ein relativer Pfad zu Memory gehört: RootMEMORY.md,memory/-Unterverzeichnis oderdreams.md.listMemoryFiles:150-210— Listet alle Memory-Dateien: canonical root → memory/ rekursiv → extra paths.resolveDefaultCollections:409-418— Default-QMD-Collections:memory-root(exakter Match aufMEMORY.md) +memory-dir(memory/**/*.md).readMemoryFile:67-158— Liest eine Memory-Datei; unterstütztfrom/lines-Pagination undsuggestReadFallback-Hinweis.readAgentMemoryFile:161-182— Löst den Workspace nach agent id auf und delegiert anreadMemoryFile.openclaw-runtime-memory.ts— Stabile Fassade für Plugin-SDK, re-exportiertresolveCanonicalRootMemoryFileu. a. interne seams.CANONICAL_ROOT_MEMORY_FILENAME Kopie:168— SDK-Seite hält dieselbe Konstante, vermeidet zirkuläre Abhängigkeit zurück in core.system-prompt Führung:229— System-Prompt teilt dem Modell mit, dassMEMORY.md„persistente Nutzer-Präferenzen und Verhaltensrichtlinien, in der Session dauerhaft befolgt" sind.workspace Import:15-18— workspace-Modul importiert die KonstanteCANONICAL_ROOT_MEMORY_FILENAMEfür die Initialisierungsdetektion.detectRootMemoryFiles:97-121— doctor scannt canonical + legacy; gibt Existenz und Bytezahl zurück.formatRootMemoryFilesWarning:128-140— Bei Koexistenz beider Dateien Verschmelzungshinweis.RootMemoryMigrationResult:142-150— Migrationsergebnis-Typ:mergedLegacy/removedLegacy/archivedLegacyPath.
Datenfluss
listMemoryFiles (listMemoryFiles:150) ist der zentrale Scan-Eintritt:
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 {}Drei Schichten: canonical-root-Datei — resolveCanonicalRootMemoryFile liefert nur echte (nicht symlink) Dateien zurück und verhindert, dass Symlinks externe Verzeichnisse hereinschleppen; das memory/-Verzeichnis läuft rekursiv durch collectMemoryFilesFromDir, dessen descend-Hook .openclaw-repair ablehnt und dessen include-Hook isAllowedMemoryFilePath Extensions filtert; extra paths kommen aus der config, jeder Pfad durch shouldSkipRootMemoryAuxiliaryPath, damit legacy-Dateien nicht doppelt gezählt werden.
backend-config registriert diese Dateien als QMD-Collections (resolveDefaultCollections:409) für die Vektorindizierung:
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 ist exakter Dateiname-Match, nicht rekursiv; memory-dir ist glob **/*.md über den gesamten memory/-Subtree. scopeCollectionBase präfixt jeden collection-Namen mit der agent id, sodass mehrere Agenten in einem Workspace ihre Collections nicht kollidieren lassen.
doctor scannt gespaltene Dateien (detectRootMemoryFiles:97) mit exactWorkspaceEntryExists, das MEMORY.md und memory.md gleichzeitig prüft:
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 }),
]);Beachten Sie: listWorkspaceEntries nutzt readdir für exakte Verzeichniseinträge (case-sensitive). macOS ist default case-insensitiv, aber OpenClaw behandelt es als case-sensitive: canonical ist immer MEMORY.md, legacy memory.md; bei Koexistenz gewinnt canonical, legacy wird nach .openclaw-repair/root-memory/<timestamp>/memory.md archiviert.
Die gesamte Lese-/Schreib-Kette:
Grenzen und Fehler
- Symlinks ignoriert:
resolveCanonicalRootMemoryFileprüft doppeltentry.isFile() && !entry.isSymbolicLink()(src/memory/root-memory-files.ts:41-54); auch dasmemory/-Verzeichnis wird von!dirStat.isSymbolicLink()bewacht. Das ist eine Sicherheitsgrenze, die verhindert, dass Nutzer (oder bösartige Plugins) per Symlink Dateien außerhalb des Workspace in den Memory-Index ziehen. - Case-Unterscheidung: Bei case-insensitivem Dateisystem (macOS HFS+/APFS default) sind
MEMORY.mdundmemory.mddieselbe Datei; OpenClaw nutztexactWorkspaceEntryExists, um den exakten Eintragsnamen vonreaddirzu matchen. Das bedeutet: Werden auf macOS beide geschrieben, kollidieren sie; doctor behandelt sie als legacy-only. - Legacy-Migration verliert nichts:
moveLegacyRootMemoryFileToArchivebenennt zuerst (rename); schlägt das fehl (EXDEV cross-device), degradiert es auf copy + unlink (src/commands/doctor-workspace.ts:152-174). Der Archivpfad trägt einen Zeitstempel; mehrfaches doctor-Laufen überschreibt nicht gegenseitig. - extra paths workspaceübergreifend: In config deklarierte extra paths können absolute Pfade oder workspace-relative Pfade sein;
normalizeExtraMemoryPaths(packages/memory-host-sdk/src/host/internal.ts:91-101) rollt tilde aus und entdoppt, verschiebt sie aber nicht aus der QMD-Collection — das Plugin entdoppt beim Indizieren nach absolutem Pfad, damit dieselbe Datei nicht zweimal indiziert wird. - Sonderpfad
dreams.md:isMemoryPatherkenntdreams.mdebenfalls als Memory-Datei (packages/memory-host-sdk/src/host/internal.ts:108-110) — diese legacy-Konvention bleibt für Altnutzer. Canonical bleibtMEMORY.md;dreams.mdist nur Hilfs-Memory. - Multimodal-Erweiterungen:
isAllowedMemoryFilePathakzeptiert default nur.md; aktiviertMemoryMultimodalSettingsBilder-/Audio-Erweiterungen, die überclassifyMemoryMultimodalPathklassifiziert werden. Das ist eine Plugin-Erweiterungsstelle; core bringt keine Multimodal-Indizierung mit. - readMemoryFile seitenweise: Lesen von Memory unterstützt
from/lines-Parameter (packages/memory-host-sdk/src/host/read-file.ts:67);defaultLinesundmaxCharswerden vonresolveAgentContextLimitsaus der Agent-Konfiguration bestimmt, damit ein einzelner Lesevorgang nicht eine riesige Memory-Datei komplett in den Prompt drückt. - doctor detektiert Spaltung:
formatRootMemoryFilesWarningwarnt bei Koexistenz von canonical und legacy (src/commands/doctor-workspace.ts:128-140): „Dreaming schreibt dauerhafte Promotions nach MEMORY.md; alte Fakten im legacy werden überschattet". Das ist der Hinweis auf den Konflikt zwischen dem eingebauten Dreaming-Mechanismus (automatisches Promoten von Memory nach MEMORY.md) und der legacy-Datei. - Plugin-SDK importiert core-intern nicht direkt:
openclaw-runtime-memory.ts(packages/memory-host-sdk/src/host/openclaw-runtime-memory.ts) re-exportiert nur stabile seams (resolveCanonicalRootMemoryFile/shouldSkipRootMemoryAuxiliaryPath/buildActiveMemoryPromptSection); Plugins rufen über das SDK auf, ohnesrc/memory/direkt zu importieren — so bricht ein core-Refactor nicht die Plugins. - Workspace-Attestation: Das Workspace-Verzeichnis wird beim ersten Gebrauch attestiert (
src/agents/workspace.ts:44WORKSPACE_ATTESTATION_DIRNAME); die Attestation notiert inode/dev, damit ein späterer Dateisystemwechsel des Nutzers den Memory-Dateipfad nicht abdriftet.
Zusammenfassung
Memory-Dateien sind die sessonübergreifend persistente Präferenzschicht eines OpenClaw-Agenten. MEMORY.md ist die canonical Wurzeldatei, memory/-Unterverzeichnis das Hilfs-Memory, extra paths erlauben workspaceübergreifende Referenzen. memory-host-sdk übernimmt Scannen, QMD-Collection-Registrierung, seitenweises Lesen; Plugins setzen über die SDK-Fassade an; doctor behandelt legacy-Migration und Spaltungsreparatur. Wie memory von der context engine beim Komprimieren synchronisiert wird, siehe Context Engine; in welche Session das Memory geladen wird, siehe Sitzungslebenszyklus Agent-Hauptschleife.
Vergleich mit offiziellen Ressourcen: Memory-Doku · README.