Appearance
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| 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).
CLI
bash
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
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).