Engram
Give your model a memory, local or cloud. One process, one SQLite file, zero code changes.
The built-in observatory at
/engram/ui: memories orbit by activation, reinforced ones fall inward, decaying ones drift out.
Click a memory and its hebbian web stays lit, dossier alongside; hover any mote for its status, activation, and title.
Engram is a transparent proxy that sits between any client and Ollama. It recalls relevant memories from past sessions and injects them before the model answers (~22 ms overhead, no LLM on the request path), then summarizes each exchange in the background - using the same small model you're chatting with.
┌─────────────────────────────┐
client ──────► │ engramd :11435 │ ──────► ollama :11434
(curl, │ recall → inject → forward │
openclaw, │ response tee → episode log │
hermes) │ idle-time summarizer │
└──────────┬──────────────────┘
└── engram.db (SQLite: FTS5 + vectors + queue)
Install
pip install engram-proxy # or: uvx --from engram-proxy engram up
ollama pull qwen3:1.7b qwen3-embedding:0.6b
Quick start
engram up
# in another terminal - stock ollama, now with memory:
export OLLAMA_HOST=127.0.0.1:11435
ollama run qwen3:1.7b "I'm Sam. My cat is Miso and I'm badly allergic to peanuts."
# ... later, in a NEW session:
ollama run qwen3:1.7b "Ordering Thai tonight - anything I should avoid?"
Requires Ollama with qwen3:1.7b (summarizer) and qwen3-embedding:0.6b
(retrieval) - ollama pull qwen3:1.7b qwen3-embedding:0.6b, ~2 GB total.
Field-tested defaults: sub-1B summarizers garble facts, so qwen3:1.7b is the
default; without it, summaries fall back to extractive, which also preserves
facts. Set
ENGRAM_SUMMARIZER_MODEL / ENGRAM_EMBED_MODEL to override
(nomic-embed-text is ~3x faster to embed but measurably worse at admission).
CLI
engram up run the proxy (default :11435 -> :11434)
engram list recent traces (title + gist + access stats)
engram recall "query" dry-run recall with per-candidate scores
engram why explain the last injection, score by score
engram stats store counts, queue depth, degraded recalls
engram bench measure embed + recall latency on your machine
engram lme LongMemEval-S subset run (dataset auto-detected in .lme/)
Numbers and methodology: BENCHMARKS.md. Headline: LongMemEval-S, all 500 questions: 98.1% evidence retrieval into a ~2300-token block against 115k-token haystacks; 66.4% judged answers with a frontier answering model, above published full-context frontier results at roughly 2% of the tokens. Losing categories and all 9 retrieval misses published.
Why answers trail retrieval, measured and ranked in BENCHMARKS.md: the benchmark ingests with extractive summaries whose truncation loses details at write time (21 of 37 analyzed misses; doubling the block budget recovered nothing, so it is not a selection problem), the answering model's own reading ability (same blocks: 62.8% frontier vs 33.8% for a 1.7B), the deliberate user-fact-primacy trade (assistant-side knowledge forms weakly because that grammar is what memory poisoning looks like), and consolidation never running inside a benchmark that never idles. Production's LLM summarizer distills instead of truncating, so the published number is a floor for the real write path.
Design in one paragraph
Every exchange becomes an episode (verbatim, SHA-deduped). A write-behind
worker - running only when the GPU is idle - asks qwen3:1.7b for two plain
lines (TITLE: / GIST:; never JSON, with a deterministic extractive fallback)
and embeds the gist (qwen3-embedding, Matryoshka-truncated to 256d). Recall is
hybrid BM25 + dense with admission on raw thresholds (unrelated turns
inject nothing) - exact search below 50k memories, a pure-numpy IVF index past
it (~86ms p95 at 250k traces, still embed-bound) - ranked by relevance + ACT-R activation
B = ln(Σ w·t^-0.5) - frequent-and-recent memories surface first, unused ones
sink, and injection itself reinforces at only 0.15 weight (capped per session)
so the rich-get-richer loop stays closed. On big-budget tiers each rendered
gist quotes cue-relevant verbatim sentences from its source episode (the gist
indexes, the record testifies), user words only, secrets never travel. Idle-time consolidation maintains a user profile and topic summaries;
silent memories resurrect on strong cues. Injection is budgeted for small
models: ≤2 gists + 2 titles, ≤350 tokens, placed first in the system message.
Failures fail open: a slow or broken pipeline degrades to a plain proxy.
Full architecture spec: see the published design document.
For coding agents
The repo ships an agent-consumable skill at skills/engram-integration/SKILL.md
(standard SKILL.md format: Claude Code, OpenClaw, and Hermes all read it).
Point your agent at it, or just say "integrate engram into my setup", and it
walks the full recipe: preflight, install, per-client wiring, verification,
and the gotchas that cost us real debugging time.
Cloud models
Engram can give a cloud model the same memory, with background work staying local and free:
# route Anthropic-protocol traffic (/v1/messages) to a cloud endpoint,
# or OpenAI-protocol traffic (/v1/chat/completions) to any OpenAI-compatible
# provider; whichever you skip keeps hitting local Ollama
ENGRAM_ANTHROPIC_UPSTREAM=https://api.minimax.io engram up
ENGRAM_OPENAI_UPSTREAM=https://openrouter.ai/api engram up
# point your client's base_url at Engram:
# Anthropic protocol (e.g. Hermes + MiniMax): MINIMAX_BASE_URL=http://127.0.0.1:11435/anthropic
# OpenAI protocol (OpenRouter, DeepSeek, ...): base_url=http://127.0.0.1:11435/v1
Auth headers pass through untouched; embeddings and summarization always run
on local Ollama (ENGRAM_SERVICES, default http://127.0.0.1:11434), so the
only added cloud cost is the injected block itself (≤350 tokens, ~1% of a
typical agent prompt). Big-prompt injection rides the newest message, so
provider prompt caches - and their cache-read discounts - survive.
Endpoints
POST /api/chat- Ollama-native NDJSON, memory-enabledPOST /v1/chat/completions- OpenAI-compatible SSE, memory-enabled (local or cloud upstream)POST [/prefix]/v1/messages- Anthropic Messages (local or cloud upstream), memory-enabled- everything else (
/api/generate,/api/tags,/api/show,/v1/models, embeddings) - byte passthrough GET /engram/stats,GET /engram/why- local introspection- header
X-Engram: offbypasses memory per-request;X-Engram-Sessionpins session identity
Metadata
Release files for engram-proxy 0.7.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| engram_proxy-0.7.0.tar.gz | 90.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| engram_proxy-0.7.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 173.8 kB
Release files / engram_proxy-0.7.0.tar.gz
| Download URL | engram_proxy-0.7.0.tar.gz |
|---|---|
| Size | 90.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
afa7224776a570cd11193164d74a3d28587482895d2aea41a00391ccc939abb6
|
|
BLAKE2b-256 checksum How to use checksums |
62a6d831861eb4c76fc5d8d598f1efb3cb4cd0f581a850e864054468fcfe5215
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.7
|
Release files / engram_proxy-0.7.0-py3-none-any.whl
| Download URL | engram_proxy-0.7.0-py3-none-any.whl |
|---|---|
| Size | 83.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
10c0e488ba3ae0c3313446b71294f9bb1e916c81ee3f57aac9e701b2d09925d4
|
|
BLAKE2b-256 checksum How to use checksums |
79223b8fb6c67daf065727854950b59bccdc02a361cf9318ab4dc7657e3d0771
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.7
|