Memory Recall
Memory Recall
Section titled “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)
Section titled “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
Section titled “Relevance signal”A memory’s score combines four documented inputs:
score = (keyword + type_prior + usage_boost) × recency_factor| Input | How it is computed |
|---|---|
| keyword | Per 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_prior | A 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_boost | log2(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_factor | clamp(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).
node ~/.claude/scripts/memory-audit/recall.js <terms...> [options]# `query` may lead the terms (optional): recall.js query commit git| Flag | Effect |
|---|---|
--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). |
--all |
Search across every project’s memory dir. |
--json |
Machine-readable output. |
--explain |
Include the per-signal score breakdown. |
--no-record |
Do not record usage for surfaced memories (read-only query). |
-h, --help |
Usage. |
Output
Section titled “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
Section titled “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
Section titled “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/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).