Skip to content

ENGRAM Agent Memory Server

The canonical copy of this runbook lives in the repository at docs/agent-memory-server.md — agents read it from the repo at runtime. This page is a published mirror.

This runbook covers running one long-lived ENGRAM MCP server that every AI coding agent — Claude Code, GitHub Copilot, Cursor, OpenAI Codex, and Gemini — connects to as a single shared primary memory. The five agents point at the same process, so a fact one agent stores is immediately recallable by the others.

The server runs as a persistent process configured for shared HTTP access:

Setting Value Notes
MCP_TRANSPORT streamable-http One shared HTTP server instead of per-client stdio
PORT 3000
HOST 127.0.0.1 Loopback only — per-machine (see caveat below)
AUTH_REQUIRED true Per-agent API keys enforced
DEPLOYMENT_PROFILE standard (default) / lite Pick per tenancy needs (see below)
  • MCP endpoint: http://127.0.0.1:3000/mcp — a single route that handles POST (JSON-RPC requests), GET (server-to-client SSE stream), and DELETE (session teardown), keyed by a per-session mcp-session-id header.
  • Health endpoint: http://127.0.0.1:3000/health.
  • There is no REST memory API. Every memory operation is MCP JSON-RPC over the /mcp route. Store, recall, and the rest of the tool surface are MCP tools, not HTTP verbs.

The rules every agent follows once connected live in agent-memory-contract.md; the per-agent client wiring lives in agent-memory-clients.md.

ENGRAM’s default transport is stdio: each MCP client spawns its own server subprocess. Five agents would mean five independent processes, five lifecycles, and five separate pools of connections to Postgres — with no shared in-process session state.

WP5 D1 chooses a single shared HTTP server instead. All five agents open MCP sessions against the same http://127.0.0.1:3000/mcp endpoint, so they share:

  • One process and one memory store — the whole point of “primary memory.”
  • One lifecycle — started once at login, supervised, restarted on crash.
  • One pool of backing-service connections — Postgres (the only backing service) is opened once, not once per agent.

The rest of this document shows how to run that one server durably (systemd user service or Docker Compose) and how to verify it.

The recommended way to run the persistent server on a single Linux machine is the systemd user unit that ships with the repository at deploy/systemd/engram-mcp.service. It fixes MCP_TRANSPORT=streamable-http, PORT=3000, and HOST=127.0.0.1; sets WorkingDirectory=%h/Cloud/Projects/engram and EnvironmentFile=%h/.engram/engram-mcp.env; and hardens the process with Restart=always, NoNewPrivileges=true, and PrivateTmp=true.

ExecStart runs node apps/mcp-server/dist/main.js, so the checkout must be built before the unit will start:

Terminal window
pnpm build
Terminal window
cp deploy/systemd/engram-mcp.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now engram-mcp.service

A user service stops when the user logs out unless lingering is enabled. Turn it on so the memory server keeps running (and restarts on boot) without an active session:

Terminal window
loginctl enable-linger "$USER"
Terminal window
journalctl --user -u engram-mcp -f # live logs
systemctl --user status engram-mcp # current state
systemctl --user restart engram-mcp # after a rebuild or env change

The unit reads all secrets and connection settings from ~/.engram/engram-mcp.env. This file is never committed and must be readable only by its owner:

Terminal window
mkdir -p ~/.engram
touch ~/.engram/engram-mcp.env
chmod 600 ~/.engram/engram-mcp.env

At minimum it must set the datastore URLs, the auth gate, the deployment profile, the admin token, and the embeddings configuration. Transport, port, and host are not set here — the unit fixes those.

Terminal window
# ~/.engram/engram-mcp.env — placeholders only, never commit real secrets
# Datastore — Postgres (pgvector image) is the only backing service; vectors,
# STM, sessions, and rate limits all live here.
DATABASE_URL=postgresql://engram:CHANGE_ME@127.0.0.1:5432/engram
# Auth gate — every agent authenticates with a per-agent API key.
AUTH_REQUIRED=true
# Deployment profile: standard (default, multi-tenant) | lite (single-user).
# The legacy value 'enterprise' is accepted as an alias for 'standard'.
DEPLOYMENT_PROFILE=standard
# Admin-only operations (reindex, backfill). Not an agent key.
MCP_ADMIN_TOKEN=CHANGE_ME_ADMIN_TOKEN
# Embeddings: ollama (default — local, no API key; run an Ollama server and
# `ollama pull nomic-embed-text` once), openai (needs a key), local
# (deterministic, for testing), or disabled.
EMBEDDING_PROVIDER=ollama
# OLLAMA_URL=http://localhost:11434 # default; change if Ollama runs elsewhere
# Opt in to OpenAI instead:
# EMBEDDING_PROVIDER=openai
# OPENAI_API_KEY=sk-CHANGE_ME

Choose DEPLOYMENT_PROFILE for your tenancy needs — the profile table and per-profile setup live in SETUP.md. Both profiles run on Postgres alone: standard (the default) wires the multi-tenant auth/organization stack, while lite is the single-user variant with that stack left unwired. The same DATABASE_URL is all either profile needs.

Generating and rotating the per-agent API keys (and turning the AUTH_REQUIRED gate on) is covered in security/agent-keys.md.

To run the server in a container instead of directly on the host, use the production image path: docker-compose.prod.yml (which builds from apps/mcp-server/Dockerfile, listening on port 3000). The full container build and release flow is documented in deploy.md.

The container path serves the same shared MCP server, so it must run with the same environment as the systemd unit — in particular MCP_TRANSPORT=streamable-http and AUTH_REQUIRED=true. Publish the container port to loopback only (for example 127.0.0.1:3000:3000) so the endpoint stays per-machine, matching the HOST=127.0.0.1 bind described above.

The repository ships a verification script at scripts/verify-engram-server.sh. It runs four checks against a running server:

  1. /health answers 200.
  2. The MCP Streamable-HTTP handshake works — an initialize request returns an mcp-session-id header.
  3. tools/list advertises the memory tools (it looks for recall).
  4. With auth enabled, an unauthenticated protected tools/call is rejected with 401.

Run it with the auth gate asserted:

Terminal window
AUTH_REQUIRED=true ./scripts/verify-engram-server.sh

Setting AUTH_REQUIRED=true in the script’s shell makes check 4 a hard assertion (the run fails if the server returns anything but 401); without it the script only notes the observed status. Override the target with ENGRAM_MCP_URL and, if needed, supply a key via ENGRAM_API_KEY.

For a quick manual liveness probe:

Terminal window
curl http://127.0.0.1:3000/health

Binding to 127.0.0.1 makes the server per-machine: only agents running on the same host can reach it. That is the intended WP5 D1 topology for one machine.

If agents run on more than one machine, they cannot share this loopback server. Reaching ENGRAM across hosts requires a hosted endpoint with TLS and authentication — network exposure, certificates, and a routed address. That is an operations concern tracked under WP6; do not widen the bind or expose the port directly. Keep this server on loopback.