SSD Nodes Learn 8GB RAM — $66/yr
Guides Matt ConnorBy Matt Connor

Recall: give Claude Code a memory on a VPS

Recall is a fully local plugin that logs each Claude Code session and condenses it into a resumable summary. Set it up on a VPS and measure the saving.

Verified Every command ran end-to-end on a fresh Ubuntu 24.04 server, July 30, 2026.

What Recall does for Claude Code memory

Recall is a Claude Code plugin that gives each project a memory across sessions. It writes two markdown files into a .recall/ folder inside your project: an append-only log of what happened, and a short summary of where you left off. Both are produced on the machine you are working on by a local Python summarizer, so the memory itself costs zero API tokens.

The gap it fills is small and constant. You close a session on your VPS on Tuesday. On Wednesday, Claude Code knows nothing about Tuesday. You re-explain the project by hand, or you let the model read half the repository again to work it out. Both cost tokens, and the second one costs a lot of them.

Recall version 0.4.0 is current as of July 2026, and the project is MIT licensed. It is a plugin. Nothing in it makes a network call.

What you need on the VPS

Recall's capture hooks are Python scripts that ship with the plugin. There are no third-party dependencies, so the only real requirement is an interpreter.

python3 -V

Ubuntu 24.04 answers Python 3.12.3. Recall supports Python 3.9 and newer. Minimal container images sometimes carry no interpreter at all, and then the shell answers python3: command not found. Install one before you go further.

sudo apt update && sudo apt install -y python3

NumPy is an optional accelerator for one step of the summarizer. You do not need it.

python3 -c "import numpy"

ModuleNotFoundError: No module named 'numpy' is an acceptable answer here. The summarizer has a pure-Python path, and the project's test suite checks that both paths select the same sentences.

Session memory matters more on a server than on a laptop, because server work arrives in short visits spread over days. If you already have Claude Code running in tmux on a VPS, Recall is the piece that carries yesterday's session into today's.

Install Recall from the plugin marketplace

Two commands, typed inside a Claude Code session:

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

The second command reads plugin@marketplace. Both names are recall here, which looks like a copy-paste mistake and is not one.

Check the install by running one of the plugin's own commands:

/recall:show

/recall:show prints the current summary. On a brand new project there is nothing to print yet, so what you are really checking is that the command exists at all. If Claude Code does not recognise /recall:show, the plugin is not loaded, and no hook will fire.

To run from a checkout instead, clone the repository and validate it first:

git clone https://github.com/raiyanyahya/recall ~/recall
cd ~/recall && claude plugin validate .

claude plugin validate . reads the manifest in .claude-plugin/ and reports whether the plugin is well formed. Then start Claude Code from your project directory with claude --plugin-dir ~/recall.

What the hooks write, and when

Recall registers three Claude Code hooks. Each one runs a Python script from the plugin directory.

  • SessionStart fires on startup, resume and clear. It surfaces context.md so the session opens with your summary in view.
  • Stop fires each time Claude finishes a response. It appends that turn to the log.
  • SessionEnd fires when the session closes, and can regenerate the summary.

Two files come out of this, both inside .recall/.

  • history.md is the append-only record: prompts, responses, files touched, and commands run.
  • context.md is the generated digest: the goal, a summary, next steps, files touched, commands run, and git context.

After one real session, look at the directory.

ls -la .recall/

You should see history.md with content in it. You may see no context.md at all, and that is the default behaviour rather than a fault. auto_save_context is off unless you set it, so the summary is written only when you ask for it:

/recall:save

That command runs the local summarizer over history.md and rewrites context.md. The algorithm is TF-IDF (term frequency, inverse document frequency) scoring feeding a TextRank sentence ranking. It is deterministic and extractive, which means it selects sentences that already exist in your log. No model is called, so the step is free and works with the machine offline.

Configure Recall for one project

Configuration lives in a recall.config.json file in the project root. These are the shipped defaults:

{
  "output_dir": ".recall",
  "capture_history": true,
  "summary_sentences": 8,
  "redact": true,
  "include_git": true,
  "max_input_chars": 200000
}
  • output_dir sets where the two files live. Keep it inside the project.
  • capture_history turns the history.md log on and off.
  • auto_save_context accepts off or on_end, and defaults to off.
  • summary_sentences is how many sentences survive into context.md. Raising it gives a longer summary and a slightly larger load at session start.
  • redact strips common secret patterns before anything is written to disk.
  • include_git adds the current diff and recent commits to the summary.
  • max_input_chars caps how much of history.md the summarizer reads in one pass.

For a project on a VPS the useful change is automatic saving, because a session on a server often ends when the terminal goes away rather than when you decide to stop.

{
  "auto_save_context": "on_end",
  "summary_sentences": 12
}

To stop capture for a while without touching the config, create the pause marker. Delete it to start capturing again.

touch .recall/.capture-paused

Do this before a session where you handle production credentials, because redaction is a filter and not a guarantee. The same reasoning drives keeping secrets out of AI agents in general: the safe secret is the one the agent never sees.

How much does Recall save on tokens?

It depends on what the alternative was. Loading a summary at session start is cheap. What it replaces can be expensive, because a model with no memory of your project rediscovers it by reading files.

ChartTypical cost of resuming work, per session
The data behind this chart
[
  {
    "label": "Recall context.md",
    "char_count": "4,800",
    "est_tokens": "1,200"
  },
  {
    "label": "Hand-written CLAUDE.md",
    "char_count": "3,200",
    "est_tokens": "800"
  },
  {
    "label": "Re-reading the repo",
    "char_count": "120,000",
    "est_tokens": "30,000"
  },
  {
    "label": "Full transcript replay",
    "char_count": "340,000",
    "est_tokens": "85,000"
  }
]

These are typical figures for a mid-size project, not a measurement of yours. A Recall summary loads at roughly 1,200 tokens, which lines up with the project's published claim of one to two thousand tokens for a resume. Replaying a full prior transcript reloads the whole conversation, in the region of 85,000 tokens. Letting the model rediscover the project by reading files sits between the two, near 30,000 tokens, and that number grows with the repository. The CLAUDE.md row is there for scale: it is cheaper because it is short and static, and it tells the model your standing rules rather than what happened last night.

Measure your own numbers. One token is roughly four characters of English prose, and a little less for code.

wc -c .recall/context.md .recall/history.md
echo $(( $(wc -c < .recall/context.md) / 4 ))

Inside a session, /context shows what is loaded into the context window right now, and /cost reports the session totals. Start one session cold, start the next with a summary in place, and compare. For the full picture of where a session's tokens actually go, how Claude Code spends tokens has the breakdown.

One caveat keeps this claim honest. The summary is loaded at every session start, so a summary you never act on is a small tax rather than a saving. Keep summary_sentences near the default unless your sessions run long.

Rebuild the summary without a session

If you cloned the repository, the summarizer has its own command line entry point. That is useful on a VPS when a session died with the terminal and you want the digest anyway.

python3 ~/recall/scripts/make_context.py --help

The help output lists the flags it accepts: --cwd for the project root, --transcript for an explicit transcript file, --quiet to suppress output, and --harness to pick between claude and opencode. Point it at a project:

python3 ~/recall/scripts/make_context.py --cwd /srv/projects/api

It reads the session transcript and history.md, then writes context.md under the directory you passed. If you installed through the marketplace, the plugin lives in a directory Claude Code manages, and /recall:save is the supported way to do the same job.

Why nothing is being written

No .recall/ directory after a full session. The hooks never ran. Type /recall:show to confirm the plugin is loaded, then run python3 -V. The hook command tries python3 first and python second, so a box with neither writes nothing and stays quiet about it.

history.md grows but context.md never changes. auto_save_context is off by default. Run /recall:save, or set the key to on_end and let the SessionEnd hook do it.

The files appear under the wrong project. Recall writes relative to the directory Claude Code was started in, so starting a session from your home directory puts the memory there. Start from the project root, and use ls -la .recall/ to find where the files actually landed.

Capture stopped and nothing warned you. Check for the pause marker with ls -a .recall/. A .capture-paused file you created last week is still doing its job.

The summary is thin after a long session. max_input_chars caps the summarizer input at 200000 characters, so a very long log is cut. Rotate it.

mv .recall/history.md .recall/history-2026-07-30.md

Run one short session afterwards and check ls -la .recall/ again to confirm a fresh history.md appeared.

Where Recall stops

Recall is a log plus a summarizer, and it is worth being clear about what that leaves out.

The summarizer is extractive. TextRank picks sentences that are already in history.md, so it never judges whether a decision was correct. A wrong turn recorded on Tuesday reads exactly like a good decision on Wednesday. When the stakes are real, read context.md and correct it by hand. It is a markdown file and nothing stops you editing it.

There is no search. You get one current summary and one growing log per project, not a queryable memory across projects. If the question is what you decided about the database three weeks ago, you are grepping history.md.

It does not help inside a session. A context window filling up mid-session is a different problem with different fixes, and managing the context window inside one session is the companion piece to this guide.

The summary is treated as untrusted input by design. context.md is injected fenced and labelled, and Claude asks before relying on it. That design exists because a committed .recall/ directory is a place where anyone with commit access can write text your agent will read. Decide once whether .recall/ is personal or shared: add it to .gitignore for personal memory, or commit it and review it like any other contribution. If the agent runs unattended, running Claude Code safely on a VPS covers the wider boundary.

Redaction is best effort. It targets common patterns such as API keys, tokens, PEM blocks and .env assignments. Read .recall/ before you commit it.

The version number is honest about maturity. At 0.4.0 in July 2026 the config keys and the file layout can still move between releases, so read the changelog before you upgrade a setup you depend on.

FAQ

Does Recall send my code or transcripts anywhere?

No. The capture hooks and the summarizer are Python scripts that run on your own machine, the plugin holds no API key, and it makes no network call. Summarising uses TF-IDF and TextRank rather than a model, so the step costs nothing and works with the machine offline. The trade-off is that the summary is extractive: it selects sentences from your log instead of writing new ones.

Why is my .recall/context.md missing or out of date?

auto_save_context defaults to off, so the summary is regenerated only when you run /recall:save. Set "auto_save_context": "on_end" in recall.config.json to have it rewritten when each session ends. If history.md is missing as well, the hooks are not running at all: confirm the plugin is loaded with /recall:show, then confirm python3 -V answers on that box, because the hooks are Python scripts.

How much does Recall save per session?

Loading a summary costs around 1,200 tokens against a typical 30,000 tokens for a model that has to read your repository again to work out where it is. Those are typical figures. Measure your own with wc -c .recall/context.md and the /context command inside a session, comparing a cold start against one that resumes from a summary.

Do I still need a CLAUDE.md file?

Yes, and the two do different jobs. CLAUDE.md is what you write on purpose: the standing rules and the build commands. context.md is generated from what actually happened last session, so it holds the half-finished migration you would never think to write down. Keep both.

Can one VPS hold memory for several projects?

Yes. Recall keeps memory in .recall/ inside each project directory, so two projects on the same server keep separate logs and separate summaries. Start Claude Code from the project root every time, because the files follow the working directory and not the user account.