Implementation
Last verified against the code: 2026-10-21 (first commit)
How each goal in GOALS.md is built, and the gaps.
Goal 1: read the raw stream
transcripts.session_files() lists *.jsonl directly under each project folder of CLAUDE_PROJECTS_DIR.
transcripts.parse() reads a file line by line: only user and assistant records with isSidechain false are
conversation. User records: isMeta lines and tag-wrapped command records (TAG) are dropped; tool_result blocks
are attached to the last assistant turn (Turn.errors when is_error). Assistant records: consecutive records are
one turn (text joined, tool calls appended), because the model streams text and tool calls as separate lines.
ingest.ingest() skips a session whose mtime the sessions table already has, otherwise replaces its episodes.
Gap: only Claude Code's format. Other agents (goal 15) need their own reader producing the same Session.
Goal 2: the unit is the surprise
ingest.episodes_of(): one error episode per distinct failed tool result in a turn, titled with the tool, its
description (from the description, file_path, command, … input key) and the first meaningful line of the
failure (transcripts._error_of() turns a bare Exit code N first line into a suffix). One correction episode per
user turn whose opening words match transcripts.CORRECTION, with the agent's preceding statement as detail.
Gaps: corrections are detected by opening words only, so "that's wrong" mid-message is missed and "no problem" is a false positive; the agent's own stated surprises ("that's unexpected") are not yet episodes.
Goal 3: recall
recall.recall() embeds the query (embed.embed(): lowercase words of two or more characters minus stop words,
plus adjacent pairs, hashed with BLAKE2b into 256 signed buckets, unit-normalized), scores every unarchived episode
and every lesson with recall.score(), sorts, returns the top k as dicts and touches the returned episodes
(Store.touch()). --cwd limits episodes to one project.
Gap: a hashed embedder has no notion of synonyms; a learned embedder is the first upgrade (paper §7.6).
Goal 4: sleep
sleep.sleep() runs decay(), consolidate(), verify() and predict() in that order and returns a report dict;
sleep.morning_text() renders it. consolidate() first attaches each uncovered surprise to the nearest existing
lesson above SAME_SURPRISE, then greedily clusters what is left by kind. verify() compares each open
prediction's embedding with the episodes created after it was made. predict() embeds the lesson text itself as
the prediction's embedding, so a repeat of the same surprise confirms it.
Gaps: thresholds are untuned first guesses; clustering is greedy and order-dependent; lessons never merge with each other once created.
Goal 5: observations, never instructions
Model-written lesson text goes through sleep._as_observation(), which prefixes "Observed:" to text that opens
with an imperative (FORBIDDEN_STARTS). The model prompt (LESSON_SYSTEM) asks for an observation under 30
words. Deterministic lesson texts are of the form "Seen N×: <title>" or "The user corrected the agent: <words>".
Gap: the check is on the opening words only.
Goal 6: have it in mind
render.render() writes om-<id>.md per lesson, with the frontmatter Claude Code's memory files use (name,
description, metadata.type of feedback for corrections and project otherwise, plus managed: openmemory,
seen, strength, updated), and one section of MEMORY.md between <!-- openmemory:start --> and
<!-- openmemory:end -->. The CLI renders into every project (cwd) that has episodes.
Gap: this is interpreted memory read at session start; compiled memory is goal 12.
Goal 7: see and veto
morning.md is written by openmemory sleep and shown by openmemory morning. render.render() keeps
written.json; a lesson in it whose file is gone is added to vetoed.json and skipped from then on.
Goal 8: recall mid-task
mcp.serve() reads newline-delimited JSON-RPC from stdin. initialize answers protocol 2024-11-05 with a
tools capability; tools/list returns TOOLS; tools/call runs recall (formatted text) or remember (a note
episode with session_id mcp). Unknown methods and tools are -32601; any other exception is -32000.
Goal 9: no model, better with one
llm.model() returns the configured model or the first one GET /models lists, cached per process, None when
OPENMEMORY_LLM=0 or nothing answers within two seconds. llm.complete() posts a chat completion and returns
None on any failure. Callers (ingest.episodes_of(), sleep.lesson_text()) use the deterministic text then.
Goals 10 to 15
Not started. The store already records what goal 10 needs: surprises per session (episodes by session_id and
kind), prediction outcomes (predictions.outcome), and repeats (lessons.seen). See ROADMAP.md.
Tests
tests/test_openmemory.py builds a transcript in a temporary project folder (an install that fails, a correction
to use uv, a success), plus a subagent transcript that must be skipped, and runs: parsing (turns, tool pairing,
error text, correction), ingest (three episodes; idempotent), recall, sleep (one lesson from the correction, none
from the single error; a prediction), render (files, the managed section, the veto), a second session the next day
(the prediction confirmed; the repeated error now a lesson), and the MCP server as a subprocess.