▄▄▄▄ ▗▞▀▚▖▄▄▄▄ ▄▄▄ ▄▄▄ ▄ ▄ ▄▄▄▄ ▄▄▄▄
█ █ █ ▐▛▀▀▘█ █ █ █ █ █ █ █ █ █ █
█ █ ▝▚▄▄▖█ █ ▀▄▄▄▀ █ ▀▀▀█ ▀▀▀█ ▀▀▀█
▄ █ ▄▄▄█ ▄▄▄█
▀▀▀
One binary. One local SQLite file. One memory, shared by every MCP client on your machine.
memory39 is a Rust CLI + MCP server backed by SQLite + FTS5. Every MCP-capable AI tool on your machine (Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw) reads and writes the same ~/.memory39/memory39.db, so a fact learned in one client is instantly recallable from any other. No cloud, no daemon, no sync.
Results are ranked by temporal-priority scoring: 0.4 x relevance + 0.3 x importance + 0.3 x recency (30-day half-life), so recent important matches surface first.
- Persistent across sessions: memories live in an on-disk SQLite file and survive restarts, CLI invocations, and MCP reconnects.
- One knowledge base across every MCP client: Claude Code, Claude Desktop, Codex, OpenCode, and OpenClaw all point at the same
~/.memory39/memory39.db. A fact stored from Claude is instantly recallable from Codex; a person stored via the CLI shows up in every MCP client. No syncing, no duplication. - Local and private: no cloud, no account, no telemetry. Your memory is a single SQLite file on your machine that you can inspect, back up, or move by copying.
- Single binary, zero daemon: CLI for scripting (
memory39 recall ...), MCP server on demand (memory39 mcp). Nothing runs in the background between calls. - Portable DB path: point at a different database by exporting
MEMORY39_DB=/path/to/other.db(supports~/expansion). Useful for project-scoped memory or isolated benchmarks. - Cross-type discovery: the
connectcommand links concepts across events, things, persons, and places in a single query, so relationships surface even when facts are stored as different memory types.
memory39 uses a bloom filter as a pre-check layer before FTS5 queries. On every recall, the bloom filter tests whether the query tokens exist anywhere in the database - if they definitely don't, the FTS5 query is skipped entirely, returning zero results in O(1) with no disk I/O.
| Layer | When it runs | Cost |
|---|---|---|
| Bloom filter | Every recall query |
~nanoseconds, in-memory |
| FTS5 search | Only if bloom says "maybe" | Full-text index scan |
How it works:
- Unigrams + bigrams - every memory field is tokenized into individual words and adjacent-word pairs, all stored in the bloom filter. A query for
"alice berlin"checks both tokens and thealice+berlinbigram. - Unicode-normalized - tokens are lowercased with diacritics removed (matching FTS5's
unicode61 remove_diacritics 2), socaféandcafehit the same entry. - Prefix-safe - long tokens (>6 chars) that FTS5 would prefix-expand are never skipped, avoiding false negatives.
- Persisted - the bloom filter is saved to
<db>.bloomalongside the database and loaded on startup. Rebuilt automatically after bulk writes. - Zero false negatives - if a token exists in any memory, the bloom filter always says "maybe". It can only produce false positives (saying "maybe" when nothing matches), which just fall through to FTS5 as usual.
Configured for 600K items at 0.001% false positive rate.
Numbers from a personal DB (~300 memories, 144 KB SQLite file):
| Query | Path | Avg per call | Throughput |
|---|---|---|---|
| Unknown fact | Bloom says "no match possible"; no disk I/O | ~120 ns | ~8.5M ops/sec |
| Known fact | Bloom says "maybe" -> FTS5 search runs | ~245 us | ~4K ops/sec |
Negative queries are ~2000x faster than positive ones. "Nanoseconds, in-memory" is literal: a bloom check is a handful of hash probes into a bitmap sitting in L1/L2 cache.
cargo install memory39Installs the binary and auto-configures it for every MCP client detected: Claude Code, Claude Desktop, Codex, OpenCode, OpenClaw.
curl -fsSL https://raw.githubusercontent.com/alejandroqh/marketplace/main/h39.sh | shSingle binary: CLI by default, MCP server with memory39 mcp.
| Prefix | Type | What it stores |
|---|---|---|
E# |
Event (dated) | Something that happened/will happen, with date+time |
U# |
Event (undated) | Same, without a specific date |
T# |
Thing | Object, concept, or fact |
P# |
Person | Social memory about someone |
L# |
Place | Spatial memory about a location |
Every memory has importance (0-10), emotion, and tags. The prefix + rowid (e.g. E3, T12) is the universal ID used by forget and alter.
| Flag | Default | Description |
|---|---|---|
--db <path> |
memory39.db |
SQLite database file |
--ram |
off | In-memory database (non-persistent) |
memory39 event "Had coffee with Alice" --date 2025-03-15 --people Alice --tags coffee,social| Arg/Flag | Required | Description |
|---|---|---|
<event> |
yes | What happened (max 255 chars) |
--date |
no | YYYY-MM-DD - omit for undated |
--time |
no | HH:MM (default 00:00) |
--note |
no | Additional note |
--tags |
no | Comma-separated tags |
--importance |
no | 0-10 (default 5) |
--emotion |
no | positive, negative, neutral, or free text |
--location |
no | Where it happened |
--people |
no | Comma-separated names |
--source |
no | experienced, told, read, observed |
memory39 thing "Rust edition 2024 requires rustc 1.85+" --category programming --confidence 9| Arg/Flag | Required | Description |
|---|---|---|
<thing> |
yes | What to remember (max 255 chars) |
--desc |
no | Description |
--category |
no | Free-text category |
--tags |
no | Comma-separated tags |
--importance |
no | 0-10 (default 5) |
--emotion |
no | Emotional valence |
--source |
no | Where this knowledge came from |
--confidence |
no | Certainty 0-10 (default 5) |
--related |
no | Comma-separated related concepts |
memory39 person "Alice" --role "ML engineer" --relationship colleague --met-at "KubeCon 2024"| Arg/Flag | Required | Description |
|---|---|---|
<name> |
yes | Person's name |
--role |
no | Role or title |
--relationship |
no | friend, colleague, family, etc. |
--contact |
no | Email, phone, handle |
--met-at |
no | Where/when you met |
--last-seen |
no | Last interaction YYYY-MM-DD |
--note |
no | Additional note |
--tags |
no | Comma-separated tags |
--importance |
no | 0-10 (default 5) |
--emotion |
no | Emotional valence |
memory39 place "Blue Bottle Coffee" --address "123 Main St, SF" --kind building --tags coffee,work| Arg/Flag | Required | Description |
|---|---|---|
<name> |
yes | Place name |
--desc |
no | Description |
--address |
no | Address or coordinates |
--kind |
no | city, building, room, outdoor, virtual, etc. |
--note |
no | Additional note |
--tags |
no | Comma-separated tags |
--importance |
no | 0-10 (default 5) |
--emotion |
no | Emotional valence |
memory39 recall "coffee" --limit 5 --min 3 --kind event
memory39 recall "*" # list all| Arg/Flag | Required | Description |
|---|---|---|
<query> |
yes | FTS5 search query (* = all) |
-l, --limit |
no | Max results (default 10) |
--min |
no | Minimum importance 0-10 |
--from |
no | Date start YYYY-MM-DD (events only) |
--to |
no | Date end YYYY-MM-DD (events only) |
--kind |
no | event, undated, events, thing, person, place |
--source |
no | experienced, told, read, observed |
--offset |
no | Skip first N results (default 0) |
memory39 connect Alice Berlin meeting| Arg/Flag | Required | Description |
|---|---|---|
<concepts...> |
yes | 2-3 concepts to connect |
--min |
no | Minimum importance 0-10 |
--timeout |
no | Timeout in ms (default 2000) |
Three-phase discovery: (1) direct - all concepts in one memory, (2) shared - concepts in separate memories linked by a common field, (3) bridge - one-hop connections through shared field values.
memory39 forget E3memory39 alter T2 --text "Updated fact" --importance 8| Flag | Description |
|---|---|
--text |
New primary text |
--note |
New note |
--tags |
New tags |
--importance |
New importance 0-10 |
--emotion |
New emotion |
--location |
New location (events only) |
--people |
New people (events only) |
--source |
New source |
--date |
New date (dated events only) |
--time |
New time (requires --date) |
memory39 mcp starts the MCP server (STDIO transport). It runs purely against the local SQLite database: no network calls, no API keys, no external services.
Database path: ~/.memory39/memory39.db (auto-created). This path is shared across every MCP client on the machine, so configuring memory39 in Claude Code, Claude Desktop, Codex, OpenCode, and OpenClaw gives all of them the same memory. Override with MEMORY39_DB=/path/to/other.db for project-scoped or isolated databases.
Add to your MCP client config:
{
"mcpServers": {
"memory39": {
"command": "memory39",
"args": ["mcp"]
}
}
}| Tool | Description | Required Params |
|---|---|---|
recall |
Search with temporal-priority scoring | query |
event |
Store an event (dated or undated) | event |
thing |
Store a fact, concept, or object | thing |
person |
Store a social memory | name |
place |
Store a spatial memory | name |
forget |
Delete a memory by ID | id |
alter |
Modify fields of an existing memory | id |
connect |
Find connections between 2-3 concepts | concepts |
All optional parameters match their CLI counterparts. See the tool schemas for full details.
Results from recall are ranked by composite score:
| Component | Weight | Description |
|---|---|---|
| Relevance | 0.4 | FTS5 match quality |
| Importance | 0.3 | Memory importance (0-10) |
| Recency | 0.3 | Exponential decay, 30-day half-life |
All tools use the same universal ID system:
| Prefix | Table |
|---|---|
E |
Dated events |
U |
Undated events |
T |
Things |
P |
Persons |
L |
Places |
memory39 has no external dependencies at runtime: no network, no API keys, no .env file. The only environment variable it reads is an optional DB-path override.
| Variable | Used by | Purpose |
|---|---|---|
MEMORY39_DB |
MCP server | Override DB path (supports leading ~/). Default: ~/.memory39/memory39.db. For the CLI, use the --db flag instead. |
- TurboMCP - Rust MCP server framework
Apache-2.0