A local-first AI memory agent written in Rust. Provides BM25 + vector hybrid search, a temporal knowledge graph, episodic/semantic memory layers, persona isolation, privacy-preserving tombstoning, and an HTTP API.
- Hybrid search — BM25 (0.5) + cosine vector (0.4) × Ebbinghaus decay
- Temporal knowledge graph — entities, time-bounded relations, point-in-time queries
- Persona isolation — all data scoped per persona; no cross-persona leakage
- Right to be forgotten — SHA-256 tombstoning; content zeroed, hash preserved in audit log
- MIP interop — JSON-LD export/import (Memory Interchange Protocol)
- HTTP server — axum-based REST API on configurable port
- Audit log — every create/read/tombstone/export is recorded
cargo build --release
# binary at target/release/dmDrake Memory requires PostgreSQL with the pgvector
extension for the memories.vector column and the HNSW cosine index. The
schema apply (dm init, and every tool on first connect) hard-fails with
extension "vector" is not available if pgvector is not installed.
Linux/macOS — install a PG distro that ships pgvector:
# Debian/Ubuntu (replace XX with your PG major version)
sudo apt install postgresql-XX-pgvector
# macOS (Homebrew)
brew install pgvector
# Or use the official Docker image
docker run -d --name pg -e POSTGRES_PASSWORD=pw -p 5432:5432 pgvector/pgvector:pg17Windows — the scoop / EnterpriseDB installers do not ship pgvector. Run the helper script after installing PG:
# Requires Visual Studio 2022 BuildTools with the C++ workload
# (Microsoft.VisualStudio.Component.VC.Tools.x86.x64). The script is idempotent.
pwsh scripts\install-pgvector-windows.ps1The script clones pgvector (pinned to v0.8.2), builds with MSVC against the
local PG headers, installs vector.dll + extension files into the PG tree, and
runs CREATE EXTENSION IF NOT EXISTS vector in drake_memory. See
scripts/install-pgvector-windows.ps1 -? for parameters (-PgRoot,
-PgvectorRef, -Database, -Force).
After installing the extension, verify with:
psql -d drake_memory -c "SELECT extname, extversion FROM pg_extension WHERE extname='vector';"dm init
dm store --content "The user prefers dark mode"
dm search --query "dark mode"
dm entity add --name "Alice" --type person
dm entity add --name "Acme" --type organization
dm relation add --subject Alice --predicate works-at --object Acme
dm kg query --name Alice
dm audit list
dm serve --port 8080dm [--db <PATH>] <COMMAND>
Commands:
init Initialize DB + default persona
store Store a memory (--content, --confidence, --tags, --persona, --json)
episode Log an episode (--content, --role, --session, --persona, --json)
search Search memories (--query, --n, --persona, --as-of, --json)
forget Tombstone a memory (--id)
mark User-mark a memory (--id)
entity add --name --type --persona
relation add | invalidate
kg query --name --as-of --persona --json
audit list --limit --json
consolidate Run episode→memory consolidation (--persona, --json)
export MIP JSON-LD export (--persona, --output)
import MIP JSON-LD import (--file, --persona)
persona list | create
serve Start HTTP server (--port)
status DB statistics
| Method | Path | Description |
|---|---|---|
| POST | /memories | Store memory |
| GET | /memories/search | Hybrid search |
| DELETE | /memories/:id | Tombstone memory |
| PUT | /memories/:id/mark | User-mark |
| POST | /episodes | Log episode |
| GET | /episodes | List recent episodes |
| POST | /entities | Add entity |
| POST | /relations | Add relation |
| GET | /kg/query | Query KG |
| GET | /audit | Audit log |
| POST | /consolidate | Run consolidation |
| GET | /export | MIP JSON-LD export |
| POST | /import | MIP JSON-LD import |
| GET | /personas | List personas |
| POST | /personas | Create persona |
| GET | /status | Status |
Requires Go 1.21+ and the dm binary on $DM_BINARY.
cd bdd-tests/steps
DM_BINARY=../../target/release/dm go test -v ./...See TENETS.md — eight failure modes the design explicitly avoids.
Default config at ~/.config/drake-memory/config.toml. Override DB with --db <PATH> or DM_DB env var.
| Variable | Default | Description |
|---|---|---|
| Database | ||
DM_DATABASE_URL |
postgres://localhost/drake_memory |
PostgreSQL connection string. Overrides config.toml. |
DM_DB |
(none) | Legacy DB path / connection override accepted on the CLI. |
DM_FORCE_SCHEMA |
false |
When 1 / true, force re-apply schema even if _dm_schema version matches. |
DM_DB_POOL_SIZE |
auto | Postgres connection pool size. Auto-sized as max(DM_UPSERT_CONCURRENCY, DM_FOLDER_CONCURRENCY) + 10. Override if you raise the concurrency vars beyond their defaults. |
DM_DB_ACQUIRE_TIMEOUT_SECS |
15 |
How long to wait for a free pool connection before erroring. Raise if you see acquire timeouts under heavy indexing. |
| Indexing concurrency | ||
DM_REPO_CONCURRENCY |
8 |
Max number of git repositories indexed in parallel during a wake/auto-index pass. |
DM_PLAIN_DIR_CONCURRENCY |
2 |
Max number of non-git subdirectories indexed in parallel. Capped low because each subdirectory itself fans out by DM_FOLDER_CONCURRENCY; raising this beyond 2 will likely require also raising DM_DB_POOL_SIZE to avoid pool starvation (see TENETS.md #10). |
DM_FOLDER_CONCURRENCY |
16 |
Max number of file indexing tasks per subdirectory. |
DM_UPSERT_CONCURRENCY |
16 |
Max concurrent memory upserts to PostgreSQL. Also drives the auto pool size. |
| LLM / embedding backend | ||
DM_LLM_BACKEND |
mcp |
mcp = use the MCP host's connected LLM via sampling/createMessage for KG extraction and the built-in FNV/TF-IDF embedder for vectors (current default behavior). custom = call a local Ollama-compatible HTTP server for both KG extraction and embeddings. |
DM_LLM_ENDPOINT |
http://localhost:11434 |
Base URL of the Ollama-compatible server when DM_LLM_BACKEND=custom. |
DM_KG_MODEL |
qwen2.5:instruct |
Model passed to /api/generate for knowledge-graph entity / relation extraction. |
DM_EMBEDDING_MODEL |
nomic-embed-text |
Model passed to /api/embeddings for memory vectors. |
DM_LLM_TIMEOUT_SECS |
60 |
Per-request timeout (KG generate and embeddings). |
Concurrency tuning. The peak number of in-flight file tasks during an
auto-index is approximately
DM_REPO_CONCURRENCY + DM_PLAIN_DIR_CONCURRENCY × DM_FOLDER_CONCURRENCY,
each of which may issue an upsert (capped further by DM_UPSERT_CONCURRENCY).
Stay within DM_DB_POOL_SIZE or you will see acquire-timeout errors. If you
raise DM_PLAIN_DIR_CONCURRENCY or DM_FOLDER_CONCURRENCY, also raise
DM_DB_POOL_SIZE (or rely on the auto formula by raising
DM_UPSERT_CONCURRENCY in lockstep).
Switching backends. Set DM_LLM_BACKEND=custom and (optionally) override the
endpoint / model names above. If the custom embedder produces a different
native dimension than the memories.vector column (512), output vectors are
truncated or zero-padded and re-normalized to fit; for best recall, re-index
after a swap. On any HTTP error the indexer falls back to the regex-based KG
extractor and the FNV embedder so indexing never stalls on a flaky local model
server.
Example — run with Ollama locally:
ollama pull qwen2.5:7b-instruct
ollama pull nomic-embed-text
DM_LLM_BACKEND=custom \
DM_KG_MODEL=qwen2.5:7b-instruct \
DM_EMBEDDING_MODEL=nomic-embed-text \
dm wake --background