# How Memory Recall Works

This page explains how Anteroom retrieves durable memories at runtime and injects them into the agent's context — separate from RAG, with scope-based visibility and its own token budget.

## The Pipeline at a Glance

Every turn (both CLI and web UI) runs this sequence alongside RAG retrieval:

1. **Strip stale memory** from the previous turn's system prompt
2. **Embed your message** (reusing the RAG embedding service)
3. **Search `memory_artifact_embeddings`** via the `memories` usearch index, filtered to namespaces you can see
4. **Filter by status and distance** — only `active` memories under the similarity threshold qualify
5. **Trim to token budget** — memories are added until `max_tokens` would be exceeded
6. **Wrap each memory** in an `<untrusted-content>` envelope
7. **Append a `## Recalled Memories`** section to the system prompt

Step 1 guarantees stale memory from a prior turn never leaks into the next one. Steps 3–5 are independent from RAG — memory gets its own budget (default 800 tokens) and doesn't compete with the source/RAG budget (default 2000 tokens).

---

## What Counts as a Memory

Memories are `memory` artifacts (#919) stored in the `artifacts` table with typed metadata:

- `memory_scope`: `user`, `project`, or `local`
- `memory_category`: `preference`, `project_fact`, `decision`, or `workflow_hint`
- `memory_status`: `active`, `candidate`, `pending_review`, `rejected`, or `archived`
- `provenance`: conversation / message / workflow / source artifact references

Only `active` memories are eligible for recall. Candidates, rejected, and archived memories are stored but excluded — the promotion/review pipeline lives in #920.

---

## Scope-Based Visibility

Memory scope maps to artifact namespace, and recall filters by namespace before ranking:

| Scope | Namespace | Visible when… |
|---|---|---|
| `user` | `user` | Always |
| `project` | *project slug* | Current space's name matches the project slug |
| `local` | `local` | Current space is explicitly local |

A space that belongs to `project A` can never see memories scoped to `project B` — the recall query's `namespace IN (…)` clause enforces this at the database level, and `retrieve_memories()` defensively re-checks the namespace of each returned row before including it. Scope isolation is covered by an explicit regression test.

Missing or ambiguous space context collapses to **`user`-only** visibility — the safe default.

---

## Untrusted Content Framing

Every recalled memory is wrapped in an `<untrusted-content>` envelope with origin `memory:<fqn>`, using the same `context_trust.wrap_untrusted()` pipeline RAG uses. This is mandatory — not optional — because:

- Memory content can carry injection payloads from earlier untrusted turns (tool output, RAG, pasted text)
- Even user-authored memories are untrusted once persisted — the author's intent is not preserved across turns
- The two-level trust model (`trusted` vs `untrusted`) is sufficient; no new trust level was introduced

See [Prompt Injection Defense](../security/prompt-injection-defense.md) for the broader threat model.

---

## Budget and Degradation

| Scenario | Reason string | Behaviour |
|---|---|---|
| `memory_recall.enabled` is `False` | `Memory recall disabled` | Skip silently |
| Query under 10 characters | `Query too short for recall` | Skip |
| No usearch memory index | `no_vec_support` | Return empty; CLI shows "Memory recall not configured" |
| Embedding service missing | `Embedding service unavailable` | Return empty |
| Embedding call raises | `Embedding failed` | Return empty |
| No rows in `memory_artifact_embeddings` | `no_embeddings_yet` | Return empty |
| All candidates above threshold | `no_matches_within_threshold` | Return empty |
| Similarity search raises | `search_failed` | Return empty |

The runtime never raises from recall — every failure mode returns `([], "<reason>")` and the agent turn continues without memory.

---

## Ordering vs RAG

Memory is appended **after** the RAG section. The model sees retrieved source chunks first, then durable memories. This matches the attribution ordering in #923 and keeps behaviour predictable when both pipelines fire on the same turn.

---

## Configuration

See [Config Reference](config-reference.md#memory_recall) for every field, default, and environment-variable override.

Defaults:

- `enabled`: auto-detect (use if embeddings are available)
- `max_memories`: 5 per turn
- `max_tokens`: 800 (independent of RAG's 2000)
- `similarity_threshold`: 0.5 (cosine distance; lower = stricter)
- `show_status`: true (CLI renders a status line per turn)

---

## Related Issues

- #919 — typed memory artifacts (storage model)
- #920 — memory promotion and review flows
- #923 — runtime context attribution (surfaces recalled memories in UI)
- #625 — memory retention policy and eviction
