Skip to content

Memory Recall

Pull only the memories relevant to a query, ranked by a transparent relevance signal — without loading the whole MEMORY.md index. Recall is the query half of the self-maintaining memory layer (#327): where memory-audit grades the store for drift, recall retrieves from it on demand.

CLI: scripts/memory-audit/recall.js (library + memory-recall skill).

Why (and the deliberate scope bound)

BoB's file-based memory is loaded whole each session via the MEMORY.md index. That is the right default at today's store size, but it does not scale as the store grows — every session pays for every memory whether relevant or not. Recall gives a returning session (or a human) a way to ask for the memories that bear on the task at hand.

Explicitly out of scope (per the #327 capability): vector databases, knowledge graphs, embeddings, and RL-based (memify-style) optimization. At a store of tens of memories a deterministic keyword scan beats semantic retrieval on both accuracy and inspectability, and it carries no index to build or keep in sync. Revisit only if a single project's store grows into the hundreds. Recall is a ranked scan over the existing flat files — nothing heavier.

Relevance signal

A memory's score combines four documented inputs:

score = (keyword + type_prior + usage_boost) × recency_factor
InputHow it is computed
keywordPer query term, weighted by where it hits: name +5, description +3, body +1 per occurrence (capped at 3 per term, anti-spam). Name/description matching is substring, case-insensitive; hyphens/underscores in the slug are treated as spaces.
type_priorA small additive prior by memory type, reflecting how often a type is actionable in-session: feedback 1.0, user 0.75, project 0.5, reference 0.25, unknown 0. It only re-orders keyword-matched memories — it never surfaces an unmatched one.
usage_boostlog2(1 + timesRecalled) × 0.5. Recall frequency is recorded in a per-dir recall-stats.json sidecar, never in the memory file, so the audit's mtime-based freshness stays intact.
recency_factorclamp(1.5 − monthsSince / staleMonths, 0.5, 1.5). Fresh memories float up; stale ones sink but never vanish (floor 0.5). monthsSince uses the audit's lastTouched — the max of file mtime, metadata.updated, and the latest ## History supersede date — so an archive-on-supersede (#343) memory reads as fresh.

Relevance-only surfacing. A memory with zero keyword hits is not relevant and is excluded from results. The one exception is a browse: a bare --type filter with no query terms lists that type ranked by recency + usage alone.

The MEMORY.md index itself is never a result — recall returns individual facts, not the index. The recall-stats.json sidecar is not a memory file and is likewise never returned (and, like the audit reports, is user-private — it is not committed).

CLI

bash
node ~/.claude/scripts/memory-audit/recall.js <terms...> [options]
# `query` may lead the terms (optional): recall.js query commit git
FlagEffect
--type <t>Filter to a memory type (user / feedback / project / reference). Repeatable.
--limit <N>Max results (default 5).
--min-score <S>Minimum relevance score to include (default 0.5).
--stale-months <N>Recency reference span for the decay factor (default 6).
--memory-dir <dir>Query an explicit memory directory.
--project-root <dir>Resolve the memory dir for this project root (default: cwd).
--allSearch across every project's memory dir.
--jsonMachine-readable output.
--explainInclude the per-signal score breakdown.
--no-recordDo not record usage for surfaced memories (read-only query).
-h, --helpUsage.

Output

Human mode prints each hit as name, type, score, description, a match-anchored snippet, and the file path (--explain adds the signal breakdown). --json emits { terms, dirs, count, results[] }; each result carries name, type, description, path, score, recency_months, usage_count, and snippet (plus breakdown under --explain).

Relationship to memory-audit

Recall reuses the memory-audit store layer as the single source of truth for how a memory file is located, parsed, and dated (memoryDirForRoot, listMemoryFiles, parseFrontmatter, lastTouched, monthsBetween, allMemoryDirs). The two are complementary halves of #327:

  • memory-audit (#342/#343/#344) — grade the store: fresh / stale / dead-reference, archive-on-supersede, rule-vs-context lint.
  • memory-recall (#345) — retrieve from the store on demand.

Generalization to other stores

The engine is store-shaped, not BoB-shaped: it ranks a directory of frontmatter-tagged markdown files. Any store that exposes { name, description, type, body, last-touched } per entry can be recalled by pointing --memory-dir at it — no per-store code. A different backing store would only need a thin adapter producing those fields; the scoring, filtering, and usage-sidecar logic are unchanged.

Tests

tests/test-memory-recall.js (Makefile test-memory-recall) — 23 assertions covering keyword weighting, relevance-only surfacing, the type filter, the recency floor, the usage boost + sidecar isolation, MEMORY.md exclusion, and CLI parsing. Hermetic (sandbox tmp dir).