Skip to content

Recall & capture ​

The loop Engram runs for your assistant: start every session briefed, recall on demand mid-session, capture decisions silently as they happen, and return a verdict — not a receipt — for every write.

The session brief ​

A session-start hook injects a compact digest of the graph's canon — suspected conflicts to judge, open problems and intents, principles, decisions, cautions, and the tag vocabulary — so the assistant doesn't start cold and doesn't have to remember to ask. Settings → Get brief shows you the same text the assistant receives, for the project the pane is bound to:

The Memory Lens panel rendering a session brief: working version, tag vocabulary, unresolved and suspected conflicts, and open work

A real (trimmed) brief from this repository:

markdown
# Engram brief
Recent tags (reuse before inventing new ones): phase-2, hooks, pane-crud, retrieval, tags

## Suspected conflicts — judge these
- "New pane drawers must go through SidePanel — a single-element scrolling drawer
  rubber-bands its content out of the frame on macOS" [Caution] vs "Pane side drawers
  unified into SidePanel.vue: one drawer per side, drag-resizable widths" [Decision] (88% similar)

## Open problems & intents
- Session quarantine: exclude a session's writes from retrieval without deleting them [Intent open]
- Phase 2: mid-session conflict push over MCP [Intent open]

## Cautions
- engram-alpha serve must start in the repo root — a relative --db silently creates a fresh DB anywhere else

You can read the same brief anytime in the pane (Memory Lens), or print it with engram-alpha brief. When your projects share a home graph, its canon rides along in every brief. A note the last session tagged handoff gets guaranteed top billing — the handoff mechanism for "read this first".

Tasks: where the last session stopped ​

Notes are what the project knows; tasks are what the assistant is doing. They live beside the graph, not in it: search never returns a task, the conflict checks never read one, and they never count as knowledge. The brief lists the active tasks right after the current cycle, titles only:

markdown
## Tasks
- Ship tasks in 0.10.0 [Task 00eqqfzu1ntm] — 2/3 done, next: Dogfood tasks on this repo [Task 00eqqfzw1ntm]
- Re-measure history endpoints after the next deploy [Task 00er4pd59ec0]
Parked: 0 delayed, 1 backlog — `list_tasks` with status reads them.
Recently done: Pane speed and screen transitions; Improve tasks ui

A group shows as one line with its progress and next step. A task carried over from an older version is marked ↻ from <version>, and one nobody touched for five sessions is marked idle, so the list gets finished, parked or dropped instead of rotting.

The assistant works the list with three MCP tools:

  • add_tasks opens one or more tasks, with a group (parent), a markdown plan, refs to notes and rules. A title that matches an open task comes back as matched, so the assistant updates that one instead.
  • update_task moves one along: progress appends a dated line where it stopped, status closes it (done, dropped) or parks it (delayed, backlog). Closing a task that settled something nudges the assistant to capture that as a note and add it to the task's refs.
  • list_tasks reads bodies and filters by status, group or ref; the brief carries titles only.

The skills teach the split: an Intent records a plan worth remembering ("we will do X, because Y"); the tasks track the doing and point at it. One task per piece of work, with steps as a checklist in its plan, and only for work that spans sessions or that you asked to plan. Rules close or wake a task by themselves at a version, on a date or when a note is resolved, and set_version reports what its version change closed or woke. You steer the same list on the pane's Tasks screen, and what you set there stands. If a graph has tasks turned off, the tools say so and the assistant works without a list.

Search that carries its context ​

Mid-session recall is hybrid: keyword search and local vector search fused, sharpened by a local cross-encoder reranker, ranked by relevance × trust. Vector search works at two depths: every node has a whole-node vector, and rich bodies additionally get one vector per sentence-sized claim — so a query matching one point buried in a detailed decision still finds it, and compact, information-dense nodes cost nothing in reachability. One search action covers all of it: keywords, tags, code refs, node vectors, claim vectors. Every hit carries its 1-hop neighbors — conflicts and supersessions first — so contradicting a standing decision is hard to do by accident: the contradiction arrives attached to the search result that would have caused it.

Every hit also carries a standing, which is the graph's own verdict on how far it can be relied on. It is built from what the graph already records, and the first that applies wins:

StandingMeaning
tombstoneA record that this knowledge was deliberately removed
supersededA live newer note replaces it; the hit names that note
contestedIt is in an open, judged conflict; the hit names the other side
staleIts trust fell below the stale line
canonYou pinned it, approved it, or wrote it
confirmedSomeone confirmed it is still true
unverifiedAn assistant wrote it and nobody has confirmed it yet

The capture skill teaches the assistant to weigh a hit by its standing, not just its rank: to tell you about a contested hit, and to treat an unverified one as a lead rather than a fact. The pane shows you the same verdict: a chip on search results that need a second look (canon, superseded, contested, stale, tombstone), and a standing line in the node drawer that links to the replacing or conflicting note.

Since 0.8.0 the result set is also calibrated. Hits scoring under a benchmark-measured delivery floor are trimmed before they reach the assistant — the floor sits at the top of a measured "free zone", so recall is untouched while the weak tail stops spending the model's attention (the effect is strongest on young graphs, where the tail is weakest). And every search reply carries a confidence verdict: strong means the top hit cleared the calibrated line; weak tells the assistant these may be adjacent topics rather than the answer — verify before relying; none says the graph is silent, and to say so rather than invent a memory. Both numbers came out of the evaluation harness's floor sweep, and both are per-graph policy (delivery_floor, weak_evidence_top) if yours needs different ones.

Search also speaks time (0.8.7): after/before take days, ISO instants, or relative phrases the daemon resolves ("last week", "2 hours ago"), during_version scopes to when a recorded working version was current, and order re-reads the delivered set chronologically or newest-first. By default the window reads created_at — when the knowledge was captured. Since 0.9.0 a graph that declares date-kind custom fields can aim the same window at its event clock instead: date_field: "event_date" filters on the field's value, and a "from..to" pair ("effective_from..effective_to") matches notes whose validity span overlaps the window — bitemporal search for historic imports, where everything was captured today but happened over years.

Since 0.8.10 delivery is also session-diverse: when more strong candidates survive the floor than fit the result list, each additional hit from a session already holding a slot is demoted a few rank positions, so one session's restatements stop crowding out complementary evidence captured in other sessions — the difference between "the answer, said five ways" and "the decision, the caution it earned, and the insight that explains it". The demotion is rank-based (it transfers across graphs where absolute score thresholds don't), changes selection and order only — scores, the floor and the verdict still read the calibrated scale — and is policy. session_diversity_demote if you want it stronger or off.

Writes come back as verdicts ​

Capture is batched and silent — no "I've saved a note!" chatter. But every write the assistant makes is checked in the same turn, and the response is a verdict it must act on:

  • Near-duplicate — the note matches an existing node; the assistant merges into it instead of creating a twin. The special case is the negated duplicate: the "duplicate" actually says the opposite ("use X" vs "don't use X") — flagged distinctly, because blindly merging it would corrupt the canon; the right move is a conflicts-with edge.
  • Warning — the note landed near conflicted, superseded, or tombstoned knowledge. For the first two the assistant checks the canon before proceeding. Tombstoned is the stricter one: a person deliberately removed this, the warning carries the Tombstone's reason, and the assistant is told not to re-add or re-derive it — superseded means "a successor exists, follow it"; tombstoned means "re-adding this is the error". The write itself is never blocked: models nominate, people judge.
  • Suspects — the write queued new look-alike pairs; the assistant judges them immediately (see Conflicts & Checkup), and a genuine contradiction is the one thing it surfaces to you out loud.
  • Canon check — a local model reads the new note against nearby knowledge: supports (the canon already backs this — link it rather than restate it) or contradicts (existing knowledge disputes it — read before proceeding). Zero tokens, and a nomination like every other check — nothing moves on its own.
  • Missing code refs — paths that don't resolve in the repository, caught at write time instead of at the next drift scan.

Secrets never land in the graph ​

Every write passes a redaction step inside the store before anything is saved. This covers assistant writes, pane edits, imports and history transcripts, so no surface can skip it. The secret is replaced with [REDACTED] and the original is kept nowhere. The step has two layers:

  • Named patterns catch the shapes secrets actually take:
    • PEM private-key blocks
    • AWS access key ids
    • JWTs
    • GitHub, Slack and OpenAI-style tokens
    • credentials inside a URL (scheme://user:pass@host)
    • password: …, token = …, api_key: … and similar assignments. Here the value is masked and the key kept, so the note still reads.
  • An entropy backstop for opaque tokens with no recognisable prefix: long runs of random-looking letters and digits. It judges each -/_// separated part on its own, so a model slug like cross-encoder/nli-deberta-v3-small or a target triple survives.

What it covers: note titles, bodies, and string values in custom fields. Tags, code refs and edge notes are not scrubbed, so keep secrets out of them.

To check it yourself, create a note in the pane whose body contains a made-up key, such as password: hunter2-not-real or AKIAABCDEFGHIJKLMNOP. The saved card shows [REDACTED]. The patterns live in crates/engram-core/src/redact.rs, with their tests beside them.

Redaction is a backstop. The capture skill tells assistants never to store secrets in the first place. The full security posture is in SECURITY.md: what is encrypted at rest, the history layer's cipher, and the known gaps.

Memory that tracks the code ​

Nodes can point at code (code_refs). When the code moves, the memory doesn't get to pretend otherwise: drift scans check every path-shaped ref against the repository, and a ref that no longer resolves badges its node as drifted — in the pane, in the health strip, and in the assistant's list_drift worklist. The contract is repair-or-retire: fix the path if the knowledge still holds, supersede it if the refactor invalidated it.

Code refs also work in the other direction. An optional file-read hook (wired by engram-alpha setup, and in the Claude Code plugin) watches which files the assistant reads or edits, and when one is covered by a node's code_refs it quietly attaches that memory to the session — so a caution about a file arrives because you touched the file, no matter what anyone searched for. The noise controls live server-side: only current, non-stale nodes, ranked by trust, capped per read, and never the same node twice in one session.

Subagents ​

Subagents share the MCP connection: they can search and read the brief, but they start cold (no injected context) and their writes attribute to the parent session. Recall flows down through the prompts you give them; capture flows back up through their findings.