Agent-first read-only verification console for lean-memory
Project description
lean-memory-console
A read-only verification console for lean-memory.
Agents write and search; the human verifies. Your agent (Claude Code via the observing MCP, or any HTTP client in Docker mode) is the only writer of memory content. You open the console read-only over stored memory — no adding, editing, or deleting facts — to see the engine's invisible signals for the first time: the ADD-only supersession spine, per-hit score decomposition, and provenance episodes.
The single write exception is the manual test-search box: it runs a real engine search and therefore bumps access stats (
touch()). That is observability of live-search behavior, not a memory mutation.
Two modes
Local (default — zero Docker, nothing runs when the agent doesn't)
Two transient localhost processes over one shared data root:
# 1. The observing MCP wrapper — your agent stores + searches through this.
uvx lean-memory-console mcp
# 2. The console — you open this to verify. Binds 127.0.0.1, prints a
# tokened URL, Ctrl-C to stop.
uvx lean-memory-console serve
serve accepts --root, --port, and --no-open (skip the browser launch).
On load the SPA captures the token from ?token=… — held in memory and sent as
the X-Console-Token header on every request — and strips it from the address
bar (history.replaceState). There is no cookie; a page refresh without the
token drops the session.
Via the Claude Code plugin (recommended), the MCP is wired automatically:
/plugin marketplace add Wuesteon/lean-memory
/plugin install lean-memory
/memory:ui # launch + open the console
/memory:status # resolved root, namespaces, connect snippets
/memory:server-up # start the Docker data plane
/memory:server-down # stop it (the data volume persists)
(From a local checkout you can register the marketplace with
/plugin marketplace add ./ at the repo root instead of the GitHub slug.)
Docker (single-tenant, long-running, container owns /data)
export LM_API_KEY=$(openssl rand -hex 24) # REQUIRED — the container refuses to boot without it
docker compose -f "$(lean-memory-console --print-compose-path)" up -d
Then connect an agent over streamable-HTTP MCP and open the UI:
claude mcp add --transport http lean-memory http://127.0.0.1:8377/mcp \
--header "Authorization: Bearer $LM_API_KEY"
# UI: http://127.0.0.1:8377/ (authenticate with the same LM_API_KEY)
Docker mode binds 0.0.0.0 so the published port is reachable; there is no
local-mode Host guard and no per-launch session token. The controls are the
bearer gate (LM_API_KEY) plus the MCP transport-security Host allowlist. If
you reach the container over a LAN hostname/IP (no reverse proxy), list the
host(s) in LM_MCP_ALLOWED_HOSTS (comma-separated; :* matches any port) or
the /mcp mount 421s on its Host check — a commented example sits in the
packaged docker-compose.yml. The compose service builds the
full image target; container env is LM_DATA_ROOT / LM_API_KEY (required) /
PORT / LM_CONSOLE_MODELS / LM_MCP_ALLOWED_HOSTS.
A plain REST mirror exists for non-MCP agents:
POST /v1/{namespace}/memories and POST /v1/{namespace}/search.
The data-root rule (read this — it is the #1 trap)
The console serves exactly one data root and never auto-merges roots.
Resolution order, both serve and mcp:
--root > $LM_DATA_ROOT > ~/.lean_memory
The ./lm_data trap. The core lean-memory engine's own default root is
./lm_data (a directory in your current working directory), not
~/.lean_memory. So if you ran the engine directly (or the core stdio MCP
server pointed at the default) your memories may be under ./lm_data while the
console defaults to ~/.lean_memory — and you would inspect an empty root.
/memory:statusprints the resolved root and warns when./lm_dataexists in the working directory but is not the served root.- Fix: point the console at the right root, e.g.
uvx lean-memory-console serve --root ./lm_data, or setexport LM_DATA_ROOT=$(pwd)/lm_data.
The console adds exactly one file to the data root: _events.db (search/add
traces). Everything else is the engine's own <namespace>.db files.
Live vs. replay: the t_ref rule
t_ref (epoch-ms) is the world/event time that becomes a fact's valid_at
and anchors the temporal supersession spine.
- Live agents omit it. The observing MCP fills
now, so facts are ordered by wall-clock ingest — correct for an agent capturing memories as they happen. - Replay / import supplies it. When backfilling historical conversations,
pass the original event time as
t_ref(epoch-ms) on every add. Omittingt_refon historical data silently collapses the spine's ordering — every backfilled fact getsnow, so supersession and point-in-time queries become meaningless.
Rule of thumb: if you are importing anything that did not happen right now,
set t_ref.
One namespace per project
Namespaces replace tenants: the engine stores one SQLite file per namespace, which is the isolation boundary. Use one namespace per project/session.
Cross-process writers on a single namespace (e.g. two Claude Code sessions
spawning the wrapper on the same data root) are supported but serialized by a
bounded retry-on-SQLITE_BUSY loop — not a lock manager. Pathological
contention degrades to retries and, eventually, a SQLITE_BUSY surfaced in the
event's payload.error. Keeping namespaces per-project avoids the contention
entirely.
Namespaces are created implicitly on first accepted memory_add. There is no
create-namespace step, and no delete-namespace surface (ADD-only discipline —
to remove a namespace, delete its .db file while nothing is running).
Namespaces whose sanitized name starts with _ are rejected (the
_events.db sidecar is reserved); an empty name is not rejected — it sanitizes
to default.
Image size (Docker full)
The default Docker image is the full target: it installs lean-memory[models]
(CPU torch + sentence-transformers + a cross-encoder reranker), so the image
is large (~1.46 GB) and the first run downloads model weights into the mounted
HF cache volume. The CPU torch build is pinned from
download.pytorch.org/whl/cpu so the resolve never pulls the multi-GB CUDA
wheels. This is deliberate — stub vectors would recreate the FakeEmbedder
first-impression failure the quality gate exists to fix. A slim target
(~365 MB, stub embedder, no models) exists for API/UI development
(docker build --target slim …) but is never the documented first-run path.
Offline by default
The console runs fully on deterministic stub backends with no network and no
model downloads (LM_CONSOLE_MODELS=stub, or auto when [models] is not
importable). When scores are stub-generated the UI shows a banner saying so.
License
Apache-2.0
Manual E2E verification (pre-merge)
Run this before merge. It is a manual gate, not automated — steps that need
a live interactive Claude Code session are marked [manual] and are driven by a
human, not by tooling. Capture the observed outcome next to each step.
Local mode (plugin + observing MCP):
-
[manual]/plugin marketplace add ./(from the repo root marketplace), then/plugin install lean-memory. Expected: plugin installs; thelean-memoryMCP server appears in the session's MCP list, and/memory:ui|status|server-up|server-downare offered as commands. -
[manual]In a real Claude Code session, ask the agent to store a couple of facts and then search them (through the observing MCPmemory_add/memory_search). Expected:memory_addreturns{fact_ids, superseded_count};memory_searchreturns hits whosefact_textmatches what was stored; rows land in<root>/_events.db. -
[manual]Run/memory:ui. Expected: browser openshttp://127.0.0.1:8377/?token=…; the address bar loses?tokenafter boot; no login screen (local mode). -
[manual]Verify all four pages render the resulting state: - Overview — the namespace card shows fact counts, top predicates, the 7-day adds/searches sparkline, supersession rate, and facts-per-add. - Memories — the fact table lists the stored facts; opening a fact drawer shows metadata, the supersession timeline (chain oldest→newest), and the provenance episode. - Episodes — the transcript shows the stored turns, each expanding to the facts extracted from it. - Activity & Traces — the polled feed shows theadd/searchevents; a search row expands to the per-hit score decomposition (0.6·relevance + 0.2·recency + 0.2·importance, dense/sparse ranks, RRF); the test-query box runs a live search and its row is labeledorigin: ui.
Docker mode (HTTP data plane):
-
[manual]export LM_API_KEY=$(openssl rand -hex 24)thendocker compose -f "$(lean-memory-console --print-compose-path)" up -d(or/memory:server-up). Expected: container starts; boot validation passes (data root writable,LM_API_KEYset, sqlite-vec loadable). -
[manual]claude mcp add --transport http lean-memory http://127.0.0.1:8377/mcp --header "Authorization: Bearer $LM_API_KEY". Expected: the MCP connection registers; a store+search from the agent succeeds and records events in the container's/data/_events.db. -
[manual]Openhttp://127.0.0.1:8377/. Expected: the login screen prompts for the key; enteringLM_API_KEYauthenticates; all four pages render the same as local mode, and the Overview shows the Docker connect snippet. -
[manual]/memory:server-down. Expected: container stops; thelm_datavolume persists (re-upshows the same memories).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lean_memory_console-0.2.1.tar.gz.
File metadata
- Download URL: lean_memory_console-0.2.1.tar.gz
- Upload date:
- Size: 93.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cc43ba9709c3dc6daff607c6723bb198885d4af9c27883f4e5ea9cb15fb3ddb1
|
|
| MD5 |
0b21358e552d3ac8a0eb10a6e15d47e6
|
|
| BLAKE2b-256 |
4def69008e96e841ac2e53ea37d9f2e771243a01a5f9a20e8c1955555d5896b2
|
Provenance
The following attestation bundles were made for lean_memory_console-0.2.1.tar.gz:
Publisher:
release.yml on Wuesteon/lean-memory
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lean_memory_console-0.2.1.tar.gz -
Subject digest:
cc43ba9709c3dc6daff607c6723bb198885d4af9c27883f4e5ea9cb15fb3ddb1 - Sigstore transparency entry: 2211329401
- Sigstore integration time:
-
Permalink:
Wuesteon/lean-memory@00442806a3b4a41de4c277a84bc44870a5b6660c -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/Wuesteon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@00442806a3b4a41de4c277a84bc44870a5b6660c -
Trigger Event:
push
-
Statement type:
File details
Details for the file lean_memory_console-0.2.1-py3-none-any.whl.
File metadata
- Download URL: lean_memory_console-0.2.1-py3-none-any.whl
- Upload date:
- Size: 36.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5b66309bc54e9412ace411e19a84f79cbf157692ffb3c8f0230aee5e07d2955e
|
|
| MD5 |
799a6801b93a246373b170beacc6eab2
|
|
| BLAKE2b-256 |
ba35e51c2c95d8b73b8390d63d02a4b4bc39d39b4b27e31909fa6d37e3893a2f
|
Provenance
The following attestation bundles were made for lean_memory_console-0.2.1-py3-none-any.whl:
Publisher:
release.yml on Wuesteon/lean-memory
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lean_memory_console-0.2.1-py3-none-any.whl -
Subject digest:
5b66309bc54e9412ace411e19a84f79cbf157692ffb3c8f0230aee5e07d2955e - Sigstore transparency entry: 2211329426
- Sigstore integration time:
-
Permalink:
Wuesteon/lean-memory@00442806a3b4a41de4c277a84bc44870a5b6660c -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/Wuesteon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@00442806a3b4a41de4c277a84bc44870a5b6660c -
Trigger Event:
push
-
Statement type: