Persistent memory for AI coding agents
This is the complete technical reference for Engram. For getting started, see the README. For per-agent setup, see Agent Setup.
| Section | What you'll find |
|---|---|
| Database Schema | Tables, FTS5, SQLite config |
| HTTP API | All REST endpoints with request/response details |
| MCP Tools | Detailed reference for all 15 memory tools |
| Memory Protocol | When/how agents should use the tools |
| Project Name Normalization | Auto-detection, normalization, similar-project warnings |
| Features | FTS5 search, timeline, privacy, git sync, compression |
| TUI | Screens, navigation, architecture |
| Running as a Service | systemd setup |
| Design Decisions | Why Go, why SQLite, why no auto-capture |
For other docs:
| Doc | Description |
|---|---|
| Installation | All install methods + platform support |
| Agent Setup | Per-agent configuration + compaction survival |
| Architecture | How it works, session lifecycle, CLI reference, project structure |
| Plugins | OpenCode & Claude Code plugin details |
| Comparison | Why Engram vs claude-mem |
- sessions —
id(TEXT PK),project,directory,started_at,ended_at,summary,status - observations —
id(INTEGER PK AUTOINCREMENT),session_id(FK),type,title,content,tool_name,project,scope,topic_key,normalized_hash,revision_count,duplicate_count,last_seen_at,created_at,updated_at,deleted_at - observations_fts — FTS5 virtual table synced via triggers (
title,content,tool_name,type,project) - user_prompts —
id(INTEGER PK AUTOINCREMENT),session_id(FK),content,project,created_at - prompts_fts — FTS5 virtual table synced via triggers (
content,project) - sync_chunks —
chunk_id(TEXT PK),imported_at— tracks which chunks have been imported to prevent duplicates
- WAL mode for concurrent reads
- Busy timeout 5000ms
- Synchronous NORMAL
- Foreign keys ON
All endpoints return JSON. Server listens on 127.0.0.1:7437.
GET /health— Returns{"status": "ok", "service": "engram", "version": "<current>"}
POST /sessions— Create session. Body:{id, project, directory}POST /sessions/{id}/end— End session. Body:{summary}GET /sessions/recent— Recent sessions. Query:?project=X&limit=N
POST /observations— Add observation. Body:{session_id, type, title, content, tool_name?, project?, scope?, topic_key?}GET /observations/recent— Recent observations. Query:?project=X&scope=project|personal&limit=NGET /observations/{id}— Get single observation by IDPATCH /observations/{id}— Update fields. Body:{title?, content?, type?, project?, scope?, topic_key?}DELETE /observations/{id}— Delete observation (?hard=truefor hard delete, soft delete by default)
GET /search— FTS5 search. Query:?q=QUERY&type=TYPE&project=PROJECT&scope=SCOPE&limit=N
GET /timeline— Chronological context. Query:?observation_id=N&before=5&after=5
POST /prompts— Save user prompt. Body:{session_id, content, project?}GET /prompts/recent— Recent prompts. Query:?project=X&limit=NGET /prompts/search— Search prompts. Query:?q=QUERY&project=X&limit=N
GET /context— Formatted context. Query:?project=X&scope=project|personal
POST /observations/passive— Extract structured learnings from text. Body:{content, session_id?, project?}
GET /export— Export all data as JSONPOST /import— Import data from JSON. Body: ExportData JSON
GET /stats— Memory statistics
POST /projects/migrate— Migrate observations between project names. Body:{source, target}
GET /sync/status— Chunk sync status (local vs remote counts, pending imports)
| Variable | Description | Default |
|---|---|---|
ENGRAM_DATA_DIR |
Override data directory | ~/.engram |
ENGRAM_PORT |
Override HTTP server port | 7437 |
ENGRAM_PROJECT |
Override project name for MCP server | auto-detected via git |
Search persistent memory across all sessions. Supports FTS5 full-text search with type/project/scope/limit filters.
Save structured observations. The tool description teaches agents the format:
- title: Short, searchable (e.g. "JWT auth middleware")
- type:
decision|architecture|bugfix|pattern|config|discovery|learning - scope:
project(default) |personal - topic_key: optional canonical topic id (e.g.
architecture/auth-model) used to upsert evolving memories - content: Structured with
**What**,**Why**,**Where**,**Learned**
Exact duplicate saves are deduplicated in a rolling time window using a normalized content hash + project + scope + type + title.
When topic_key is provided, mem_save upserts the latest observation in the same project + scope + topic_key, incrementing revision_count.
Update an observation by ID. Supports partial updates for title, content, type, project, scope, and topic_key.
Suggest a stable topic_key from type + title (or content fallback). Uses family heuristics like architecture/*, bug/*, decision/*, etc. Use before mem_save when you want evolving topics to upsert into a single observation.
Delete an observation by ID. Uses soft-delete by default (deleted_at); optional hard-delete for permanent removal.
Save user prompts — records what the user asked so future sessions have context about user goals.
Get recent memory context from previous sessions — shows sessions, prompts, and observations, with optional scope filtering for observations.
Show memory system statistics — sessions, observations, prompts, projects.
Progressive disclosure: after searching, drill into chronological context around a specific observation. Shows N observations before and after within the same session.
Get full untruncated content of a specific observation by ID.
Save comprehensive end-of-session summary:
## Goal
## Instructions
## Discoveries
## Accomplished
## Relevant Files
Register the start of a new coding session.
Mark a session as completed with optional summary.
Extract structured learnings from text output. Looks for ## Key Learnings: sections and saves each numbered/bulleted item as a separate observation. Duplicates are automatically skipped.
Admin tool. Merge multiple project name variants into a single canonical name. Accepts an array of source project names and a target canonical name. All observations, sessions, and prompts from the source projects are reassigned to the canonical project.
The Memory Protocol teaches agents when and how to use Engram's MCP tools. Without it, the agent has the tools but no behavioral guidance. Add this to your agent's prompt file (see Agent Setup for per-agent locations).
Call mem_save IMMEDIATELY after any of these:
- Bug fix completed
- Architecture or design decision made
- Non-obvious discovery about the codebase
- Configuration change or environment setup
- Pattern established (naming, structure, convention)
- User preference or constraint learned
Format for mem_save:
- title: Verb + what — short, searchable (e.g. "Fixed N+1 query in UserList", "Chose Zustand over Redux")
- type:
bugfix|decision|architecture|discovery|pattern|config|preference - scope:
project(default) |personal - topic_key (optional, recommended for evolving decisions): stable key like
architecture/auth-model - content:
**What**: One sentence — what was done **Why**: What motivated it (user request, bug, performance, etc.) **Where**: Files or paths affected **Learned**: Gotchas, edge cases, things that surprised you (omit if none)
- Different topics must not overwrite each other (e.g. architecture vs bugfix)
- Reuse the same
topic_keyto update an evolving topic instead of creating new observations - If unsure about the key, call
mem_suggest_topic_keyfirst and then reuse it - Use
mem_updatewhen you have an exact observation ID to correct
When the user asks to recall something — any variation of "remember", "recall", "what did we do", "how did we solve", "recordar", "acordate", or references to past work:
- First call
mem_context— checks recent session history (fast, cheap) - If not found, call
mem_searchwith relevant keywords (FTS5 full-text search) - If you find a match, use
mem_get_observationfor full untruncated content
Also search memory PROACTIVELY when:
- Starting work on something that might have been done before
- The user mentions a topic you have no context on — check if past sessions covered it
Before ending a session or saying "done" / "listo" / "that's it", you MUST call mem_session_summary with this structure:
## Goal
[What we were working on this session]
## Instructions
[User preferences or constraints discovered — skip if none]
## Discoveries
- [Technical findings, gotchas, non-obvious learnings]
## Accomplished
- [Completed items with key details]
## Next Steps
- [What remains to be done — for the next session]
## Relevant Files
- path/to/file — [what it does or what changed]
This is NOT optional. If you skip this, the next session starts blind.
When completing a task, include a ## Key Learnings: section at the end of your response with numbered items. Engram will automatically extract and save these as observations.
Example:
## Key Learnings:
1. bcrypt cost=12 is the right balance for our server performance
2. JWT refresh tokens need atomic rotation to prevent race conditions
You can also call mem_capture_passive(content) directly with any text that contains a learning section.
If you see a message about compaction or context reset:
- IMMEDIATELY call
mem_session_summarywith the compacted summary content - Then call
mem_contextto recover additional context from previous sessions - Only THEN continue working
Do not skip step 1. Without it, everything done before compaction is lost from memory.
Engram automatically prevents project name drift — the same project saved under different names ("engram" vs "Engram" vs "engram-memory") by different clients or users.
All project names are normalized on write and read: lowercase, trimmed, collapsed hyphens/underscores. If a name is changed during normalization, a warning is included in the response.
The MCP server auto-detects the project name at startup using a priority chain:
--projectflagENGRAM_PROJECTenvironment variable- Git remote origin URL (extracts repo name)
- Git repository root directory name
- Current working directory basename
When saving to a project that doesn't exist yet, Engram checks for similar existing project names (Levenshtein distance, substring, case-insensitive matching) and warns the agent if a likely variant already exists.
Use engram projects consolidate to interactively merge variant project names, or mem_merge_projects for agent-driven consolidation.
- Searches across title, content, tool_name, type, and project
- Query sanitization: wraps each word in quotes to avoid FTS5 syntax errors
- Supports type and project filters
Three-layer pattern for token-efficient memory retrieval:
mem_search— Find relevant observationsmem_timeline— Drill into chronological neighborhood of a resultmem_get_observation— Get full untruncated content
<private>...</private> content is stripped at TWO levels:
- Plugin layer (TypeScript) — Strips before data leaves the process
- Store layer (Go) —
stripPrivateTags()runs insideAddObservation()andAddPrompt()
Example: Set up API with <private>sk-abc123</private> becomes Set up API with [REDACTED]
Separate table captures what the USER asked (not just tool calls). Gives future sessions the "why" behind the "what". Full FTS5 search support.
Share memories across machines, backup, or migrate:
engram export— JSON dump of all sessions, observations, promptsengram import <file>— Load from JSON, sessions use INSERT OR IGNORE (skip duplicates), atomic transaction
Share memories through git repositories using compressed chunks with a manifest index.
engram sync— Exports new memories as a gzipped JSONL chunk to.engram/chunks/engram sync --all— Exports ALL memories from every projectengram sync --import— Imports chunks listed in the manifest that haven't been imported yetengram sync --status— Shows how many chunks exist locally vs remotelyengram sync --project NAME— Filters export to a specific project
.engram/
├── manifest.json <- index of all chunks (small, git-mergeable)
├── chunks/
│ ├── a3f8c1d2.jsonl.gz <- chunk 1 (gzipped JSONL)
│ ├── b7d2e4f1.jsonl.gz <- chunk 2
│ └── ...
└── engram.db <- local working DB (gitignored)
Why chunks?
- Each
engram synccreates a NEW chunk — old chunks are never modified - No merge conflicts: each dev creates independent chunks, git just adds files
- Chunks are content-hashed (SHA-256 prefix) — each chunk is imported only once
- The manifest is the only file git diffs — it's small and append-only
- Compressed: a chunk with 8 sessions + 10 observations = ~2KB
Instead of a separate LLM service, the agent itself compresses observations. The agent already has the model, context, and API key.
Two levels:
- Per-action (
mem_save): Structured summaries (What/Why/Where/Learned) - Session summary (
mem_session_summary): Comprehensive end-of-session summary (Goal/Instructions/Discoveries/Accomplished/Files)
All memory comes from the agent itself — no firehose of raw tool calls. Why? Raw tool calls (edit: {file: "foo.go"}, bash: {command: "go build"}) are noisy and pollute FTS5 search. The agent's curated summaries are higher signal, more searchable, and don't bloat the database. Shell history and git provide the raw audit trail.
Interactive Bubbletea-based terminal UI. Launch with engram tui.
| Screen | Description |
|---|---|
| Dashboard | Stats overview (sessions, observations, prompts, projects) + menu |
| Search | FTS5 text search with text input |
| Search Results | Browsable results list from search |
| Recent Observations | Browse all observations, newest first |
| Observation Detail | Full content of a single observation, scrollable |
| Timeline | Chronological context around an observation (before/after) |
| Sessions | Browse all sessions |
| Session Detail | Observations within a specific session |
j/kor arrow keys — Navigate listsEnter— Select / drill into detailt— View timeline for selected observationsor/— Quick search from any screenEscorq— Go back / quitCtrl+C— Force quit
- Catppuccin Mocha color palette
(active)badge — shown next to sessions and observations from active sessions, sorted to top- Scroll indicators — position in long lists (e.g. "showing 1-20 of 50")
- 2-line items — each observation shows title + content preview
- Move binary to
~/.local/bin(ensure it's in your$PATH) - Create directories:
mkdir -p ~/.engram ~/.config/systemd/user - Create
~/.config/systemd/user/engram.service(see below) systemctl --user daemon-reloadsystemctl --user enable engramsystemctl --user start engramjournalctl --user -u engram -f
[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
[Install]
WantedBy=default.target- Go over TypeScript — Single binary, cross-platform, no runtime. The initial prototype was TS but was rewritten.
- SQLite + FTS5 over vector DB — FTS5 covers 95% of use cases. No ChromaDB/Pinecone complexity.
- Agent-agnostic core — Go binary is the brain, thin plugins per-agent. Not locked to any agent.
- Agent-driven compression — The agent already has an LLM. No separate compression service.
- Privacy at two layers — Strip in plugin AND store. Defense in depth.
- Pure Go SQLite (modernc.org/sqlite) — No CGO means true cross-platform binary distribution.
- No raw auto-capture — The agent saves curated summaries. Shell history and git provide the raw audit trail.
- TUI with Bubbletea — Interactive terminal UI following Gentleman Bubbletea patterns.
| Package | Version | Purpose |
|---|---|---|
github.com/mark3labs/mcp-go |
v0.44.0 | MCP protocol implementation |
modernc.org/sqlite |
v1.45.0 | Pure Go SQLite driver (no CGO) |
github.com/charmbracelet/bubbletea |
v1.3.10 | Terminal UI framework |
github.com/charmbracelet/lipgloss |
v1.1.0 | Terminal styling |
github.com/charmbracelet/bubbles |
v1.0.0 | TUI components |
@opencode-ai/plugin— OpenCode plugin types and helpers- Runtime: Bun (built into OpenCode)
- Agent Setup — connect your agent to Engram
- Plugins — what the OpenCode and Claude Code plugins add beyond bare MCP
- Obsidian Brain — visualize memories as a knowledge graph (beta)
- Contributing — how to contribute