sessionmemory
Durable memory for coding agents, one folder of searchable pages per project.
An agent starts every session knowing nothing about the last one. Yesterday's session
worked around a trap, rejected the obvious approach for a reason, and found the one flag
that makes a library behave. All of it ends with the session, and the next one works it
out again from nothing. sessionmemory keeps what a project learned and hands it back
when the next session starts. Each project keeps its own memory, and no page is ever
shared between projects.
| Part | What it is |
|---|---|
| The vault | Your knowledge as markdown pages, one folder per project |
The sessionmemory plugin |
Claude Code hooks that feed a session at its start and record it at its end |
The sessionmemory CLI |
Searches pages by meaning and creates them. Everything else is a file you edit |
The pages follow the memoryfield format by Cal Paterson, described in
his article and defined in
the memoryfield spec. Each project's
learnings/ folder is one field in that format: a flat directory of markdown pages beside one
vector index file. You can export that folder, share it, or read it with any tool that
speaks the format.
Requirements
- uv. It installs the CLI, and the plugin runs its hooks through it.
- Python 3.13 or 3.14.
uvdownloads one when none is installed. - git. The vault is a git repository, and a project is registered by its git remote.
- Claude Code, to run the plugin. The CLI works on its own without it.
The first search downloads the nomic-embed-text-v1.5 embedding model, about 520MB, and
caches it under ~/.cache/sessionmemory/models. Nothing else touches the network.
Install
Installation is three steps: the CLI, the plugin, and a vault for them to share.
1. Install the CLI
The CLI is published on PyPI. Install it as a tool, which puts sessionmemory on your
PATH:
uv tool install sessionmemory
To upgrade it later, run uv tool upgrade sessionmemory.
2. Install the Claude Code plugin
In Claude Code, add this repository as a marketplace and install the plugin from it:
/plugin marketplace add natelandau/sessionmemory
/plugin install sessionmemory@sessionmemory
The hooks run the sessionmemory from step 1 when its version is at or past the
plugin's own. Keep the two in step: after /plugin update sessionmemory@sessionmemory,
run uv tool upgrade sessionmemory as well. A plugin newer than the tool falls back to
a copy of the CLI it carries, in a Python environment of its own that the first such
session builds.
3. Create a vault
The vault is its own directory. Make it a git repository of its own, so your pages and this code do not share a history. Then initialize it:
mkdir -p ~/repos/my-vault
cd ~/repos/my-vault
git init
sessionmemory init ~/repos/my-vault
sessionmemory init writes the three files a vault needs. A marker in _system/vault.toml
identifies the directory as a vault. A .gitignore keeps the derived index out of your
history. A README explains the layout to whoever opens the vault later. sessionmemory init
never overwrites a file, so you can run it again safely.
Then tell the CLI and the hooks where the vault is. Both read the same two places, in the
same order: the SESSIONMEMORY_VAULT environment variable, then vault.root in
~/.claude/sessionmemory.toml. Record the root in the file, since a session launched
from a GUI or an IDE does not read your shell profile:
[vault]
root = "~/repos/my-vault"
The variable wins when both are set, so exporting it in one shell points that shell at a different vault without touching the file. The plugin documentation lists every other key that file accepts.
Until a directory holds the marker that sessionmemory init writes, every command refuses
to touch it. The refusal protects you. If the root points at your home directory by
mistake, the first page written scatters a projects/ tree into it.
Nothing commits the vault on a timer. The plugin commits it when a session starts and again when a session ends, so a page reaches git within the session that wrote it. Pushing that history to a remote stays yours to do.
Note: To bring an existing directory of notes under the CLI, run
sessionmemory init --force ~/repos/my-vaultonce.--forcemeans only that the directory already has contents. Nothing existing is overwritten.
Register a project
A project gets memory when its repository is registered. With the plugin installed, the first session you open in a git repository registers it. The session begins with one line that names the slug:
This repository was registered with the vault as project 'invoice-api'.
The slug comes from the git remote, or from the directory name when the repository has no remote. There is nothing else to choose: no tags, no scope, no note type.
Only a git repository is registered for you. A slug is permanent once pages carry it. A session opened in your home directory or a scratch folder must not leave a project named after it in the vault. To register a directory outside git, or to choose the slug yourself, run the command once:
cd ~/repos/invoice-api
sessionmemory project --register --cwd .
✓ registered 'invoice-api'
└─ root: ~/repos/invoice-api
From then on, a session that starts in a registered repository receives that project's memory. A session that ends or compacts hands its transcript to a background pass, which records what was worth keeping.
Search and write pages
The CLI does two things. It finds pages by meaning, and it creates pages. Reading and editing a page is a job for your editor or your agent's own tools.
sessionmemory search "why does the same stripe event arrive twice" --limit 2 --cwd .
~/repos/my-vault/projects/invoice-api/learnings/stripe-retries-a-webhook-for-72-hours-so-the-handler-must-be-idempotent.md
Stripe retries a webhook for 72 hours, so the handler must be idempotent
Stripe redelivers an unacknowledged webhook for up to 72 hours, so the handler records the event id and ignores a repeat.
~/repos/my-vault/projects/invoice-api/learnings/the-nightly-reconciliation-job-must-start-after-the-02-00-bank-feed.md
The nightly reconciliation job must start after the 02:00 bank feed
The bank feed lands at 02:00 UTC; a reconciliation run before it reports every open invoice as unpaid.
A result is a path, a title, and a summary. A paraphrase finds the page, because search
ranks by meaning and not by words in common. A query that nothing answers returns no
results rather than the nearest pages. Pass --read to print every hit in full.
sessionmemory new learning \
--title "Stripe retries a webhook for 72 hours, so the handler must be idempotent" \
--summary "Stripe redelivers an unacknowledged webhook for up to 72 hours, so the handler records the event id and ignores a repeat." \
--cwd .
✓ created stripe-retries-a-webhook-for-72-hours-so-the-handler-must-be-idempotent.md
└─ ~/repos/my-vault/projects/invoice-api/learnings/stripe-retries-a-webhook-for-72-hours-so-the-handler-must-be-idempotent.md
The vault path is shortened to ~ here; the command prints absolute paths.
The command writes the frontmatter and prints the path. Write the body into that file,
or pass it with --body-file. The title is what every future session sees at its start,
and the summary is what a search result shows. Both state the fact and not the topic.
What a session sees
sessionmemory inject prints the block a session starts with. This is the block for a
project holding four learnings, one spec, one plan, and two open backlog items:
## Using this vault
Durable memory for this project lives in a vault of markdown pages. Nothing below is
loaded for you: the titles are what the vault holds, and each is one `sessionmemory search`
away. The project's folder has `learnings/` and `logs/`, searched by meaning, beside
`specs/` and `backlog.md`, which are ordinary files you Read and Edit.
`sessionmemory project --json` prints every path.
- Before assuming nothing was written down, search: `sessionmemory search "<words>"`
prints each hit's path, title, and summary, and `--read` prints every hit's whole
page in one call. A paraphrase still matches. No hits means nothing is recorded,
not that the query needs loosening.
- Past sessions, one page each: `sessionmemory search "<words>" --logs`.
- Open work: read `backlog.md`. An item is one line under a `## <kind>` heading
(feat, fix, refactor, perf, docs, test, build, ci), sized S, M, or L:
`- [S] <imperative description> - <YYYY-MM-DD> [#topic]`. Add one with
`sessionmemory new backlog --kind <kind> --size <S|M|L> --title "..." --topic <topic> --cwd .`,
which creates the file or heading when missing. Delete a finished line, and one
that will never be done, directly; never tick or annotate it. Git history is the
record of what was finished.
- Specs: `specs/` holds one design record per feature, named `<date>-<topic>.md`
and kept after the work ships. Before designing or changing a feature, list the
directory and read any spec whose name matches, so a decision already made is not
made again. `sessionmemory new spec --title "..." --cwd .` creates one and prints its
path. Edit it directly after that.
- Learnings are captured at session end, not by you mid-session. When the user asks
to keep one now: `sessionmemory new learning --title "..." --summary "..." --cwd .`
creates the page and prints the path to write prose into. Title and summary state
the fact, not the topic. Keep a page under 8KB; more detail is another page.
## What this project knows
- Invoice numbers come from a Postgres sequence, never from max(id) plus one
- pytest-asyncio needs asyncio_mode = auto or every async test is skipped
- Stripe retries a webhook for 72 hours, so the handler must be idempotent
- The nightly reconciliation job must start after the 02:00 bank feed
## Open work
2 open backlog items
1 spec in specs/
A page body never enters that block, so its cost grows with the number of pages and not
with their length. The titles say what exists. sessionmemory search returns what they say.
Documentation
| Page | What it covers |
|---|---|
| Concepts | Pages, fields, the index, and the layout of a vault |
| CLI reference | Every command, its options, and its output |
| The Claude Code plugin | The hooks, the sweep, every setting, and the slash commands |
| Vault health | What sessionmemory doctor reports, and what to do about it |
Development
git clone https://github.com/natelandau/sessionmemory
cd sessionmemory
uv sync # install dependencies
uv run duty lint # ruff, ty, typos, yamllint, shellcheck, prek
uv run duty test # pytest with coverage
CLAUDE.md records the conventions this project holds itself to.
License
MIT. See LICENSE.
Release files for sessionmemory 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sessionmemory-0.5.0.tar.gz | 45.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sessionmemory-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 105.7 kB
Release files / sessionmemory-0.5.0.tar.gz
| Download URL | sessionmemory-0.5.0.tar.gz |
|---|---|
| Size | 45.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
95a1117602f925add9a0dd32d8668da7631a79d85b84eb1ad5fbfdf0caf5427c
|
|
BLAKE2b-256 checksum How to use checksums |
e9513a04f6b0db93bbeb495e2f2615f029dc261a1eec50bc1ce3f0655c784f3c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / sessionmemory-0.5.0-py3-none-any.whl
| Download URL | sessionmemory-0.5.0-py3-none-any.whl |
|---|---|
| Size | 60.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c5e5d4af61509ea7f00e9e729ff3190c67cd0a99ac199df3f2c4bd1fe7238f54
|
|
BLAKE2b-256 checksum How to use checksums |
c24232721f020106251f00ce2e69f62ceb8e9f56eff8fcc170843a4306e14b55
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|