Engram — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Engram (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
100% coded by AI (Claude) · from human inspiration (David Dand)
⚠️ Experimental — early days. Engram works and is tested, but it has not been proven in long, real-world use. The features that make it useful (persistent, semantic, structured, attachable) are solid; the features that make it distinctive (memory that improves with use and forgets cleanly over time) are a bet we're still testing — see Status and the roadmap. Run it, kick the tyres, tell us what breaks — but don't trust it with anything you can't afford to have mis-remembered yet.
Engram is an open-source memory brain for AI agents — not another vector store. You run it as a container, and your agent attaches to it as its persistent memory. It's built on a few ideas most "memory" libraries don't have:
It ships as two components:
| Component | What it does |
|---|---|
| Model Seed | Folds structured knowledge about any entity into a grounded, source-cited object an agent can consume |
| Path Memory | A self-organising memory layer where connection weights emerge from use, not design |
Together they form a knowledge system that learns what matters by watching what gets used.
The objective: give an AI agent a persistent, self-organising memory that lives outside the model — so it remembers across sessions, gets better at recall the more it's used, and can surface the right memory from many angles, not just the exact words it was stored under.
How an agent uses it — three verbs:
A note on cost: generating the fan-out perspectives means eachMemory.save()makes a few small LLM calls (the lenses + classification). That's on by default because it's what makes recall feel uncanny — but for bulk imports or cost-sensitive use, passperspectives=Falseto save the literal memory only.
The attach point is an MCP endpoint (SSE transport, for the widest client compatibility): spin up the container and point any MCP-capable agent (OpenClaw, Claude Desktop, your own) at http://<host>:8080/sse — it has a brain, no glue code. The server exposes six tools — remember, recall, recall_with_associations, supersede, remember_json (fold a whole JSON blob into recallable memories, one per leaf), and recall_json (reassemble a folded blob back into one object) — backed by everything above. Run one brain per agent, or share one across a fleet. (You can also drive it directly via the Python API / CLI, shown below.)
Attaching the endpoint makes the tools available — but a model won't reflexively reach for memory unless you tell it to. This is the single most important integration step: add a short directive to your agent's system prompt / persona, so recall-and-remember becomes a habit rather than something it only does when asked:
You have a persistent memory (engram). Before you answer, `recall` relevant context. When you learn something worth keeping — a fact, a decision, a preference — `remember` it. Scope memories to the current project where that helps.
That one instruction is the difference between "the brain is plugged in" and "the agent thinks with it." The tool descriptions steer how to call each tool; this steers when. (In OpenClaw, add it to the agent's persona; in Claude Desktop, to the system prompt; in your own agent, to its system message.)
A complete, ready-to-paste version lives in [`AGENT_PROMPT.md`](AGENT_PROMPT.md) — copy it straight into your agent's persona and adjust the voice to taste. It covers every tool (recall-first, remember-what-matters, project scoping, JSON folding, supersede) plus the creativity and collapse recall options.
With engram running (so http://localhost:8080/sse is live), point OpenClaw at it:
# 1. Register engram as an MCP server (OpenClaw infers SSE from the /sse URL)
openclaw mcp set engram '{"url":"http://localhost:8080/sse"}'
# 2. Confirm OpenClaw connects and can see the tools
openclaw mcp probe engram # -> "engram: 6 tools"Then make the agent actually use the brain — in OpenClaw the persona is the workspace AGENTS.md file. Paste the block from AGENT_PROMPT.md into ~/.openclaw/workspace/AGENTS.md, then reload:
openclaw mcp reload # use the new config on the next turnThat's it — your OpenClaw agent now recalls and remembers through engram. (Verified on OpenClaw 2026.6.10.)
You can give engram JSON and read it back, two ways depending on what you want:
remember_json folds a JSON blob into one memory perleaf, keyed by its path (business.hours.sat). Then recall finds the right pieces by meaning — "what are the weekend hours?" surfaces the sat leaf even with no shared keywords. Use this when you want to ask questions about the data.
# agent side (MCP): remember_json('{"business":{"hours":{"sat":"10am-4pm"}}}', project="acme")
# query a piece: recall("weekend opening time", project="acme") -> business.hours.sat: 10am-4pm
# get it all back: recall_json(project="acme") -> {"business": {"hours": {"sat": "10am-4pm"}}}recall_json is the inverse of remember_json — it gathers the folded leaves for a scope and reassembles the whole object, with types intact (strings, numbers, bools, null, nested lists). So folding is symmetric: blob in, blob out.
searchable piece-by-piece, just store the JSON as a memory body and recall hands it back intact:
# remember(subject="customer 4821 record", body="<the JSON string>", project="acme")
# recall("customer 4821") -> body is the JSON, returned wholeThe first makes the data searchable by meaning and reassemblable as a whole; the second is the quick path when you only ever want the object back as a unit. Use whichever the task needs.
recall takes an optional `creativity` (0–1). At 0 you get the precise best matches. Turn it up and engram swaps a growing share of the tail of the results for near-miss memories — semantically adjacent, but not the obvious answer — to nudge the agent toward a connection the literal query would never surface. It always keeps the real top hit; it just mixes in sparks.
recall("how do we keep users logged in", creativity=0) # -> the auth decision
recall("how do we keep users logged in", creativity=0.8) # -> auth decision + the founder, the office, the caching debateThe point is the painter's happy accident, or a useful mutation: a small, aimed detour is where inspiration comes from. So the noise isn't random — random is just irrelevant — it's the adjacent possible, drawn from the second ring of the embedding space. Sparks are flagged serendipity=true, so the agent treats them as prompts rather than facts, and they never strengthen the use-built graph. Almost every memory system chases precision; this is the dial for the opposite — and it's grounded in how minds actually generate ideas (divergent thinking, incubation, the way dreams recombine distant memories).
recall takes an optional `collapse` (default off). Normally a query returns a fixed limit of results — so a question with only three real answers comes back padded with two weak ones, and the agent can't tell which is which. Turn collapse on and engram stops padding: it looks at how relevance falls off across the candidates, finds the natural cliff, and returns only what sits above it.
recall("how do I pull a good espresso shot", limit=5) # -> 5 rows: 3 real + 2 noise
recall("how do I pull a good espresso shot", limit=5, collapse=True) # -> 3 rows: just the real answersThe idea: raw similarity is a blur — near-but-wrong memories look just like genuinely-relevant ones. Engram resolves that blur into keep vs drop by mixing in everything it knows from use (edge strength, recency, supersession), ranking by the result, and then cutting where relevance falls off a cliff. If nothing falls off — it's all relevant — you simply get the top limit back, so it's always safe to leave on. limit becomes a ceiling, not a quota.
Engram is 100% coded by AI (Claude) — every line of it — from human inspiration and direction (David Dand). The human brought the vision and the ideas; the AI wrote the code. Two sources shaped that vision:
Engram is one self-contained container — Postgres+pgvector, the app, and the schema baked into a single image. You bring your own OPENAI_API_KEY and ANTHROPIC_API_KEY (used for embeddings, classification, and lens generation); the container never ships with keys.
Run the pre-built image (available once a release is published to GHCR):
cp .env.example .env # add your OPENAI_API_KEY and ANTHROPIC_API_KEY
docker run -p 8080:8080 -v engram_pgdata:/var/lib/postgresql/data --env-file .env ghcr.io/gsn2dd/engram-p 8080:8080 publishes the MCP endpoint (attach agents at http://localhost:8080/sse); -v engram_pgdata:… keeps the brain across restarts.
Or build from source (for development / contributing):
git clone https://github.com/gsn2dd/engram
cd engram
cp .env.example .env # fill in your OPENAI_API_KEY and ANTHROPIC_API_KEY
docker compose upThat's it — Postgres initialises with the schema on first boot, and the brain is live inside the container. On first boot it also seeds a small demo brain (a fictional startup's notes) so recall works immediately — try:
docker compose exec engram python3 cli/pm.py recall "how do we keep users logged in"
# -> surfaces the JWT auth decision, with no words in commonSet ENGRAM_SEED_DEMO=0 for a clean, empty brain in a real deployment.
Talk to it with the CLI / Python library against the running container:
docker compose exec engram python3 cli/pm.py save "Auth decision" "We use short-lived JWTs." --person my-project
docker compose exec engram python3 cli/pm.py recall "how do we keep users logged in"Most AI memory systems are retrieval-only. You put things in, you get things out. The weights are fixed.
Engram is different: the observer changes the memory.
JSON Layer 1: [A] ----w=0.8---- [B]
| |
w=0.1 w=0.6
| |
JSON Layer 2: [C] ----w=0.0---- [D]
|
w=0.9
|
JSON Layer 3: [E]Every retrieval that passes through a path strengthens it. Every path never taken fades. The system trains itself through use — there is no separate training phase.
The JSON structure defines the possible paths. Retrieval history decides which ones survive.
A model seed is a structured knowledge object — not a document, not a scraped summary. It contains:
The seed is the 2D projection of a multi-dimensional knowledge object. The JSON is the shadow. The real structure lives in the embedding space.
Nested JSON is not flat. Each layer of nesting is a layer of the knowledge structure:
Layer 1 (top-level keys) → embed each element → 768D vector
Layer 2 (nested objects) → embed each element → 768D vector
Layer N (leaf values) → embed each element → 768D vectorConnection weight between any two elements across layers:
weight(A → B) = accumulated retrieval signal through A and BThis is Hebbian learning at the data-structure level: connections that fire together wire together. But the training signal is not gradient descent — it is the observer.
When a memory is retrieved:
UPDATE memories SET
access_count = access_count + 1,
weight = weight + (1.0 / (access_count + 2)), # diminishing returns
last_accessed = now()
WHERE id = %s0+0.50+0.33+0.25Every memory is auto-classified on save:
| Type | What it covers |
|---|---|
person | A human being |
place | A geographic location |
project | An initiative, product, codebase, or ongoing work |
thing | An idea, concept, or anything else |
Classification is automatic — the system inspects the entity and content and decides. No manual tagging.
Memories never retrieved age out:
# Decay weight over time for unaccessed memories
UPDATE memories SET weight = weight * 0.95
WHERE last_accessed < now() - interval '7 days'
# Archive if weight falls below threshold
UPDATE memories SET archived = true
WHERE weight < 0.01The system forgets what is not needed. The most-travelled paths become highways. The rest become dirt tracks, and eventually disappear.
This runs on its own. The container executes a consolidation pass on a loop (default hourly — set ENGRAM_CONSOLIDATE_INTERVAL in seconds): it compacts raw co-recall edges into the path graph that spreading-activation reads, decays unused node and edge weights, and archives what's faded. Trigger it by hand any time with pm consolidate. Weights strengthen on every recall regardless, but this pass is what lets the graph reorganise itself over time — so it ships on by default.
Decay answers "how much should we still trust this, given how long it's gone unused." It does not answer "what tense should this be read in, right now." A claim like "next year's Olympics" doesn't go stale because nobody asked about it for a while — it goes stale because the calendar moved past the Games' actual dates, regardless of how often the memory was recalled.
Memories can carry an optional calendar anchor:
Memory.save(
subject="olympics-2028", body="Los Angeles will host the 2028 Summer Olympics...",
person="los-angeles",
temporal_anchor_start="2028-07-14", temporal_anchor_end="2028-07-30",
)recall() re-derives a live temporal_status (upcoming / current / past) against today's date on every call — never frozen at write time — and format_for_prompt() surfaces it as a tense hint so the writer doesn't copy stale wording verbatim once an anchor's status has moved on. Run pm temporal-sweep to list every calendar-anchored memory and its current status — useful before a recycle pass, to catch claims whose frozen prose no longer matches reality.
A path through the JSON layers — activated repeatedly by retrieval — becomes a piece of knowledge in itself. Not the nodes. The route between them.
This is what makes Engram different from a vector database:
| Vector DB | Engram |
|---|---|
| Retrieval finds nearest neighbours | Retrieval strengthens connections |
| Weights are static | Weights emerge from use |
| No forgetting | Temporal decay removes unused paths |
| Flat similarity search | Layered structure with directional paths |
| Training phase separate from operation | Every retrieval is a training step |
CREATE TABLE memories (
id serial PRIMARY KEY,
person text, -- entity: person, place, project name, or NULL
subject text, -- short subject label
body text, -- full content
noun_type text DEFAULT 'thing', -- person | place | project | thing
embedding vector(768), -- semantic position in 768D space
access_count integer DEFAULT 0, -- singleton retrieval counter
weight float DEFAULT 0.0, -- accumulated observer weight
last_accessed timestamptz, -- for aging/decay
archived boolean DEFAULT false, -- soft-deleted memories
created_at timestamptz DEFAULT now()
);
CREATE INDEX ON memories USING hnsw (embedding vector_cosine_ops);
CREATE INDEX ON memories USING gin (person gin_trgm_ops);
CREATE INDEX ON memories USING gin (subject gin_trgm_ops);Requires: PostgreSQL + pgvector extension.
# Find memories by meaning, not keyword
python3 cli/pm.py recall "how do we keep users logged in"
# Filter by entity
python3 cli/pm.py recall "the caching decision" --person my-project
# Filter by noun type
python3 cli/pm.py recall "who knows the deploy process" --noun person
# Limit results
python3 cli/pm.py recall "auth approach" --limit 3Every recall updates the observer weights automatically.
The seed generator turns any structured data source into a grounded intelligence packet deployable to AI systems:
Raw data + local contributions
→ fold (research debt + public contribution engines)
→ weighted knowledge object (the seed)
→ embed → store in the brain
→ self-host, or build your own publishing layer on topSeed consumers (what you can build with a seed):
The seed generator is free and open source. WorldTownGuide is one production deployment built on this engine — a useful reference, not a requirement.
The seed generator and Path Memory layer are free and open source.
Fork it. Run it. Generate seeds for any entity — cities, projects, people, ideas. Build your own knowledge layer. The weights that emerge from your usage are yours.
git clone https://github.com/gsn2dd/engramThe weights that emerge from your usage are yours.
Active development, and genuinely experimental. The core works, but Engram has not yet been tested in long trials. To be straight about where the line falls:
collapse returns only the relevant matches; JSON fold/unfold round-trips; project scoping, supersede, and temporal anchoring all behave as described; it attaches by MCP with no glue code, and it's open and fully inspectable.So the open question we most want help with:
How does memory retention and retrieval hold up over time?
As the graph grows to thousands of memories and the decay-and-strengthening loop runs for weeks and months, does the right memory stay easy to find — and does the unused stuff fade cleanly — or does recall slowly drift? We don't yet know, and that's exactly the thing a real brain has to get right.
What's shipped, and what's next: see CHANGELOG.md for the release history, and ROADMAP.md for the experiments this opens up — most of which only become testable once a brain has aged into a real association graph (e.g. resolve-then-ridge recall, which walks the strongest learned path through the relevant set rather than ranking by similarity alone).
If you run it, we'd genuinely like to hear back: what got harder to find, what surfaced that shouldn't have, how recall felt after a month of real use. That long-horizon feedback is the most valuable thing you can give the project — open an issue and tell us what your brain remembered and what it forgot.
Path Memory layer: operational. Model Seed generator: operational. Open-source packaging: in progress.
Built in the open by David — more at play4gain.com. Inspired by Tony Buzan's work on radiant, associative thinking.
engram is free and always will be. If it's useful to you and you'd like to keep the experiments running, buy me a coffee ☕ — or use the ♡ Sponsor button at the top of the repo.
100% coded by AI, with human inspiration. Conceived and directed by David Dand (gsn2dd); every line of code written by Claude.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.