# Fusion Memory Plugin Contract

[← Docs index](./README.md)

This document is the source-of-truth contract for Fusion memory backend behavior after the FN-2087 migration series.

---

## Table of Contents

1. [Current Fusion Memory Baseline](#1-current-fusion-memory-baseline)
2. [OpenClaw Research Findings](#2-openclaw-research-findings)
3. [Fusion Memory Plugin Contract](#3-fusion-memory-plugin-contract)
4. [Migration Strategy + Compatibility Guardrails](#4-migration-strategy--compatibility-guardrails)
5. [Downstream Task Alignment](#5-downstream-task-alignment)

---

## 1. Current Fusion Memory Baseline

### 1.1 Two Memory Systems (Distinct Responsibilities)

Fusion currently has two related but distinct memory systems:

1. **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)
2. **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

### 1.2 Canonical Constants and Paths

| 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` |

### 1.3 Exported Surface (Post-Migration)

#### `project-memory.ts` exports

| 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 |

#### `memory-backend.ts` key exports

| 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 |

### 1.4 Settings Baseline

`ProjectSettings` fields that govern memory behavior:

- `memoryEnabled?: boolean` (default `true`)
- `memoryBackendType?: string` (default `"qmd"`)
- `insightExtractionEnabled?: boolean` (default `false`)
- `insightExtractionSchedule?: string` (default `"0 2 * * *"`)
- `insightExtractionMinIntervalMs?: number` (default `86400000`)

Runtime backend resolution uses an internal `MemorySettings` shape with `memoryBackendType` and optional additional keys.

### 1.5 Key Invariants

1. Canonical layered long-term memory file is **`.fusion/memory/MEMORY.md`**.
2. Runtime memory APIs only read/write canonical layered files under `.fusion/memory/`.
3. Default backend is **`qmd`** (`DEFAULT_MEMORY_BACKEND`).
4. Backend selection key is **`memoryBackendType`**.
5. Prompt instruction context is backend-aware (`file` path hint vs `qmd`/`readonly` behavior).
6. Dashboard `/api/memory` routes are backend-aware; layered file routes validate requests against allowed memory workspace files.

---

## 2. OpenClaw Research Findings

Fusion adopted OpenClaw-style layered memory while keeping a concrete TypeScript runtime contract.

### 2.1 What Was Adopted

- 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

### 2.2 Implications Table

| 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 |

---

## 3. Fusion Memory Plugin Contract

### 3.1 Runtime Interface (Canonical)

```ts
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.

### 3.2 Error Contract

```ts
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;
}
```

### 3.3 Built-In Backends

| 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 |

### 3.4 Registry Contract (Function-Based)

```ts
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.

### 3.5 Resolution Contract

```ts
export const MEMORY_BACKEND_SETTINGS_KEYS = {
  MEMORY_BACKEND_TYPE: "memoryBackendType",
} as const;

export const DEFAULT_MEMORY_BACKEND = "qmd";

export function resolveMemoryBackend(settings?: { memoryBackendType?: string }): MemoryBackend
```

Resolution chain:
1. Configured `memoryBackendType` (if registered)
2. `DEFAULT_MEMORY_BACKEND` (`"qmd"`)

### 3.6 Layered Memory Operations

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`

### 3.7 Prompt Instruction Context Contract

`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 |

### 3.8 File Compatibility & Source-of-Truth Semantics

#### 3.8.1 Canonical source-of-truth

- Canonical layered workspace: `.fusion/memory/`
- Canonical long-term file: `.fusion/memory/MEMORY.md`
- Runtime path validation rejects non-layered legacy requests

#### 3.8.2 QMD stale-index normalization scope

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.

#### 3.8.3 Dashboard and API path constraints

Dashboard memory routes must remain rooted in project-scoped memory APIs:

- `/api/memory` uses backend-aware read/write (`readMemory`, `writeMemory`)
- `/api/memory/file` uses validated layered file operations (`MEMORY.md`, `DREAMS.md`, daily files)
- Read-only backends must reject writes with `READ_ONLY`

#### 3.8.4 Prompt compatibility matrix

| 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 |

#### 3.8.5 Insight extraction relationship

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.

---

## 4. Migration Strategy + Compatibility Guardrails

### 4.1 Migration Phase Status

| 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 |

### 4.2 Must-Not-Break Invariants

1. Canonical long-term layered memory remains `.fusion/memory/MEMORY.md`.
2. Legacy top-level memory requests are not part of the runtime API contract.
3. Runtime backend selection remains keyed by `memoryBackendType`.
4. `DEFAULT_MEMORY_BACKEND` remains `"qmd"` unless explicitly changed in code + docs.
5. Prompt instruction behavior remains backend-dependent via `resolveMemoryInstructionContext()`.
6. Missing working-memory reads degrade gracefully to empty string where contract requires.

### 4.3 Resolution Fallback Chain

1. Use configured backend type if registered.
2. Otherwise fall back to `DEFAULT_MEMORY_BACKEND` (`"qmd"`).
3. For `qmd` search failures/unavailability, fall back to local layered file search.

### 4.4 Compatibility Matrix

| 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 |

### 4.5 Test Coverage Verification Checklist

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)
- `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

---

## 5. Downstream Task Alignment

This contract should remain aligned with:

- [Architecture](./architecture.md)
- [Contributing](./contributing.md)
- [Settings Reference](./settings-reference.md)
- [Docs README](./README.md)

If backend contract behavior changes in source, update these docs in the same change.

---

*Last updated: 2026-04-19*
