Skip to main content

local-memory

Local-first long-term memory for AI agents. SQLite, offline, zero cloud.

Your agent's memory stays on your machine. SQLite. No cloud. No API keys. Works offline.

License: MIT Python 3.10+ CI

local-memory is a standalone MCP server (stdio by default, plain REST and MCP-over-HTTP optional) that gives any AI agent durable long-term memory: search over past sessions (keyword, semantic, exact), a catalog of sessions, full session reads, and a write channel for new memories. It works with Claude Code, opencode, Cursor, Cline, Codex, or any other MCP client — or with plain curl over REST.

The engine is small and boring on purpose: one SQLite file, the Python standard library, and numpy. The only other dependency is the mcp package, which powers the MCP transports; the REST transport is stdlib-only.


Why

AI agents are stateless by default. Every new session starts from zero: the decisions made yesterday, the bugs already chased away, the project's conventions, the exact error message you fixed last week — all of it is gone unless you copy-paste it back in by hand.

The usual fix is a cloud memory API. But that means your conversations — often your most sensitive work context — are stored, indexed, and processed on someone else's servers. For local work that is a bad trade: the "memory" you paid for is also the leak you did not want.

local-memory flips the trade. The memory is a plain SQLite database on your machine, readable with any SQL client, back-upable with any backup tool, and searchable with your own embeddings model (or without any model at all). The agent gets the memory interface it wants; you keep the data you already had.


How it works

                 +------------------------------------------------+
                 |              your machine (offline)            |
                 |                                                |
   MCP client     |   +----------------+          +-------------+  |
   (Claude Code,  |   |  local-memory  |  stdio   |   SQLite    |  |
    opencode,     +-->|  MCP server    |<-------->|  memory.db  |  |
    Cursor, ...)   |   |  or REST /    |          |  + FTS5     |  |
    via stdio,     |   |  MCP-HTTP     |          |  + vec tbl  |  |
    HTTP, MCP-HTTP |   +-------+-------+          +------+------+ |
                 |           |                              ^      |
                 |           | embeddings (optional,        |      |
                 |           | only if YOU configure it)    |      |
                 |           v                              |      |
                 |   +----------------+          +-------------+  |
                 |   | embeddings API |          | numpy matrix |  |
                 |   | (OpenAI-style) |          | cache (.npy) |  |
                 |   +----------------+          +-------------+  |
                 +-------------------------------------------------+

One database, three search engines, chosen automatically:

Mode When used How
exact query looks like code (a=b, fn(x)) literal LIKE, then FTS5
keyword default for memory_search FTS5 with prefix expansion, LIKE safe
semantic memory_search_semantic embeddings -> TF-IDF -> keyword

Every result carries a context window: the neighboring chunks around the hit, so the agent sees what came before and after without an extra round trip.

Transports: stdio (default), plain REST, or MCP streamable-http — serve --transport stdio|http|mcp-http. Vector search: numpy matmul over the .npy cache, or sqlite-vec inside the database (extra [vector]).


Tools

Tool Args Returns
memory_overview days=7 recent sessions: id, title, counts, times
memory_search query, limit=5 keyword hits + context, engine used
memory_search_semantic query, limit=5 semantic hits + context, engine used
memory_fetch session_id, position=0, limit=20 consecutive chunks of one session
memory_status — db statistics (counts, size, cache)
memory_ingest session_id, content, project, metadata write result (position, ok)

Result rows (a public contract — stable across versions):

{
  "session_id": "alpha",
  "position": 3,
  "content": "the postgres connection pool timed out under load",
  "score": 0.83,
  "context": [
    {"position": 2, "content": "..."},
    {"position": 3, "content": "the postgres connection pool timed out under load"},
    {"position": 4, "content": "..."}
  ]
}

Quickstart

1. Install

pip install local-memory

Want the optional extras?

pip install "local-memory[semantic]"   # + TF-IDF fallback (scikit-learn)
pip install "local-memory[vector]"     # + in-database vector search (sqlite-vec)

Both extras are optional; the core works with just numpy + mcp. Prefer a local checkout? Clone and pip install ".[semantic]" from the repository instead.

2. Initialize the database

local-memory init          # creates ~/.local/share/local-memory/memory.db
local-memory status        # {"exists": true, "sessions": 0, ...}

3. Write a memory

local-memory ingest --session-id demo --project myproject \
  --text "the postgres connection pool timed out under load, raised max size"

4. Search it

local-memory search "postgres connection pool" -k 5
local-memory search "pool timeout" --semantic

5. Connect an MCP client

MCP clients can run it with uvx — it fetches and caches the package for you:

uvx local-memory serve

Claude Code:

claude mcp add local-memory -- uvx local-memory serve

opencode (opencode.json):

{
  "mcp": {
    "local-memory": {
      "command": "uvx",
      "args": ["local-memory", "serve"]
    }
  }
}

Cursor / any generic MCP client (mcpServers snippet):

{
  "mcpServers": {
    "local-memory": {
      "command": "uvx",
      "args": ["local-memory", "serve"]
    }
  }
}

Already installed with pip in step 1? You can skip uvx and point command at the installed binary directly: "command": "local-memory", "args": ["serve"] (see examples/generic_mcp.json).

Ready-made snippets live in examples/ — or generate one for your client without copy-paste errors (tests guard the output against drift from examples/):

local-memory config --client claude      # or: opencode | cursor | generic
local-memory config --client claude --setup --dry-run   # print the registration command

Configuration

Everything is configured via environment variables. There is no config file — one process, one database, zero moving parts.

Variable Default Meaning
MEMORY_DB_PATH ~/.local/share/local-memory/memory.db path to the SQLite database
EMBED_API_URL (empty = embeddings disabled) OpenAI-compatible /v1/embeddings endpoint
EMBED_MODEL local-embedder model name sent to the embeddings API
EMBED_OFF (unset) 1 forces keyword/TF-IDF only
MEMORY_CTX_WINDOW 3 context window width (neighbors per side)
MEMORY_MAX_CHUNKS 1000000 cap on rows scanned per search
MEMORY_MATRIX_CAP 5000 max new chunks embedded per call
MEMORY_HTTP_HOST 127.0.0.1 bind address for serve --transport http
MEMORY_HTTP_PORT 8787 port for serve --transport http
MEMORY_HTTP_TOKEN (unset) bearer token for HTTP; required when binding off-loopback
MEMORY_VECTOR_BACKEND auto vector search backend: auto | npy | sqlitevec (extra [vector])

Ingesting memory

memory_ingest / local-memory ingest is the write channel. A client hook or plugin calls it once per message (or chunk) it wants the agent to remember. Positions auto-increment per session; the FTS index is updated by a database trigger, so ingested text is searchable immediately.

CLI:

local-memory ingest --session-id my-project --project my-project \
  --text "decided: use SQLite WAL mode for the memory db" \
  --metadata '{"source": "standup", "date": "2026-10-06"}'
local-memory ingest --session-id my-project --file notes.md

MCP tool:

{
  "session_id": "my-project",
  "content": "decided: use SQLite WAL mode for the memory db",
  "project": "my-project",
  "metadata": {"source": "standup"}
}

Example hook script (call from your agent's post-message hook):

#!/usr/bin/env bash
# remember.sh — feed one message into local-memory
local-memory ingest \
  --session-id "project-${1}" \
  --text "${2}" \
  --project "hooks" >/dev/null

Semantic search (optional)

memory_search_semantic tries, in order:

  1. Embeddings — any OpenAI-compatible /v1/embeddings endpoint (llama.cpp llama-server, Ollama, vLLM, a local transformer service...). Point EMBED_API_URL at it; vectors are cached on disk next to the database (embed_cache_*.npy) and reused, so repeated queries are fast.
  2. TF-IDF — character n-gram cosine similarity, offline, needs the optional scikit-learn extra.
  3. Keyword fallback — the exact/FTS5/LIKE engines, with "engine": "keyword-fallback" in the response.

Prebuilding the cache (only needed for the embeddings path):

export EMBED_API_URL=http://localhost:8080/v1/embeddings
export EMBED_MODEL=my-local-embedding-model
local-memory build            # embed all chunks once

If no embeddings endpoint is configured, semantic search silently degrades to TF-IDF/keyword. Nothing is sent over the network unless you configure a EMBED_API_URL.

Vector storage: .npy or sqlite-vec

The embedding matrix lives next to the database as embed_cache_<dbname>_<model>.{ids,mat}.npy (numpy matmul — the permanent fallback). The cache key includes the database filename, so multiple databases in the same directory never share vectors. With the optional [vector] extra, the same vectors can live inside the SQLite database via sqlite-vec:

pip install ".[vector]"                    # sqlite-vec extension
local-memory migrate-vectors               # copy the .npy cache into the vec table
local-memory migrate-vectors --remove-npy  # ...and drop the .npy files

Backend selection: MEMORY_VECTOR_BACKEND=auto (default — sqlite-vec when installed and populated, otherwise .npy), npy, or sqlitevec (force; fails fast if the extension is missing). New chunks are mirrored to the vec table automatically. Both backends stay 100% local files.


HTTP / REST mode

Besides stdio, serve can run over the network — two extra transports, zero new dependencies (stdlib ThreadingHTTPServer):

local-memory serve --transport http      # plain REST (curl-friendly), port 8787
local-memory serve --transport mcp-http  # MCP streamable-http, port 8000

REST endpoints (JSON, same shapes as the CLI/MCP tools):

Endpoint Description
GET /health liveness + version
GET /status database statistics
GET /overview recent sessions (?days=7)
POST /search {"query", "limit"?, "semantic"?}
POST /ingest {"session_id", "content", "project"?, "metadata"?}
POST /fetch {"session_id", "position"?, "limit"?}
GET /tools MCP tools manifest

Security: loopback binds (127.0.0.1, ::1, localhost) work without a token. Binding any other address requires a token (--token or MEMORY_HTTP_TOKEN) — otherwise the server refuses to start (fail fast). With a token set, every request must send Authorization: Bearer <token> or X-Token: <token> (constant-time compare). No TLS — put a reverse proxy or SSH tunnel in front for anything beyond loopback.

Example:

local-memory serve --transport http &
curl -s localhost:8787/health
curl -s -X POST localhost:8787/search \
  -H 'Content-Type: application/json' \
  -d '{"query": "postgres connection pool", "limit": 5}'

Privacy

  • The database, FTS index, and embedding caches are plain files under MEMORY_DB_PATH (default ~/.local/share/local-memory/). Back them up or delete them like any other file.
  • No telemetry, no update checks, no phone-home of any kind.
  • The only outgoing network access in the entire codebase is the embeddings call, and it only happens if you set EMBED_API_URL yourself.
  • The HTTP/REST transport is inbound-only (you start it, you bind it) and defaults to loopback; off-loopback binding is refused without a token.
  • memory_ingest stores content as-is, unencrypted, in SQLite. If your data is sensitive, protect the file with filesystem permissions.
  • Nothing is ever sent to the authors of this project.

Testing

The test suite runs fully offline against a synthetic in-memory-style fixture database (three fake sessions, ~30 chunks). No real conversation data is read, no network is touched.

python3 -m pytest tests/ -q

Contributing

See CONTRIBUTING.md. Short version: local-first is the product promise; keep dependencies light; keep fixtures synthetic; add tests.

Security

See SECURITY.md. Report vulnerabilities privately via the repository's Security tab — not in public issues.

License

MIT — do what you want, just keep the notice.

Attribution — if you use or copy this code, keep the author notice in the source headers, the repository link (https://github.com/Sergey-Vladimirovich-Hankok/local-memory), and the contact email (kokgfnu@gmail.com). MIT license covers permissions; attribution keeps the work traceable. See NOTICE.


Roadmap

  • PyPI release (for now the git repository is the only source).
  • Qdrant backend for the embeddings matrix (sqlite-vec already ships — extra [vector]).
  • Plugin packages for specific agents (Claude Code, opencode, Cursor) — local-memory config --client ... already generates the snippets.
  • Session summaries via any local LLM (opt-in, offline).
  • Done: REST mode (serve --transport http / mcp-http), sqlite-vec vector backend, per-client config generator.
  • Not on the roadmap: cloud sync, web UI, Rust rewrites. If it must be local, it stays local.

Disclaimer

local-memory is provided for educational and personal use. You are responsible for what you ingest into it and for protecting the resulting database file. The authors are not liable for lost, leaked, or misused data — the database is yours, and so is the responsibility.

Metadata

Release files for local-memory 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for local-memory 0.2.0
File Size Uploaded Provenance
local_memory-0.2.0.tar.gz 44.3 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for local-memory 0.2.0
File Interpreter ABI Platform
local_memory-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 78.9 kB

Release files / local_memory-0.2.0.tar.gz

Download URL local_memory-0.2.0.tar.gz
Size 44.3 kB
Tags Source
SHA-256 checksum
How to use checksums
bd7d11ed6ca58b362ec999b6aaae1a88afc0aae5d481244b4b739a4926c94c48
BLAKE2b-256 checksum
How to use checksums
48c90f8252c416958ef51092f54fe25d8f130aaf44710b1e6dd2116ab9bfc3bd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Pop!_OS","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / local_memory-0.2.0-py3-none-any.whl

Download URL local_memory-0.2.0-py3-none-any.whl
Size 34.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3c1b83e0eda28e9965d0ca7606b771842aca5098d8fad1cacba62869b06f5a3d
BLAKE2b-256 checksum
How to use checksums
12173b67b5f61b73a5aa69661382d7d496db3ce3f178e7549a4bb96b4d93949d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Pop!_OS","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for local-memory 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page