# Stop wasting tokens and re explaining your project between sessions

> Source: <https://github.com/raiyanyahya/recall>
> Published: 2026-06-21 21:05:37+00:00

Claude Code starts every session cold. Recall keeps a local log of your sessions and condenses it into a resume-ready summary —

entirely on your machine. No API key, no external model, nothing sent anywhere. It's built for people running Claude Code locally on a subscription: the only AI in the loop is Claude Code itself; the summarization is done by a classical Python summarizer.

**Free on your subscription.** It solves the cold-start problem — no more re-explaining the project each session — without a metered summarizer running up a bill. The summary is a local algorithm, not an LLM call, so persistent memory costs you nothing beyond the subscription you already pay for.**Saves your usage credits.** Two ways: (1) the summary is built locally, so capturing and updating your memory spends**zero** model tokens; and (2) resuming from a compact`context.md`

(~1–2K tokens) instead of re-explaining the project from scratch each session means far fewer tokens spent per session — stretching your subscription's usage limits (or, on the API, lowering billed credits).**Nothing leaves your machine.** Your transcripts (code, paths, sometimes secrets) are never sent to any API. Most "memory" tools pipe your context to a model endpoint; Recall makes a privacy guarantee they can't.**Zero-friction.** No`pip install`

, no local model to run, no key to configure, works offline. It starts working the moment the plugin loads.

Two files, written into your project under `.recall/`

:

—`history.md`

*the log.*Append-only. Every session is captured here as it happens (your prompts, Claude's replies, the files touched and commands run).—`context.md`

*the summary.*Overwritten by the local summarizer — the condensed "where are we right now" you load into the next session: goal, summary,**next steps / open threads**, files touched, and where you left off.

| Moment | What happens |
|---|---|
During the session |
The `Stop` / `SessionEnd` hooks append new activity to `.recall/history.md` . Capture is incremental (only new turns) and fully local. |
At session start |
The `SessionStart` hook surfaces `context.md` and has Claude ask you two things: resume from the saved context? and keep logging this session? |
Before you wrap up |
You run `/recall:save` . The local summarizer reads `history.md` and (over)writes `context.md` . |
…or automatically |
Set `auto_save_context: "on_end"` and `context.md` regenerates every time a session ends — no `/recall:save` needed. |

There is no LLM call anywhere — the summary is produced by **TF-IDF + TextRank**
(extractive summarization) running locally.

`scripts/summarizer.py`

ranks the most central sentences of your session:

- TF-IDF sentence vectors
- a cosine-similarity graph between sentences
**TextRank**— PageRank power iteration over that graph — to score sentences- the top
*N*are kept in original order

`context.md`

wraps that summary with deterministic facts pulled straight from the
transcript and git: the goal (your first ask), files touched, commands run, where
you left off, and `git diff --stat`

.

**No installs required.** The whole TF-IDF + TextRank implementation is vendored
in `summarizer.py`

. If `numpy`

happens to be importable it's used to vectorize the
math (faster on big sessions); if not, an identical pure-Python TextRank runs
instead. Same algorithm, same result — numpy is an optional accelerator, never a
requirement. The save output tells you which path ran.

`/recall:save`

— run the local summarizer → (over)write`context.md`

.`/recall:show`

— print`context.md`

.`/recall:log`

— tail`history.md`

.

Drop this in your project root to override defaults:

| Key | Default | Purpose |
|---|---|---|
`output_dir` |
`".recall"` |
Where `history.md` / `context.md` live. |
`capture_history` |
`true` |
Append session activity to `history.md` . |
`auto_save_context` |
`"off"` |
Regenerate `context.md` when a session ends: `"off"` or `"on_end"` . |
`summary_sentences` |
`8` |
How many sentences the summary keeps. |
`redact` |
`true` |
Strip obvious secrets before writing the md files. |
`include_git` |
`true` |
Add `git diff --stat` + recent commits to `context.md` . |
`max_input_chars` |
`200000` |
Cap on text fed to the summarizer (oldest dropped). |

**Pause logging** for a project without editing config: create
`.recall/.capture-paused`

. Delete it to resume.

Recall makes **no network calls, uses no API key, and loads no third-party
model.** The summarizer is local Python; the hooks are stdlib-only (numpy is an
optional accelerator). It reads your session transcript and writes only under
`output_dir`

. Concretely:

**No credentials, ever.** The plugin has zero references to API keys, auth,`ANTHROPIC_*`

, or HTTP. If`claude`

itself shows*"Invalid API key"*, that's the CLI's own auth — usually a stale`ANTHROPIC_API_KEY`

env var shadowing your subscription login.`unset ANTHROPIC_API_KEY`

(or run`env -u ANTHROPIC_API_KEY claude …`

). It has nothing to do with Recall.**Redaction.** A best-effort pass strips common secret shapes (API keys, tokens,`.env`

assignments, PEM keys) before writing, since`context.md`

/`history.md`

may be committed. Best-effort, not a guarantee — review before committing.**Hardened git.**`git diff`

/`log`

are run with`core.fsmonitor`

,`diff.external`

, hooks, and the pager disabled, so an untrusted cloned repo can't use its own git config to execute code when Recall reads ground-truth. Set`include_git: false`

to skip git entirely.**Confined writes.**`output_dir`

is forced to stay inside the project; a project-shipped config can't redirect writes to an absolute path or`../..`

.**Scoped transcript.** Recall only reads the transcript for the current project (matched by cwd); it never falls back to another project's sessions.**Trust boundary for shared memory.**`context.md`

is injected into the model at session start. If you**commit**, treat it like any other shared input: a teammate (or a bad actor with repo write access) could craft a`.recall/`

as shared team memory`context.md`

to attempt prompt-injection. SessionStart fences the content and labels it untrusted data, and Claude asks before relying on it — but if you don't fully trust who can write the repo, keep`.recall/`

git-ignored (the default).

Both are fine. Commit it for shared team memory, or git-ignore it for personal
memory (`.gitignore`

ships ignoring it by default — flip the comment to commit).

**From the marketplace** (this repo is its own marketplace):

```
/plugin marketplace add raiyanyahya/recall
/plugin install recall@recall
```

**Local dev** (no install step):

```
claude --plugin-dir /path/to/recall
```

No `pip install`

— the summarizer is vendored and stdlib-only (numpy used as an
optional accelerator if present). Work a session, run `/recall:save`

, and open
a fresh session — Recall greets you with where you left off.

```
python -m venv .venv && . .venv/bin/activate
pip install pytest ruff bandit numpy   # numpy optional

ruff check scripts tests               # lint
bandit -c pyproject.toml -r scripts    # security static analysis
pytest                                 # run the suite (also test without numpy)
claude plugin validate .               # official manifest validation
```

CI (`.github/workflows/`

) runs lint + Bandit, the test suite across Python
3.9–3.13 **with and without numpy** (both summarizer paths), CodeQL, secret
scanning, and manifest JSON validation on every push and PR. See
[CONTRIBUTING.md](/raiyanyahya/recall/blob/master/CONTRIBUTING.md) and [SECURITY.md](/raiyanyahya/recall/blob/master/SECURITY.md).

```
recall/
├── .claude-plugin/plugin.json   # manifest
├── hooks/hooks.json             # SessionStart (ask/resume) · Stop+SessionEnd (capture)
├── commands/                    # /recall:save · show · log
├── scripts/
│   ├── summarizer.py            # vendored TF-IDF + TextRank (numpy optional)
│   ├── make_context.py          # build/overwrite context.md
│   ├── capture.py               # append session activity to history.md
│   ├── session_start.py         # surface context + ask the start questions
│   ├── parse_transcript.py      # transcript → events + renderers
│   └── config.py · common.py · redact.py
├── tests/                       # pytest suite (summarizer, capture, security, …)
├── .github/                     # CI, CodeQL, secret scan, dependabot
├── recall.config.json        # config template / defaults
├── pyproject.toml               # ruff / pytest / bandit config (no runtime deps)
├── LICENSE · SECURITY.md · CONTRIBUTING.md
└── .gitignore
```

Bugs and ideas are welcome — open an [issue](https://github.com/raiyanyahya/recall/issues/new/choose)
(bug-report and feature templates provided) or a pull request. See
[CONTRIBUTING.md](/raiyanyahya/recall/blob/master/CONTRIBUTING.md) before submitting, and report security
vulnerabilities privately per [SECURITY.md](/raiyanyahya/recall/blob/master/SECURITY.md) rather than in a public issue.
