p.enthalabs

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.

Read the docs β†’

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