Engram: persistent memory for agents on Linux

Mascote do LinuxPro encaixando um cartucho de memória no SQLite, com o elefante do Engram na bancada

The terminal agent is great until the session ends. In the next one, it rediscovers the same bug, rereads the same README, and asks again if the API uses JWT. Engram is a local brain for this: a binary in Go, a SQLite database with FTS5 at ~/.engram/engram.db, and the same database for Claude Code, Codex, Cursor, Grok, and any other MCP client.

It's not a transcript dump. The agent decides what's worth remembering — decision, bug, convention — and writes with mem_save. In the next session, mem_search and mem_context return the right chunk. The map of alternatives (mnemo, Leteo, Subcog…) is at Claude-Mem alternatives in Go, Rust, and C; the map of terminal agents, at AI assistants on the Linux terminal. This guide walks through setting up Engram on Linux: installation, connecting to agents, configuring the project name, and enabling the daemon at boot.

What it is — and what it isn't

Engram (/ˈen.ɡræm/, the physical trace of a memory in the brain) is a Go binary with embedded SQLite via modernc.org/sqlite — pure Go, no CGO, no Node, no Python, no Docker for daily use. MIT license. The stable line in July 2026 is v1.20.0.

Four ports for the same database:

Port Command What for
MCP stdio engram mcp --tools=agent The agent talks with memory (Claude, Grok, Codex…)
CLI engram search, save, context You manually query and save
TUI engram tui Browse sessions and observations in the terminal
Local HTTP engram serve (port 7437) JSON API. It is not a browser page

The HTML dashboard (/dashboard) is part of Engram Cloud — another process, Postgres, optional. Without Cloud, the local viewer is the TUI.

The philosophy, explicit in the README: the agent already has the LLM and the context. Instead of capturing every tool call and compressing afterward (the claude-mem model), Engram asks the agent to record only what lasts. Result: clean FTS5 search, zero extra API calls, one file you control.

Install on Ubuntu/Linux

There is no apt package or .deb in the releases. What the production documentation asks for is the binary. One file, zero runtime: no Node, no Python, no Docker. The stable branch in July 2026 is v1.20.0.

The releases provide ready-to-use Linux binaries for amd64 and arm64. The official executable is statically linked, with no runtime dependencies; just pick the file for your machine's architecture. To compile from source, the requirement is Go 1.24+.

Ubuntu amd64 and arm64

sudo apt-get update
sudo apt-get install -y curl ca-certificates

VER=1.20.0
case "$(dpkg --print-architecture)" in
  amd64) FILE=engram_${VER}_linux_amd64.tar.gz ;;
  arm64) FILE=engram_${VER}_linux_arm64.tar.gz ;;
  *) echo "sem binário pronto para $(dpkg --print-architecture)"; exit 1 ;;
esac

cd /tmp
curl -fsSL -O "https://github.com/Gentleman-Programming/engram/releases/download/v${VER}/${FILE}"
curl -fsSL -O "https://github.com/Gentleman-Programming/engram/releases/download/v${VER}/checksums.txt"
sha256sum -c checksums.txt --ignore-missing

mkdir -p ~/.local/bin
tar -xzf "$FILE" engram
install -m 755 engram ~/.local/bin/engram

# o ~/.profile do Ubuntu só acrescenta ~/.local/bin no próximo login,
# e só se o diretório já existir
export PATH="$HOME/.local/bin:$PATH"
hash -r
engram version

The expected output is engram 1.20.0. The tarball contains the executable at the root, plus README, LICENSE, and CHANGELOG. Default database: ~/.engram/engram.db.

To make the PATH permanent, open a new terminal after installation. Ubuntu's default ~/.profile already has the block if [ -d "$HOME/.local/bin" ] — so mkdir comes before install.

go install

Use this if you already have Go 1.24+ (the documentation requirement). golang-go from Ubuntu 24.04 is 1.22 and the one from 22.04 is 1.18 — you can't go install that way; use the release binary or install Go from the website:

go install github.com/Gentleman-Programming/engram/cmd/engram@latest
# cai em $(go env GOPATH)/bin — em geral ~/go/bin/

go install stamps the version as dev. If you want the git-describe in engram version, clone and pass -ldflags — a go install and a go build in the same PATH leave two binaries and the “dev” version wins. There's a v2 line (github.com/Gentleman-Programming/engram/v2/cmd/engram) for release candidates; production continues on v1.

Homebrew on Linux

brew install gentleman-programming/tap/engram

The tap covers the stable v1.20.0 line. Upgrade: brew update && brew upgrade engram. Swapping the binary kills an unsupervised engram serve process — that's why the systemd service, below.

First contact

engram version
engram stats
engram projects list
engram tui

Default database: ~/.engram/engram.db. Override: ENGRAM_DATA_DIR. In the TUI, j/k navigates, Enter opens, / searches, Esc returns. Catppuccin Mocha theme.

The local API, if you start the daemon:

engram serve          # 127.0.0.1:7437
curl -s http://127.0.0.1:7437/health
# {"service":"engram","status":"ok","version":"0.1.0"}

/ and /dashboard on this port return 404. It's JSON, not a site. GET /project/current uses the cwd of the serve process — on systemd with WorkingDirectory=%h, this becomes the basename of home, not the repo you're editing.

Connect to agents

engram setup <agente> writes MCP in the format of each client. It does not configure Cloud. Restart the agent afterwards.

Agent Command
Claude Code claude plugin marketplace add Gentleman-Programming/engram and claude plugin install engram — or engram setup claude-code
Codex engram setup codex
OpenCode engram setup opencode
Cursor engram setup cursor
Gemini CLI engram setup gemini-cli
VS Code Copilot engram setup vscode-copilot
Windsurf, Qwen, Kiro, Kilo, Pi, Antigravity engram setup <nome> — list in engram --help

MCP Profiles: agent (the set that the agent uses on a daily basis), admin, or all. The default of engram mcp without a flag is all. For the agent, use --tools=agent.

Grok Build (manual)

There is no engram setup grok. In ~/.grok/config.toml:

[mcp_servers.engram]
command = "/home/SEU_USER/.local/bin/engram"
args = ["mcp", "--tools=agent"]
enabled = true

Check with grok mcp doctor engram — handshake ok and the tools from the agent profile. Reload the Grok session: MCP only kicks in on the one that boots up after the config. The database is the same as Claude's. The project name may differ: each process detects it through its own cwd.

Any MCP client

{
  "mcpServers": {
    "engram": {
      "command": "engram",
      "args": ["mcp", "--tools=agent"]
    }
  }
}

The client spawns a stdio process per session. That doesn't replace systemd’s engram serve — they're different things. MCP reads and writes SQLite directly; serve is the HTTP API (and Cloud autosync, if you enable it).

How Engram chooses the project

It doesn't index the path. It resolves a name and filters SQLite by that name. Precedence:

  1. explicit argument (mem_context with project, engram context teste);
  2. process override (engram mcp --project … or ENGRAM_PROJECT);
  3. detection by cwd.

The directory detection, in order:

# source What it uses
0 config .engram/config.json with project_name
1 git_remote repo with origin — remote name, stored in a private binding of the clone
2 git_root git without origin — basename of the root
3 git_child cwd has exactly one git child — promotes that
4 ambiguous multiple git children — doesn't pick one on its own
5 dir_basename last resort: filepath.Base(cwd)

Folder without git becomes the basename. Claude saving in /home/você/teste and Grok in …/site-linuxpro are two projects. The memory doesn't show up “on the other side” until you search with the right name or all_projects=true.

Stable lock at the repo root:

{
  "project_name": "site-linuxpro"
}

Engram uses the config closest below the git root — you can have backend/.engram/config.json and frontend/.engram/config.json in the monorepo. Outside git, only the current directory's config applies: ~/.engram/config.json doesn't leak to child directories. First useful call of any session: mem_current_project — returns project, project_source and, if applicable, available_projects.

If the write comes back ambiguous_project, the agent cannot guess. You pick a name from the list and the tool retries with project_choice_reason=user_selected_after_ambiguous_project.

Start the server on boot

This section is optional. Claude Code, Codex, and the other MCP clients via stdio use engram mcp and don't need engram serve. Start the service if you need the local HTTP API, Cloud autosync, or HTTP integrations like OpenCode and Pi.

When it's needed, a supervisor prevents engram serve from dying on reboot or during binary replacement.

User unit, in the official template:

# ~/.config/systemd/user/engram.service
[Unit]
Description=Engram Memory Server
After=network.target

[Service]
WorkingDirectory=%h
ExecStart=%h/.local/bin/engram serve
Restart=always
RestartSec=3
Environment=ENGRAM_DATA_DIR=%h/.engram
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=default.target
mkdir -p ~/.engram ~/.config/systemd/user
systemctl --user daemon-reload
systemctl --user enable --now engram.service
curl -s http://127.0.0.1:7437/health
journalctl --user -u engram -f

enable ties startup to login. To start at boot without a graphical session, the user needs linger:

loginctl enable-linger "$USER"
loginctl show-user "$USER" -p Linger

If port 7437 is already taken by an orphan server, the unit fails to bind. Kill the old process (ss -tlnp | grep 7437) and systemctl --user restart engram.

The memory contract

The README treats Engram as curated memory, not as a dump. The cycle:

  1. Guide — mem_current_project, then mem_context / mem_search.
  2. Search before repeating — decisions, bugs, conventions that may already be in the bank.
  3. Reveal in layers — mem_search returns a preview (~300 characters); mem_get_observation returns the full text; mem_timeline shows the before/after in the session.
  4. Record what lasts — mem_save with a short title, type (decision, bugfix, config, architecture…) and a What / Why / Where / Learned body. Do not store raw tool output.
  5. Evolving topic — a stable topic_key (architecture/auth-model). Same project + scope + topic becomes an upsert, not a new row.
  6. Close the session — mem_session_summary with Goal, Discoveries, Accomplished, Next Steps, Files.

Scope: project (default), personal or global. A personal entry with the same topic_key does not overwrite the project-level one.

What doesn't go into SQLite: passwords, tokens, dumps of journalctl. Git remains the record — the history is in The history of Git. Tags <private> are stripped in two layers in Engram; even so, do not store anything that must not leak.

Cloud and dashboard in the browser

Cloud is optional replication + UI. Local SQLite remains authoritative. Docker smoke-test setup, in a clone of the repository:

docker compose -f docker-compose.cloud.yml up -d
engram cloud config --server http://127.0.0.1:18080
engram cloud enroll meu-projeto
engram sync --cloud --project meu-projeto

Local dashboard: http://127.0.0.1:18080/dashboard. In the smoke compose setup, /dashboard/login redirects without asking for a token. Useful routes: /dashboard/browser, /dashboard/projects, /dashboard/activity.

For everyday use on a laptop, Cloud is extra. TUI + MCP are enough.

Tips to avoid pain

  • One brain, the right names. Claude, Grok, and Codex only share memory if the project is the same. Put .engram/config.json in the repo on day one.
  • Do not install three memory servers in the same agent. They compete for context. One for sessions (Engram) + the code graph (codebase-memory-mcp) is already the useful limit — how it fits into the workflow is covered in Vibe coding on Linux.
  • MCP ≠ serve. Grok/Claude starts engram mcp over stdio. systemd starts engram serve on 7437. Both can coexist; one does not replace the other.
  • Serve cwd. A unit with WorkingDirectory=%h makes /project/current return the home directory's basename. To read a project, use the agent's MCP (repo cwd) or pass --project.
  • FTS5 search uses AND by default. match_mode=any broadens it. A preview is not the full record — open the id.
  • Doctor. engram doctor (or mem_doctor) checks the SQLite lock, session mismatch, and binding. Four checks passing means a healthy state.
  • Linger. Without loginctl enable-linger, the user unit only lives while a session exists. With linger, it survives a headless reboot.
  • Upgrading the binary. Homebrew or an install over the existing binary kills the process unless systemd is configured with Restart=always.
  • Do not mix native autosync and cron wrappers. The documentation calls for a single mode.
  • HTTP token. ENGRAM_HTTP_TOKEN protects delete/export/import in the local API. Without it, these routes are open on 127.0.0.1 — fine on a laptop, bad if you expose 7437 on the LAN.

Engram or claude-mem?

Engram was inspired by claude-mem, with opposite design choices: a single binary, any MCP agent, FTS5 instead of a worker + ChromaDB, and the agent compressing on the spot instead of a separate pipeline. If you only use Claude Code and are already in its hooks ecosystem, the original remains valid. If Claude, Codex, and Grok need to remember the same decision, start with Engram. A side-by-side comparison and the other Go/Rust/C options: Alternatives to Claude-Mem.

Closing

Install the release binary, pin the project name in a .engram/config.json, enable MCP in each agent, and leave engram serve in user systemd. The rest is discipline: store what lasts, search before repeating, and close the session with a summary. The next morning's agent already knows that authentication uses JWT — and you do not pay a second model just to remember it.

To continue: AI assistants in the terminal, Ollama if inference is local too, and the history of Go — the language that makes Engram a single file.

Official links: github.com/Gentleman-Programming/engram · releases · engram.gentlemanprogramming.com · Installation · Agent Setup