Skip to main content
Glama
README.md
# engram

**The memory engine that forgets.**

Every agent memory system ever shipped has the same bug: **it never forgets.** They hoard every trace forever, then wonder why context rots, tokens bleed, stale facts override fresh ones, and the "memory" of a 3-month-old agent is a landfill.

The only general intelligence we know of — the human brain — runs on forgetting. You remember the *gist* of ten thousand breakfasts, not the pixels. Forgetting isn't a failure of memory. It's the compression strategy that makes memory work.

`engram` is a long-term memory layer for AI agents built on that principle:

| Mechanism | Borrowed from | What it does |
| --- | --- | --- |
| **Ebbinghaus decay** | Forgetting curve (1885) | Every trace fades unless used: `R(t) = exp(-t / S)` |
| **Spaced reinforcement** | Spacing effect, reconsolidation | Every successful `recall()` strengthens the trace — use it or lose it |
| **Sleep-time consolidation** | Hippocampal replay | `dream()` merges duplicates, distills repeated episodes into semantic gists, rewires associations, dissolves the dead |

Local-first SQLite. Zero required dependencies. MCP-native. One file you own.

```python
from engram import Memory

mem = Memory("./brain.db")

mem.remember("user is allergic to peanuts", salience=0.8)
mem.remember("had pho for breakfast", salience=0.3)

mem.recall("what should I cook?")   # retrieval also REINFORCES the winners
mem.dream()                          # sleep: compress the day, drop the noise
mem.forget(query="breakfast")        # deliberate, immediate erasure
```

Six months later: the allergy is one tool-call away (the agent kept touching it). The breakfasts are gone. Your prompt stays 70 tokens, not 3,000.

---

## Why forgetting is the feature

**The agent tax.** Memory engines that keep everything pay for everything, every turn. A transcript-dumping agent carries months of stale context into every single call. In our 120-day synthetic benchmark, engram's per-query context cost is **2.3%** of the full transcript — with 8–10/10 exam hit-rate on facts the agent actually used.

**Context rot.** A memory store that never forgets degrades: duplicates accumulate, old states shadow new ones ("user lives in Hanoi" *and* "user lives in Saigon"), and retrieval quality decays as the index fills with dead weight. Forgetting is garbage collection for cognition.

**Use it or lose it.** Memories an agent keeps retrieving get exponentially more durable (stability compounds per recall — from ~4 days at birth to years after a handful of spaced uses). Memories nobody touches fade on an Ebbinghaus curve and are swept out of the file at the next dream. The memory *becomes what the agent's life actually is* — no manual curation, no TTL configs, no cron jobs to prune the past.

**Privacy that compounds.** Data you stop using doesn't linger: it decays and dissolves. The retention surface of a running agent shrinks by itself. And when the user says *forget it* — `forget()` is immediate, total, row-level deletion. Not "archived." Gone.

## Install

```bash
pip install engram-ai            # core: pure stdlib, no dependencies
pip install "engram-ai[mcp]"     # + the MCP server for Claude/OpenClaw/Cursor/…
```

Python ≥ 3.10. The brain is one SQLite file — back it up, copy it, inspect it with the `sqlite3` CLI, `rm` it.

## The 60-second tour

```python
from engram import Clock, Memory

# a virtual clock lets you time-travel a brain (great for tests & demos)
clock = Clock(virtual=True)
mem = Memory.in_memory(clock=clock)

used   = mem.remember("user is allergic to peanuts", salience=0.8)
unused = mem.remember("printer on floor 3 jams on wednesdays", salience=0.4)

# use the allergy on a spaced schedule; ignore the printer
for gap in (2, 5, 10, 20):
    clock.jump(days=gap)
    mem.recall("peanut allergy", k=1)

clock.jump(days=60)   # two quiet months
print(used.retention(clock.now()))    # 0.97 — solid
print(unused.retention(clock.now()))  # 0.00 — gone
```

Run the full visual version: `python examples/forgetting.py`

```
day   used-memory R        unused-memory R
  0   ############################ 100%    ############################ 100%
 10   #######################       82%    #                              2%
 35   ###########################   95%                                   0%
 60   ###########################   97%                                   0%
```

### dream(): sleep-time consolidation

```python
>>> mem.dream()
dream complete: scanned 10 traces
  merged 2 duplicates
  distilled 1 gists from 3 episodes
  formed 1 new associations
  dissolved 0 traces past the forgetting floor
  new knowledge: had pho for breakfast again
net trace change overnight: -4
```

Ten messy notes from one day become six clean traces: duplicates fused, four breakfast episodes distilled into one semantic gist carrying their combined stability, related facts linked for future spreading activation. No LLM involved — the default dream is deterministic, offline, auditable. (Bring your own model to write prettier gists; the bookkeeping is identical.)

### context(): memory as a prompt block

```python
>>> print(mem.context("what should I know about the user?", k=3))
<memory>
- (2026-10-02) [core] user's name is Hieu
- (2026-10-02) user is allergic to peanuts
- (2026-10-02) user prefers concise bullet-point answers
</memory>
```

Drop it in the system prompt. Fading traces rank lower automatically — a memory's accessibility is part of its retrieval score, just like a tip-of-the-tongue fact that takes a specific cue to surface.

## MCP: memory for any agent

engram ships as a first-class MCP server. One command, any client:

```jsonc
// Claude Code / OpenClaw / Cursor / any MCP client
{
  "mcpServers": {
    "memory": {
      "command": "engram",
      "args": ["serve"]
    }
  }
}
```

The agent gets six tools — `remember`, `recall`, `context`, `forget`, `dream`, `stats` — with docstrings that teach it *when* to use each one (store durable facts, recall at task start, dream when idle, forget on request). Because retrieval reconsolidates, an agent that *uses* its memory automatically maintains it. That's the whole trick.

## CLI

```console
$ engram remember "user's name is Hieu" --kind core --salience 0.9
$ engram recall "who is allergic to peanuts?"
$ engram context "plan a dinner"          # prompt-ready block
$ engram dream
$ engram stats                            # counts, decay state, token story
$ engram forget --query "flight numbers"  # GDPR-grade erasure
$ engram export brain.json                # your memory, your file
```

## Benchmark

Synthetic but honest: an agent lives 120 days, writes 370 memories (10 durable profile facts + daily noise), uses its memory organically ~360 times, sleeps on it, then takes an exam. Deterministic — same seed, same numbers. [`benchmark/run.py`](benchmark/run.py)

```
memories written            370
organic recall calls        358
night: merged 20  gisted 60  dissolved 106
traces in the brain         24 (from 370 written)
profile knowledge alive     8/10 (trace or gist)
most-used fact stability    1826 days (born at ~4)
exam hit-rate@5             8/10
avg context per query       70 tokens
full transcript dump        3078 tokens/query
prompt cost vs transcript   2.3%
```

94% of the noise is compressed away; the used knowledge is all there — as raw traces or distilled gists — for 2.3% of the token cost. Try `--seed 42` for a 10/10 run.

## How it works

```
remember() ──► engram trace ──► SQLite (single file you own)
                 │  content, kind, salience
                 │  stability S          ◄── ratchet: grows on every recall
                 │  retention = e^(−t/S) ◄── Ebbinghaus, computed live
                 ▼
recall() ──►  score = √BM25 · + vec · + Hebbian spread ) × retention^0.35
                 │                     lexical   optional  association
                 └──► winners reconsolidated (S ↑) + co-fired pairs wired (Hebb)

dream() ──►  1. dissolve  (R < 0.02: delete, rewire surviving links)
             2. merge     (near-duplicates fuse, stability fuse)
             3. gist      (3+ similar episodes → one semantic trace)
             4. associate (related traces get weak links)
```

- **Retrieval** is hybrid: BM25 over lightly-stemmed tokens + character trigrams (typo/morphology tolerant, any language incl. CJK), optional vector axis (plug any embedding function; the default is a zero-dependency hashing embedder), and spreading activation through Hebbian links — memories recalled together become associated, so the graph self-organizes into a semantic network with use.
- **No cliff, only a slope**: faded memories are demoted in ranking but never hidden — a specific cue can still rescue a fading trace (that's the reconsolidation rescue), preventing the rich-get-richer death spiral of naive decay systems.
- **Anti-cramming**: recalls spaced a sliver apart barely count; a near-dead trace cannot bank time it wasn't intact. The stability ratchet is honest.
- **Kinds**: `episodic` (events, the default), `semantic` (facts), `core` (identity — near-permanent, exempt from dissolution).
- **Time is injectable**: `Clock(virtual=True)` makes every decay decision deterministic and testable. This is why the whole system is testable — the test suite time-travels a decade in milliseconds.

## Design principles

1. **Local-first, forever.** One SQLite file. No server required, no cloud, no telemetry. Your agent's memory is *yours* — exportable, inspectable, deletable.
2. **Zero required dependencies.** The core is pure stdlib. The default embedder is deterministic feature hashing — no model downloads, works offline, any language. Want real embeddings? `Memory(embedder=my_fn)`. Want a server? `Memory` behind FastAPI is 20 lines (see roadmap).
3. **Honest no-LLM defaults.** `dream()` distills gists with deterministic clustering, not a hidden API call. Everything is auditable. Swap in an LLM for prettier gists when you want — it's a hook, not a dependency.
4. **Forgetting is policy, not loss.** Decay, dissolution and erasure are explicit, inspectable mechanisms — with `stats()` reporting exactly what the mind holds, what faded, and what the night swept away.

## Compared to other agent memory engines

| | keeps everything | forgetting model | sleep consolidation | deps | storage |
| --- | --- | --- | --- | --- | --- |
| **engram** | no — use it or lose it | Ebbinghaus + spaced reinforcement | `dream()` built-in | **0** | SQLite file |
| vector-dump engines | yes | none | — | heavy | server + vector DB |
| extractive memory (Mem0-style) | mostly | manual TTL/pruning | background extraction (LLM) | LLM required | service |
| graph memory (Zep/Letta-style) | yes | none | incremental graph updates | heavy | graph DB/service |

Those projects solve retrieval at scale; engram solves the *lifecycle* of memory. They compose well — plug a real embedder into engram, or run engram beside a big store as the decay-and-consolidation layer. We think forgetting is the missing half of every one of them.

## Roadmap

- [ ] `pip install engram-ai[server]` — FastAPI/REST wrapper with multi-agent brains
- [ ] LLM-backed gists as an optional `DreamConfig(summarizer=...)` hook
- [ ] `review()` — retrieval-practice scheduler ("what's about to fade?") for agents that want to actively study their own memory
- [ ] sqlite-vec / FTS5 accelerators for very large brains
- [ ] WASM build for in-browser agents

## Development

```bash
pip install -e .[dev]
pytest -q                  # 80+ tests, all time-travelling
ruff check .
python benchmark/run.py    # deterministic benchmark
```

## License

MIT — © [HalisAI](https://github.com/HalisAI). Toward AGI, one forgetting curve at a time.

> *engram* (n.) — the physical trace a memory leaves in a brain. Coined by Richard Semon, 1904.

Maintenance

ActivityMaintained
ResponsivenessNo issues