This document is the source-of-truth contract for Fusion memory backend behavior after the FN-2087 migration series.
- Current Fusion Memory Baseline
- OpenClaw Research Findings
- Fusion Memory Plugin Contract
- Migration Strategy + Compatibility Guardrails
- Downstream Task Alignment
Fusion currently has two related but distinct memory systems:
- Layered memory backend system (
memory-backend.ts+project-memory.ts)- Handles agent-facing read/write/get/search for project memory
- Canonical layered workspace under
.fusion/memory/ - Uses pluggable backends (
file,qmd,readonly, custom)
- Insight extraction system (
memory-insights.ts)- Runs scheduled extraction and pruning workflows
- Reads working memory and writes extracted insights/audit artifacts
- Independent automation lifecycle and settings
| Constant | Value | Source Module |
|---|---|---|
MEMORY_WORKSPACE_PATH |
.fusion/memory |
memory-backend.ts |
MEMORY_LONG_TERM_FILENAME |
MEMORY.md |
memory-backend.ts |
MEMORY_DREAMS_FILENAME |
DREAMS.md |
memory-backend.ts |
DEFAULT_MEMORY_BACKEND |
qmd |
memory-backend.ts |
MEMORY_WORKING_PATH |
.fusion/memory/MEMORY.md |
memory-insights.ts |
MEMORY_INSIGHTS_PATH |
.fusion/memory/memory-insights.md |
memory-insights.ts |
MEMORY_AUDIT_PATH |
.fusion/memory/memory-audit.md |
memory-insights.ts |
| Export | Purpose |
|---|---|
getDefaultMemoryScaffold() |
Default long-term scaffold content |
ensureMemoryFile() |
Filesystem bootstrap for canonical memory |
ensureMemoryFileWithBackend() |
Backend-aware bootstrap |
readProjectMemoryWithBackend() |
Backend-aware read helper |
searchProjectMemory(), getProjectMemory() |
Backend-aware search/get wrappers |
resolveMemoryInstructionContext() |
Backend-aware instruction context |
| Planning instruction builder | Planning prompt memory instructions |
buildExecutionMemoryInstructions() |
Executor prompt memory instructions |
buildReviewerMemoryInstructions() |
Reviewer prompt memory instructions |
readProjectMemory() |
Direct canonical long-term file read |
| Export | Purpose |
|---|---|
MemoryBackend, MemoryBackendCapabilities |
Runtime backend contract |
MemoryBackendError, MemoryBackendErrorCode |
Typed backend error model |
FileMemoryBackend, QmdMemoryBackend, ReadOnlyMemoryBackend |
Built-in backend implementations |
registerMemoryBackend(), getMemoryBackend(), listMemoryBackendTypes() |
Function-based registry API |
resolveMemoryBackend() |
Backend resolution from settings |
memoryWorkspacePath(), memoryLongTermPath(), dailyMemoryPath(), memoryDreamsPath() |
Canonical layered path helpers |
ensureOpenClawMemoryFiles() |
Layered file bootstrap |
listProjectMemoryFiles(), readProjectMemoryFile(), writeProjectMemoryFile() |
Validated layered file operations |
ProjectSettings fields that govern memory behavior:
memoryEnabled?: boolean(defaulttrue)memoryBackendType?: string(default"qmd")insightExtractionEnabled?: boolean(defaultfalse)insightExtractionSchedule?: string(default"0 2 * * *")insightExtractionMinIntervalMs?: number(default86400000)
Runtime backend resolution uses an internal MemorySettings shape with memoryBackendType and optional additional keys.
- Canonical layered long-term memory file is
.fusion/memory/MEMORY.md. - Runtime memory APIs only read/write canonical layered files under
.fusion/memory/. - Default backend is
qmd(DEFAULT_MEMORY_BACKEND). - Backend selection key is
memoryBackendType. - Prompt instruction context is backend-aware (
filepath hint vsqmd/readonlybehavior). - Dashboard
/api/memoryroutes are backend-aware; layered file routes validate requests against allowed memory workspace files.
Fusion adopted OpenClaw-style layered memory while keeping a concrete TypeScript runtime contract.
- Layered workspace (
MEMORY.md, daily files,DREAMS.md) - Backend abstraction with explicit capability declarations
- Search over layered files with bounded snippets
- Migration safety via strict canonical path validation for layered files
| OpenClaw Concept | Fusion Current State | Contract Implication |
|---|---|---|
| Layered memory files | Implemented under .fusion/memory/ |
Canonical source-of-truth is workspace-based, not single-file |
| Path abstraction | Implemented via backend methods that receive rootDir (read(rootDir), write(rootDir, ...), etc.) |
Backends must resolve paths relative to project root; no global hardcoded absolute paths |
| Pluggable backends | Implemented (file, qmd, readonly, custom) |
Contract must document MemoryBackend exactly as shipped |
| Capability negotiation | Implemented via boolean-struct capabilities |
No enum or lifecycle-based capability API |
| Search | Implemented through optional search(rootDir, options) hooks |
Results are bounded snippets, not full-document dumps |
| Path migration completion | Legacy top-level path support removed from runtime APIs | Canonical source-of-truth is .fusion/memory/ only |
export interface MemoryBackendCapabilities {
readable: boolean;
writable: boolean;
supportsAtomicWrite: boolean;
hasConflictResolution: boolean;
persistent: boolean;
}
export interface MemoryBackend {
readonly type: string;
readonly name: string;
readonly capabilities: MemoryBackendCapabilities;
read(rootDir: string): Promise<MemoryReadResult>;
write(rootDir: string, content: string): Promise<MemoryWriteResult>;
get?(rootDir: string, options: MemoryGetOptions): Promise<MemoryGetResult>;
search?(rootDir: string, options: MemorySearchOptions): Promise<MemorySearchResult[]>;
exists?(rootDir: string): Promise<boolean>;
}
export interface MemoryBackendConfig {
type: string;
options?: Record<string, unknown>;
}This is the active contract. The following are not part of current runtime semantics: initialize(), hasCapability(), flush(), shutdown(), class-based registry APIs, or lifecycle interfaces.
export type MemoryBackendErrorCode =
| "NOT_FOUND"
| "READ_ONLY"
| "READ_FAILED"
| "WRITE_FAILED"
| "UNSUPPORTED"
| "CONFLICT"
| "QUOTA_EXCEEDED"
| "BACKEND_UNAVAILABLE";
export class MemoryBackendError extends Error {
readonly code: MemoryBackendErrorCode;
readonly backend: string;
}| Backend Class | Type | Current Behavior |
|---|---|---|
FileMemoryBackend |
file |
Reads/writes canonical .fusion/memory/MEMORY.md; supports exists/get/search; atomic writes via temp file rename |
QmdMemoryBackend |
qmd |
Delegates read/write to file backend; schedules qmd refresh; uses qmd search first, local layered search fallback |
ReadOnlyMemoryBackend |
readonly |
Read-only; write() throws MemoryBackendError("READ_ONLY", ...); search() returns empty |
registerMemoryBackend(backend: MemoryBackend): void
getMemoryBackend(type: string): MemoryBackend | undefined
listMemoryBackendTypes(): string[]Backends are stored in a module-level Map<string, MemoryBackend>. Built-ins are registered at module load.
export const MEMORY_BACKEND_SETTINGS_KEYS = {
MEMORY_BACKEND_TYPE: "memoryBackendType",
} as const;
export const DEFAULT_MEMORY_BACKEND = "qmd";
export function resolveMemoryBackend(settings?: { memoryBackendType?: string }): MemoryBackendResolution chain:
- Configured
memoryBackendType(if registered) DEFAULT_MEMORY_BACKEND("qmd")
The layered memory workspace helpers in memory-backend.ts provide:
- Workspace bootstrap:
ensureOpenClawMemoryFiles() - File listing:
listProjectMemoryFiles() - Validated file access:
readProjectMemoryFile(),writeProjectMemoryFile() - Path helpers:
memoryLongTermPath(),dailyMemoryPath(),memoryDreamsPath()
Allowed workspace files are constrained to:
.fusion/memory/MEMORY.md.fusion/memory/DREAMS.md.fusion/memory/YYYY-MM-DD.md
resolveMemoryInstructionContext(settings?) in project-memory.ts currently resolves as:
| Condition | backendType |
instructionPathHint |
Behavior |
|---|---|---|---|
memoryEnabled === false |
disabled |
null |
No memory instructions |
memoryBackendType === "file" |
file |
.fusion/memory/MEMORY.md |
Explicit path-aware read/write instructions |
memoryBackendType === "readonly" |
readonly |
null |
Read-only instruction set |
memoryBackendType === "qmd" or unknown |
qmd |
null |
Backend-aware generic instructions |
- Canonical layered workspace:
.fusion/memory/ - Canonical long-term file:
.fusion/memory/MEMORY.md - Runtime path validation rejects non-layered legacy requests
QMD search result normalization may remap stale indexed paths to canonical layered paths so search results remain consumable. This normalization does not re-enable legacy read/write APIs.
Dashboard memory routes must remain rooted in project-scoped memory APIs:
/api/memoryuses backend-aware read/write (readMemory,writeMemory)/api/memory/fileuses validated layered file operations (MEMORY.md,DREAMS.md, daily files)- Read-only backends must reject writes with
READ_ONLY
| Backend | Read behavior | Write behavior | Instruction style |
|---|---|---|---|
qmd (default) |
Delegated file read | Delegated file write + qmd refresh schedule | Generic backend-aware instructions, no fixed path hint |
file |
Direct canonical file read | Direct canonical file write | Explicit .fusion/memory/MEMORY.md path hint |
readonly |
Empty/non-throw read semantics | Throws READ_ONLY |
Read-only instructions only |
Insight extraction is a separate subsystem that currently uses:
- Working source:
.fusion/memory/MEMORY.md - Insight output:
.fusion/memory/memory-insights.md - Audit output:
.fusion/memory/memory-audit.md
It is related to, but not equivalent to, backend selection and prompt instruction logic.
| Phase | Task(s) | Status |
|---|---|---|
| Core backend contract + built-ins | FN-1418 | Complete |
| Engine backend-aware prompt integration | FN-1419 | Complete |
| Dashboard backend-aware memory integration | FN-1420 | Complete |
| Path/backend reconciliation umbrella | FN-2087 | Complete |
| Reconciliation implementation slices | FN-2131, FN-2132, FN-2133, FN-2134 | Complete |
- Canonical long-term layered memory remains
.fusion/memory/MEMORY.md. - Legacy top-level memory requests are not part of the runtime API contract.
- Runtime backend selection remains keyed by
memoryBackendType. DEFAULT_MEMORY_BACKENDremains"qmd"unless explicitly changed in code + docs.- Prompt instruction behavior remains backend-dependent via
resolveMemoryInstructionContext(). - Missing working-memory reads degrade gracefully to empty string where contract requires.
- Use configured backend type if registered.
- Otherwise fall back to
DEFAULT_MEMORY_BACKEND("qmd"). - For
qmdsearch failures/unavailability, fall back to local layered file search.
| Concern | Required Behavior |
|---|---|
| Settings persistence | Unknown memoryBackendType may be persisted, but runtime still falls back safely |
| Read-only backend | Writes fail with typed READ_ONLY error |
| Legacy upgrades | Runtime memory APIs ignore legacy top-level paths and operate on layered files only |
| Memory file APIs | Must enforce workspace-relative path validation and project root boundaries |
| Prompt generation | Must honor memoryEnabled toggle and backend-aware instruction context |
Contract-critical behavior is covered by:
packages/core/src/memory-backend.test.ts- built-in backends (
file,qmd,readonly) - registry helpers and
resolveMemoryBackend()default/fallback behavior - layered workspace helpers (
ensureOpenClawMemoryFiles, list/get/write, path validation)
- built-in backends (
packages/core/src/project-memory.test.ts- canonical long-term path bootstrap/read behavior
resolveMemoryInstructionContext()branching- planning/execution/reviewer instruction generation per backend
packages/core/src/store.test.ts- memory bootstrap behavior when memory is enabled/disabled and toggled
- Engine planning and executor tests
- memory instruction injection behavior by settings/backend
This contract should remain aligned with:
If backend contract behavior changes in source, update these docs in the same change.
Last updated: 2026-04-19