Semantic memory service and MCP adapter backed by PostgreSQL, pgvector, and local Ollama models.
Current layout:
agent client <--stdio--> memory-mcp <--HTTP--> memoryd
|
+--> memory-common
|
+--> PostgreSQL + pgvector
|
+--> Ollama
Workspace crates:
memory-common: shared config, models, transcript parsing, error types, and HTTP/MCP payload typesmemoryd: the HTTP service plusdreamandingestmaintenance binariesmemory-mcp: the stdio MCP adapter that talks tomemorydover HTTP
- Persistent semantic memories with embeddings stored in PostgreSQL
- Hybrid retrieval: vector similarity plus PostgreSQL full-text search
- Optional query expansion and LLM reranking for
memory_search - Core-memory recall at session start with
memory_recall - CRUD tools: store, search, list, get, update, delete
- Memory graph: weighted edges between memories for cross-reference traversal and search expansion
- Session transcript archival via
session_log_store - Session-log fallback search when no durable memories match
- Maintenance binaries for transcript ingest and dream/prune passes
| Category | Purpose |
|---|---|
context |
Project conventions and stable background |
decision |
Architectural or workflow decisions |
error_fix |
Symptoms, root cause, and resolution |
plan |
Reusable implementation plans |
rule |
Durable instructions or constraints |
memory_recall returns the categories considered core by the current
implementation: decision, error_fix, plan, and rule.
- Rust 1.85+
- PostgreSQL 17 with
pgvector - Ollama with:
- embedding model:
bge-m3 - generation models: defaults use
llama3.1
- embedding model:
Example with Podman:
podman run -d --name memory-pg \
-e POSTGRES_DB=memory \
-e POSTGRES_USER=memory \
-e POSTGRES_PASSWORD=memory \
-p 5432:5432 \
-v memory-pg-data:/var/lib/postgresql/data \
pgvector/pgvector:pg17ollama pull bge-m3
ollama pull llama3.1Copy and edit config.toml.example if needed:
http_bind = "127.0.0.1:8080"
memoryd_url = "http://127.0.0.1:8080"
database_url = "postgres://memory:memory@localhost/memory"
ollama_url = "http://localhost:11434"
embedding_model = "bge-m3"
expand_model = "llama3.1"
rerank_model = "llama3.1"
dream_model = "llama3.1"
expand_num_ctx = 8192
rerank_num_ctx = 8192
dream_num_ctx = 8192Configuration fields:
| Field | Default | Purpose |
|---|---|---|
database_url |
postgres://memory:memory@localhost/memory |
PostgreSQL connection string |
http_bind |
127.0.0.1:8080 |
Bind address for memoryd |
memoryd_url |
http://127.0.0.1:8080 |
Base URL used by memory-mcp |
ollama_url |
http://localhost:11434 |
Ollama base URL |
embedding_model |
bge-m3 |
Embedding model |
embedding_tokenizer_repo |
None |
HF hub repo (e.g. BAAI/bge-m3) to download tokenizer from for guided truncation |
embedding_tokenizer_revision |
main |
HF hub repo revision (branch/tag/SHA) |
expand_model |
llama3.1 |
Query expansion model |
rerank_model |
llama3.1 |
Search reranking model |
dream_model |
llama3.1 |
Dream/prune maintenance model |
expand_num_ctx |
8192 |
Context window for query expansion |
rerank_num_ctx |
8192 |
Context window for search reranking |
dream_num_ctx |
8192 |
Context window for dream maintenance |
generate_num_ctx |
8192 |
(Legacy) Context window for all generation calls; used as fallback |
cargo build -p memory-mcp --release
cargo build -p memoryd --releaseWhen building a specific workspace package from the repository root,
always pass -p <package>. For example, use cargo build -p memory-mcp --release instead of cargo build --bin memory-mcp --release, because
the latter can still pull in other workspace members and unify their
features.
RUST_LOG=info ./target/release/memory-mcp ./config.toml
RUST_LOG=info ./target/release/memoryd ./config.tomlmemoryd runs the HTTP service on http_bind. memory-mcp is the
stdio adapter that calls memoryd_url.
Minimal memory-mcp config:
memoryd_url = "http://127.0.0.1:8080"
# api_token = "replace-me"memoryd uses the full server config shown earlier. memory-mcp only
needs memoryd_url and, if enabled on the server, api_token.
Add an MCP server entry to ~/.codex/config.toml:
[mcp_servers.memory]
command = "/absolute/path/to/target/release/memory-mcp"
args = ["/absolute/path/to/config.toml"]
[mcp_servers.memory.env]
RUST_LOG = "info"Add a stdio MCP server entry to ~/.claude.json:
{
"mcpServers": {
"memory": {
"type": "stdio",
"command": "/absolute/path/to/target/release/memory-mcp",
"args": ["/absolute/path/to/config.toml"]
}
}
}For use cases where multiple agents across different machines or locations need to share a common memory store:
By default, memoryd binds to 127.0.0.1, which only allows local connections.
To make it reachable over a network, set http_bind to 0.0.0.0 (all
interfaces) or a specific IP address in your config.toml:
http_bind = "0.0.0.0:8080"When the service is reachable over a network, you must enable api_token in
the server config.toml. The default (no token) is only appropriate for
private localhost use.
api_token = "your-secure-shared-secret"Each agent's memory-mcp needs to know where the central memoryd is running.
In each agent's local config.toml, set memoryd_url to the server's actual
address and include the api_token:
memoryd_url = "http://memory.example.com:8080"
api_token = "your-secure-shared-secret"memoryd speaks plain HTTP. For internet-facing setups or when traversing
untrusted networks, it is recommended to run memoryd behind a reverse proxy
(such as Nginx or Caddy) that provides TLS (HTTPS) termination.
Multiple agents (e.g. Claude Code on a laptop and Codex on a remote server) can
all point to the same memoryd instance. They will share all memories
within the same project namespace, enabling cross-agent collaboration and
persistent context.
memoryd currently exposes:
GET /api/v1/healthPOST /api/v1/memoriesPOST /api/v1/memories/searchGET /api/v1/memories/{id}PATCH /api/v1/memories/{id}DELETE /api/v1/memories/{id}GET /api/v1/projects/{project}/recallPOST /api/v1/sessions
If api_token is set in config, all /api/v1/* routes except health
require Authorization: Bearer <token>.
| Tool | Purpose |
|---|---|
memory_server_version |
Return version plus git hash |
memory_store |
Store a new memory |
memory_search |
Hybrid semantic search within a project |
memory_recall |
Load core memories for a project |
memory_rules |
Load general + project durable rules |
memory_bootstrap |
Load effective rules plus non-rule core recall |
memory_list |
Browse memories by project/category |
memory_get |
Fetch a single memory by UUID |
memory_update |
Update summary/content/tags and re-embed if needed |
memory_neighbors |
List neighbor memories reachable via graph edges |
memory_delete |
Delete a memory by UUID |
session_start |
Create or upsert a normalized shared session |
session_message_append |
Append a prompt/response/tool event to a shared session |
session_finalize |
Finalize a shared session into searchable chunks |
session_log_store |
Store a full session transcript for archival/search |
review_queue |
List memories tagged review-needed, with optional category filter |
review_submit |
Store a review decision and mark the original reviewed |
memory_search behavior:
- expands the user query with the configured LLM
- runs hybrid vector + FTS retrieval against durable memories
- expands seed results via graph edges (same-project by default)
- optionally reranks the combined set with the configured rerank model (disabled by default)
- falls back to session-log search if no durable memories match
Parses a JSONL transcript file and stores it into session_logs and
session_log_chunks.
cargo run -p memoryd --release --bin ingest -- ./config.toml /path/to/transcript.jsonlDry run:
cargo run -p memoryd --release --bin ingest -- --dry-run ./config.toml /path/to/transcript.jsonlRuns maintenance passes that merge near-duplicate memories and prune
stale low-importance memories. plan and rule memories are protected
from these mutations.
cargo run -p memoryd --release --bin dream -- ./config.tomlDry run:
cargo run -p memoryd --release --bin dream -- --dry-run ./config.tomlMigrations currently create and evolve:
memoriessession_logssession_log_chunks- full-text search support on memories and session logs
- HNSW vector indexes for semantic retrieval
The current schema is migration-driven. For the next planned shape, see
docs/http-api-v1.md.
A Claude Code PreCompact hook script is included at
hooks/pre-compact.sh. It runs the ingest
binary against the session transcript before compaction.
Additional hook scripts are available for durable rule bootstrap and per-message session capture:
hooks/bootstrap.sh: fetches and cachesmemory_bootstrapoutput for the current session and creates the normalized remote session rowhooks/capture-message.sh: append a user or assistant message to the normalized remote session streamhooks/pre-command.sh: ensures bootstrap state exists for the session and records command attempts as session eventshooks/session-stop.sh: final flush of the normalized session into searchable chunks
This lets you save each prompt and each response as the session unfolds, with agent identity, instead of only storing a transcript at compaction time.
Example wiring with explicit agent identities:
{
"hooks": {
"SessionStart": [{
"hooks": [{
"type": "command",
"command": "/absolute/path/to/memory-server/hooks/bootstrap.sh claude"
}]
}],
"UserPromptSubmit": [{
"hooks": [{
"type": "command",
"command": "/absolute/path/to/memory-server/hooks/capture-message.sh claude user"
}]
}],
"AssistantResponse": [{
"hooks": [{
"type": "command",
"command": "/absolute/path/to/memory-server/hooks/capture-message.sh claude assistant"
}]
}],
"PreToolUse": [{
"hooks": [{
"type": "command",
"command": "/absolute/path/to/memory-server/hooks/pre-command.sh claude"
}]
}],
"PreCompact": [{
"hooks": [{
"type": "command",
"command": "/absolute/path/to/memory-server/hooks/pre-compact.sh"
}]
}],
"Stop": [{
"hooks": [{
"type": "command",
"command": "/absolute/path/to/memory-server/hooks/session-stop.sh"
}]
}]
}
}Notes:
- Use
claudeandcodexas explicit first arguments if both clients write into the same memory service. That makes the stored session events attributable during search/finalization. - The exact event names vary by client. Map these scripts to the closest available events in each client.
- The scripts expect
jqandcurl. - They read
memoryd_urland optionalapi_tokenfromconfig.toml, or fromMEMORY_SERVER_CONFIGif you want to point at another config file. - Hook state is cached under
/tmp/memory-server-hooks/<external-session-id>/.
For durable instruction enforcement, prefer the following flow over
duplicating guidance in AGENTS.md or CLAUDE.md:
- Store durable instructions as
rulememories. Put cross-project rules under projectgeneral; put repo-specific rules under that repo's project name. - Call
memory_bootstrap(project)at session start or first prompt in a hook so the agent receives the effective rule set plus supporting non-rule recall memories. - Call
memory_rules(project)from pre-action hooks when only the enforceable rule set is needed. - Keep hooks focused on deterministic enforcement and verification that does not preempt the client's own permission flow: ensuring bootstrap has happened where needed and recording compliance failures.
- Keep memory rules focused on durable intent and policy that the model must follow but that a shell hook cannot reliably derive on its own.
Memories are connected by weighted edges stored in the memory_edges
table. Edges enable graph-aware search expansion and cross-reference
navigation.
| Relation | Direction | Description |
|---|---|---|
references |
directed | Explicit reference from one memory to another |
related_tag |
undirected | Shared non-structural tags between memories |
similar |
undirected | Embedding cosine similarity neighborhood |
| Origin | When created |
|---|---|
content_uuid_ref |
Write-time: UUID found in memory content |
structural_tag_ref |
Write-time: structural tag like plan:<uuid> |
shared_tag |
Dream maintenance: shared topical tags |
embedding_neighbor |
Dream maintenance: cosine similarity 0.75–0.92 |
usage_reinforcement |
Future: successful retrieval signals |
manual |
Future: explicit user/admin edits |
memory_search expands results via graph edges between outer RRF and
LLM reranking. Expansion follows non-suppressed edges with weight ≥ 0.5.
Scope policy (all conservative by default):
- Same-project edges: always followed
generalproject: only wheninclude_general=true- Foreign projects: only when
cross_project=true, optionally filtered byproject_allowlist
Score decay per hop: 0.7×, with additional discounts for general
(0.9×) and foreign projects (0.5×).
The dream binary includes a graph refresh phase that runs before
merge/prune. It builds similar and related_tag edges using
idempotent upserts. ON DELETE CASCADE on both foreign keys ensures
edges are cleaned up when memories are deleted.
For cross-agent collaboration, use the review-needed tag on any memory
to request review, and the review_queue/review_submit tools to
manage the workflow.
- Claude stores a
planmemory taggedreview-needed. - Codex calls
review_queue(project, category: "plan")to find pending plan reviews. - Codex reviews the plan and calls
review_submit(...). review_submitstores adecisionmemory linked to the plan and updates the original plan tags fromreview-neededtoreviewed.
- An agent stores a
contextmemory taggedreview-neededandcode-review, with git range and description in the content. - Another agent calls
review_queue(project)orreview_queue(project, category: "context")to find pending reviews. - The reviewer inspects the code and calls
review_submit(...). review_submitstores adecisionmemory and retags the original.
When launching short-lived, targeted worker sessions, you must avoid context dilution. Unrestricted bootstraps (memory_bootstrap) in highly specialized workers (e.g. ones that solely write frontend CSS vs ones that manage SQL migrations) will pollute the AI's context with rules and guidelines meant for entirely different phases of the project.
For isolated workers, you should completely avoid memory_bootstrap or pass include_recall=false minus the general rules, and instead rely on Tag-Based Filtering. Rules and memories can be scoped using tags such as lang:rust, lang:typescript, phase:planning, or phase:styling.
- At session start, specialized workers MUST be instructed to call
memory_rules(project, tags=["lang:rust"])or multi-tag combinations liketags=["lang:rust", "phase:planning"]. - For retrieval, workers must exclusively use
memory_search(tags=...)targeted to their operational domain. - If creating rules or plans intended for specialized agents, always ensure they are tagged with the relevant
lang:*orphase:*identifiers.
Repo-managed skills live under skills/.
Current shared skill:
skills/review/SKILL.md: unified workflow for requesting and performing reviews (plans, code, etc.) withreview_queueandreview_submit
Install symlink(s) for local clients with:
./scripts/install-skills.sh allOr target one client:
./scripts/install-skills.sh codex
./scripts/install-skills.sh claudeThis symlinks the repo-managed skill into:
~/.codex/skills/review~/.claude/skills/review
Run tests with:
cargo testUseful files:
src/tools.rs: MCP tool surfacesrc/db.rs: SQL access layersrc/transcript.rs: JSONL transcript parsing and chunkingdocs/http-api-v1.md: planned HTTP API split