l0-memory
Provides Git hook integrations to connect memory operations with Git workflows.
Can use Ollama's OpenAI-compatible embeddings endpoint to enable hybrid retrieval, blending FTS5 text search with vector similarity.
Can use OpenAI's embeddings API to enable hybrid retrieval, blending FTS5 text search with vector similarity.
l0-memory
Long-term memory for AI assistants, backed by a single Go binary that speaks the Model Context Protocol over stdio and exposes the same SQLite store via a CLI. Memories are partitioned by scope, can be pinned, linked into a typed graph, and tracked for freshness. A VSCode extension provides a sidebar UI, including a force-directed visualisation of the graph.
The store is local and plaintext. By default there is no network listener
and no embeddings — search is pure SQLite FTS5. Point LTM_EMBEDDING_URL
at an OpenAI-compatible endpoint (Ollama, LM Studio, vLLM, …) to opt into
hybrid retrieval, which blends FTS5 with vector similarity. The Go side has
zero CGO dependencies; the binary cross-compiles to every supported
platform.
Repository layout
server/ Go MCP server + CLI (the `ltm` binary)
extension/ VSCode extension (TreeView UI + bundled binaries)
extension-browser/ Web Clipper browser extension (Manifest V3)
integrations/ Host and shell integrations (Claude Code, Git hooks)Related MCP server: tartarus-mcp
Install
From a release
Download from the releases page:
ltm-<os>-<arch>.tar.gz(or.zipon Windows). Extract and placeltmon yourPATH.l0-memory-<version>.vsix. Install the extension withcode --install-extension l0-memory-<version>.vsix.
The macOS binaries in the release archives are ad-hoc codesigned. See SECURITY.md for the rationale (Sequoia/Tahoe provenance gate).
From source
make build # server/ltm
make install # build + install ltm to ~/.local/bin
make test # go vet + go test -race
make vsix # cross-compile all binaries + package the extensionmake install places ltm at ~/.local/bin/ltm; make sure that directory is on
your PATH (e.g. add export PATH="$HOME/.local/bin:$PATH" to your shell rc),
otherwise ltm won't be found in a fresh shell.
The default DB path is ~/.long-term-memory/memories.db. Override with
LTM_DB=/path/to.db.
Connecting an MCP host
ltm is a stdio MCP process. Every host that speaks MCP can use the same
SQLite store, so a memory saved from one tool is visible from another.
Claude Code
make install-mcp
# equivalent to: claude mcp add l0-memory $(pwd)/server/ltm mcpFor automatic recall, also install the optional integration — a
SessionStart hook that injects your persona (pinned user scope) and the
current project's memory (repo:<slug>, pinned first), plus a /checkpoint
skill to save state at the end of a session:
make install-claude # see integrations/claude-code/Claude Desktop
make install-mcp-desktop
# Edits the Claude Desktop config (macOS:
# ~/Library/Application Support/Claude/claude_desktop_config.json,
# Linux: ~/.config/Claude/claude_desktop_config.json), backs up the
# previous file, and points Claude Desktop at the local ltm binary.
# Quit and reopen Claude Desktop to pick up the change.Cursor / Cline / any other MCP host
Point the host at ltm with args ["mcp"]. Example config snippet:
{
"mcpServers": {
"l0-memory": {
"command": "/absolute/path/to/ltm",
"args": ["mcp"]
}
}
}MCP tools
Tool | Required args | Optional args | Behaviour |
|
|
| Insert or update by |
|
|
| Compact descriptor by default; pass |
|
|
| Search over key, value, tags. FTS5 by default; hybrid (FTS5 + vector) when an embedding endpoint is configured. Compact hits with snippet and score. |
| — |
| Most recently updated entries, pinned first, archived hidden. |
|
|
| Remove an entry. Cascades to incident links. |
|
|
| Slice a JSON-valued memory by JSON Pointer (RFC 6901) plus |
|
|
| Toggle the pinned flag. Pinning implies a verify. |
|
|
| Create a typed edge |
|
|
| Remove a single edge. |
|
|
| List every link incident to a memory, in either direction. |
|
|
| BFS subgraph view. Depth defaults to 1; direction |
|
|
| Atomic rename inside one scope. Cascades through incident links. |
|
|
| Set |
|
|
| Archive the old key, create the new, link new |
Pinned memories are also exposed as MCP resources at
memory:///<scope>/<key>, so an MCP host that supports
resources/list + resources/read can attach pinned context without
calling memory_get. The server emits
notifications/resources/list_changed whenever the pinned set changes
(pin, unpin, supersede, delete of a pinned row).
Search semantics
memory_search is backed by SQLite FTS5 with the unicode61 tokenizer.
Each whitespace-separated token becomes a prefix match, AND'd with the others.
caddy wafmatches entries containing bothcaddy*andwaf*, ranked by BM25.Tokens that contain non-alphanumeric characters are quoted as phrases, so queries like
100%or"hellodo not break the parser.Punctuation in the indexed data (
_,-,.,:,/, etc.) is a separator at index time, sorepo:caddy-wafis matched bycaddy,waf, orrepo.Results order: FTS5 rank, then
updated_at DESC.A query that fails to parse as FTS5 falls back to a case-insensitive
LIKEsubstring scan with%and_treated literally.
Hybrid retrieval
FTS5 only matches literal tokens and their prefixes, so a cross-lingual or
paraphrased query that shares no token with its target returns nothing —
idee per migliorare la memoria finds no match for a semantically perfect
English memory. Set LTM_EMBEDDING_URL (and LTM_EMBEDDING_MODEL) to an
OpenAI-compatible /v1/embeddings endpoint — Ollama, LM Studio, vLLM,
llmproxy, or OpenAI itself — to turn on hybrid retrieval:
On every
memory_save, the value is embedded and the vector stored in the same row. Best-effort: a failed embed never blocks the save, only the vector is skipped.memory_searchthen runs FTS5 and a flat cosine vector search and blends the two rankings with Reciprocal Rank Fusion (k=60). Pinned status is only a tie-breaker on equal RRF score, not an override.With the env unset (or
LTM_EMBED_DISABLE=1) the vector path is skipped entirely and search is pure FTS5 — no behavioural change from earlier versions.
Existing memories are not embedded retroactively. After enabling the
endpoint, run ltm reembed once to backfill; subsequent saves auto-embed.
Variable | Default | Description |
| (empty) | OpenAI-compatible |
| (empty) | Embedding model name passed to the endpoint. |
| (empty) |
|
|
| Per-request timeout (Go duration). |
Compact responses
memory_get returns {key, scope, tags, pinned, archived?, size_bytes, schema | preview, hint, verified_at?, staleness_days?, origin?, created_at, updated_at, compact: true} by default. The value field is
omitted. Pass expand:true to get the full record.
memory_search likewise returns SearchHit objects without the value, but
with a snippet (FTS5 snippet() with <<…>> markers) and a score
(-bm25(), larger is more relevant). In most cases the snippet shows what
the caller needed without a follow-up memory_get.
The CLI commands (ltm get, ltm search) always return the full record.
Scopes
A memory is identified by (scope, key). The same key can live
independently in different scopes — (user, focus) and
(repo:l0-memory, focus) are different rows. memory_search,
memory_list, and the resource list accept scope to restrict; omit it
to query every scope at once. memory_save, memory_get, memory_delete
default to user.
Conventional scope names:
user— cross-project notes.repo:<name>— repo-specific context.desktop,code, etc. — host-specific notes when the same store is shared across multiple MCP hosts.
Pinning and freshness
Three orthogonal signals can be attached to a memory:
Pinned (
memory_pin {pinned:true}) — surfaced first bymemory_listand exposed as an MCP resource. Pinning setsverified_at = now.Verified (
memory_verify) —verified_atis updated; compact views exposestaleness_days = (now - verified_at) / 1 day. The host can use this to skip or flag old memories.Archived — set by
memory_supersede. Archived rows are hidden frommemory_listandmemory_searchby default but stay queryable (memory_getstill returns them). The graph keeps incident links, so a successor can be reached from old references.
Knowledge graph
Edges are typed and directional. The triple (from, to, rel) is unique:
re-linking the same triple is a no-op. Edges respect a foreign-key cascade,
so deleting a memory drops every edge incident to it. Edges may cross
scope boundaries.
ltm save tech:caddy "Caddy server" tech
ltm save repo:caddy-waf "WAF plugin" repo
ltm link repo:caddy-waf depends_on tech:caddy
ltm traverse tech:caddy 2
# {root, depth, nodes:[…], edges:[…]}memory_traverse runs a BFS from the given node, deduplicates visits,
filters by rel if requested, and supports direction out, in, or
both.
CLI reference
ltm [--scope <name>] <command> [args...]
# LTM_SCOPE in the environment has the same effect as --scope.
ltm list [limit] # pinned-first, archived hidden
ltm pinned [limit] # only pinned entries
ltm get <key>
ltm search <query> [limit]
ltm query <key> [path] # JSON Pointer + '*' wildcard
ltm save <key> <value|-> [tags] # value of "-" reads from stdin
ltm delete <key>
ltm rename <old_key> <new_key> # cascades through links
ltm verify <key> # mark "still current"
ltm supersede <old> <new> <value|-> [tags] # archive old, create new, link supersedes
ltm pin <key>
ltm unpin <key>
ltm link <from_key> <rel> <to_key> # same-scope edge (cross-scope is via MCP)
ltm unlink <from_key> <rel> <to_key>
ltm links <key>
ltm traverse <key> [depth] # JSON: {root, depth, nodes, edges}
ltm reembed [--force] # backfill embeddings for hybrid retrieval
ltm path # prints the SQLite DB path
ltm version
ltm serve [port] # local HTTP REST server (default 8080); prints an auth token
ltm doctor # one-shot health check: binary, store, serve, hookVSCode extension
The sidebar has two panes:
Pinned — pinned memories. Pin/unpin context actions.
Memories — full list. The toolbar exposes Add, Search, Filter by scope, Toggle group by scope, Sort, Open knowledge graph, Refresh, Clear filter, Delete selected. The view title shows the active filters in the form
scope:user · q:"caddy" · sort:key · grouped.
Per-item context actions: Open in editor, Verify, Supersede with new key, Rename key, Edit, Link to, Show neighbors, Remove a link, Open graph from here, Pin/Unpin, Delete.
A status bar item on the right ($(database) l0: N, plus
$(pinned) K when pinned > 0) shows totals and clicks back to the
sidebar.
Knowledge graph viewer
The Open knowledge graph button (toolbar of the Memories pane, or
per-item context action) launches a side webview that renders the store
as a D3 force-directed graph. Nodes are coloured by scope class; pinned
nodes have a stronger outline; the root of a per-item graph has the
thickest outline. Click a node to open the memory; double-click to
re-root; drag to reposition; scroll to zoom. Depth (1–4) and direction
(out/in/both) selectors trigger a re-fetch via memory_traverse.
D3 is bundled locally (no CDN).
Settings
Setting | Default | Description |
|
| Absolute path to |
|
| Override SQLite DB path (sets |
|
|
|
|
| Group memories under collapsible scope nodes in the Memories tree. |
|
|
|
|
| Spawn the MCP server in the background on activation. Usually unnecessary because the host starts it. |
Binary auto-discovery
When l0-memory.binaryPath is empty, the extension searches in this order:
The bundled binary inside the extension at
bin/<goos>-<goarch>/ltm.The dev layout (
../server/ltmrelative to the extension folder, when running from this repository).Common install locations:
/usr/local/bin/ltm,/opt/homebrew/bin/ltm,~/.local/bin/ltm,~/go/bin/ltm.ltmresolved viaPATH.
If none of the above resolves to an executable, the sidebar surfaces an
error with two actions: open the binaryPath setting, or open the
output channel.
REST API & web clipper
ltm serve exposes the store over a local HTTP/JSON API on 127.0.0.1:8080
(GET /health, GET/POST/DELETE /memories) — the backend for the browser
web clipper in extension-browser/, and usable by any local script.
Every route except GET /health requires a bearer token (X-LTM-Token or
Authorization: Bearer). Binding to 127.0.0.1 does not stop a malicious
web page from calling the server, so the token is what actually gates it — the
old Access-Control-Allow-Origin: * let any site read/write the whole store.
The token is generated once, stored 0600 at <db-dir>/serve-token (override
with LTM_SERVE_TOKEN), and printed on startup; CORS is returned only for
chrome-extension:// / moz-extension:// origins.
Quick start: run ltm serve, load extension-browser/ as an unpacked extension
(chrome://extensions → Developer mode → Load unpacked), and paste the printed
token into the popup. Clips default to scope web. Full walkthrough and
troubleshooting in extension-browser/README.md;
run ltm doctor to check the whole setup at a glance.
Local Conflict Resolution (Auto-Supersede)
l0-memory includes automatic local conflict resolution on write:
When saving a new memory, the Go server automatically checks for existing active memories in the same scope with highly similar content.
It uses Jaccard similarity (token overlap) for pure offline setups, and Cosine similarity for vector-enabled setups.
If a conflict is detected (above 70% keyword overlap or 85% vector similarity), the old memory is automatically archived and linked as
--supersedes-->to the new one (equivalent to runningltm supersede).This behavior is enabled by default. To disable it, set
LTM_CONFLICT_DISABLE=1in your environment.
Git Hook Integration
Keep your AI assistant updated with your repository's recent commit history automatically and offline.
Run
integrations/git/install-hooks.shinside your repository.This installs a
post-commitscript under.git/hooks/.On every git commit, the hook automatically extracts the commit SHA, message, and saves it into the
repo:<name>scope.
Diagnostics
ltm doctor prints a one-shot health check — binary version, store path + entry
count, embeddings config/reachability, REST server liveness + token, and the
Claude Code recall hook — as a ✓/✗/⚠ checklist. Reach for it first when
something isn't wired up.
ltm also honours two diagnostic environment variables:
LTM_DEBUG=1— timestamped lines on stderr (boot, OpenStore success, every JSON-RPC line read, EOF, scanner errors).LTM_LOG_FILE=/path/to/file— same lines appended tofile(and impliesLTM_DEBUG). Useful when the host does not forward subprocess stderr to its own log.
Development
make build # server/ltm
make test # go vet + go test -race in server/
make vsix # full extension build, including cross-compiled binaries
make cleanCI runs the same checks on Linux/macOS/Windows × Go 1.22 and 1.23, plus
the extension compile on Node 20 and 22. The release workflow is
tag-driven (v*); it cross-compiles the five platform binaries with
ad-hoc codesign on the macOS targets, packages the extension with
embedded binaries, and uploads everything as a GitHub release.
See CONTRIBUTING.md for the contribution process and SECURITY.md for the threat model and the macOS provenance gate troubleshooting.
License
MIT — see LICENSE.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Persistent, portable memory for AI assistants — your private memory graph, from any MCP client.
Persistent memory for AI agents — log and recall conversation context over MCP.
Persistent memory for AI assistants. Save once; recall from Claude, ChatGPT, or any MCP client.
Token-efficient MCP memory for Markdown vaults. Tiered search, GraphRAG, AI memories.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenancePersistent memory MCP server for AI coding agents. Stores, searches, and retrieves context across sessions using SQLite and FTS5.
- AlicenseNot gradedqualityCmaintenanceA local-first MCP memory server providing persistent, searchable memory for AI agents, powered by SQLite.51Apache 2.0
- AlicenseAqualityCmaintenancePersistent memory MCP server for AI agents, using SQLite with hybrid keyword and semantic search for long-term memory storage.5Do What The F*ck You Want To Public
- FlicenseNot gradedqualityDmaintenanceA lightweight MCP memory server built on SQLite + FTS5, providing cross-session long-term memory for Claude Code.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/fabriziosalmi/l0-memory'
If you have feedback or need assistance with the MCP directory API, please join our Discord server