Claude Code starts every session cold. Recall keeps the memory.
π
recall
Built for Claude Code Β· MIT
A local log of your sessions, condensed into a resume-ready summary β entirely on your machine. No API key, no external model, nothing sent anywhere. Saves your usage credits.
claude β session start
β· Recall Β· resuming from .recall/context.md
goal ship the local TextRank summarizer
touched scripts/summarizer.py Β· tests/test_rank.py
ran pytest Β· ruff check scripts
left off numpy-free path passing, wiring git diff
βΊ Resume from saved context? yes
01 / cost
Free on your subscription
Persistent memory with no metered summarizer running up a bill. The summary is a local algorithm, not an LLM call.
02 / privacy
Nothing leaves your machine
Your transcripts β code, paths, sometimes secrets β are never sent to any API. A privacy guarantee other memory tools can't make.
03 / friction
Zero-friction, works offline
No pip install, no local model to run, no key to configure. It starts working the moment the plugin loads.
Two files under .recall/
The log, and the summary.
context.md β the summary
Overwritten on demand by the local summarizer β the condensed βwhere are we right nowβ you load into the next session.
TextRank deterministic
.recall/context.md
Where we are goal: ship local TextRank summarizer files touched scripts/summarizer.py scripts/make_context.py commands run pytest Β· ruff check left off numpy-free path green; wiring git diff --stat into context.md
How it works
Three moments, no LLM call anywhere.
1
During the session
The `Stop` / `SessionEnd` hooks append new activity to history.md. Incremental and fully local.
2
At session start
The `SessionStart` hook surfaces context.md and asks: resume from saved context, and keep logging?
3
Before you wrap up
You run `/recall:save`. The local summarizer reads history.md and overwrites context.md.
The summarizer
TF-IDF + TextRank. Running on your laptop.
No LLM call anywhere β the summary is produced by classical extractive summarization. The whole implementation is vendored and stdlib-only; numpy is an optional accelerator, never a requirement.
01 TF-IDF sentence vectors
02 Cosine-similarity graph between sentences
03 TextRank β PageRank power iteration to score
04 Top _N_ sentences kept in original order
def summarize(history):
vecs = tfidf(sentences(history))
graph = cosine_graph(vecs)
scores = textrank(graph) # PageRank
return top_n(scores, n=8)
β no network Β· no api key Β· 0 deps
`/recall:save`
Run the local summarizer β overwrite context.md.
`/recall:show`
Print the current context.md.
`/recall:log`
Tail history.md.
Tested in the open
Both summarizer paths, every Python.
Every push runs lint, Bandit static analysis, CodeQL, secret scanning, and manifest validation β and the full suite across Python 3.9β3.13, _with and without numpy_, so both the accelerated and pure-Python TextRank stay identical.
CI CodeQL Bandit secret scan
5
Python versions, 3.9 β 3.13
0
Runtime dependencies
2
Summarizer paths, identical output
100%
Python, stdlib-only
Configuration
Drop one file in your project root.
Every default is sensible, so `recall.config.json` is optional. Override only what you need.
pause logging without editing config
touch .recall/.capture-paused
Key Default Purpose
output_dir.recall Where the md files live
capture_history true Append activity to history.md
summary_sentences 8 Sentences the summary keeps
redact true Strip obvious secrets before writing
include_git true Add git diff --stat + commits
max_input_chars 200000 Cap on text fed to the summarizer
Privacy & security
No network calls. No API key. No third-party model.
The summarizer is local Python and the hooks are stdlib-only. Recall reads your session transcript and writes only under `output_dir` β concretely:
No credentials, ever
Zero references to API keys, auth, `ANTHROPIC_*`, or HTTP anywhere in the plugin.
Best-effort redaction
A pass strips common secret shapes β API keys, tokens, `.env` assignments, PEM keys β before writing.
Hardened git
git runs with fsmonitor, external diff, hooks and pager disabled, so an untrusted repo can't execute its own config.
Confined & scoped
Writes are forced to stay inside the project, and the transcript read is scoped to the current project by cwd β never another's.
trust boundary
If you commit `.recall/` as shared team memory, treat `context.md` like any untrusted input β it's injected at session start. SessionStart fences it and labels it untrusted, and Claude asks before relying on it. Don't trust who can write the repo? Keep `.recall/` git-ignored β the default.
How it compares
Most memory tools ship your context to a model. Recall doesn't.
Recall
Typical memory tools
Summary engine
Local TF-IDF + TextRank
Metered LLM call
Your transcripts
Never leave your machine
Piped to an API endpoint
Cost of memory
$0 beyond your subscription
Per-token, runs up a bill
API key required
None
Usually
Setup
No pip install, no model
Install + configure
Works offline
Yes
No β needs network
Work a session. Run save. Pick up where you left off.
This repo is its own marketplace. Two lines, no pip install.
$ /plugin marketplace add raiyanyahya/recall
$ /plugin install recall@recall
or local dev β claude --plugin-dir /path/to/recall