Captain Memo

What happens while you're not looking

Dreaming

Every day you work, the ship's log gets longer. Dreaming is the pass that reads what piled up, folds what belongs together into one theme, and leaves the originals archived underneath. It runs on your machine — and it folds memories from every assistant you use, not one at a time.

Shipping · consolidation runs on its own · latest release · dream --dry-run stays a read-only explorer
A ship captain's night cabin — the log book closed beside a brass lamp and compass, its memories rising as golden filaments that gather into three glowing constellation rosettes beneath a crescent moon

What ships today

Four passes, all on by default, all reversible, none deleting.

Two of the passes run hourly. Dedup folds near-duplicate observations where title similarity and an embedding confirm both hold. Supersede demotes an older version-fact by 0.5x when a newer one exists, a demotion you can undo rather than an archive. The other two are idle-time passes, and captain-memo stats lists all four under Housekeeping.

The idle-time passes run while your machine is idle: no work queued, no assistant mid-session, and nothing written or recalled for thirty minutes. A busy week simply defers it. A pass that found nothing to change is skipped until an observation is written or surfaced or a vector is written; captain-memo consolidate always runs one, and a restart runs each pass once.

Folding collapses restatements written inside a single session — one event the summarizer described twice, in different words. The survivor keeps the counts; the restatement is archived into it. Folding calls no model.

Every fold passes a guard on the titles first. It vetoes a pair when exactly one title states an absence or a failure, when both name dotted versions and not the same ones (v1.30 and 1.30 are the same, v1.30 and v1.32 are not), when both carry identifiers such as paths or letters-and-digits codes and share none, or when each states a number the other lacks. A clock time such as 14:56 is not read as a number, a date is, and a rewording that keeps the same figures still folds. The guard is a heuristic on titles alone: two codes such as p95 and p99 still fold when the titles share another identifier. The worker picks up a changed guard at its next restart.

Themes handle the other case: the same standing fact learned again in a different session, weeks later. Folding those would erase the evidence that the knowledge failed to stick, so instead one written theme states the durable fact and the originals are archived beneath it, listed and restorable. Writing the theme is the one step that calls a model: for each candidate group (a bounded number per pass) the pass makes one call to the summarizer you chose, carrying the dates, types and titles of that group's observations, and the summarizer may decline. With no summarizer configured, no themes are written.

A theme only forms where both signals agree: the observations read alike, and you have actually pulled them up together. Similarity alone would group everything that shares a vocabulary, which on a real corpus is most of a project. The recall evidence is what separates one topic from one word-list — and it comes from the audit log your own searches have been writing all along.

Everything stays inside one project and branch, nothing you drilled into or anchored is ever touched, and nothing is deleted. captain-memo theme undo <id> puts a theme's members back, and captain-memo dedup --undo reverses every fold. When a sweep got a few wrong, name them: --member ID..., --run N (an id from the read-only captain-memo dedup --runs), or --since with an optional --job dedup|semantic; selectors combine. A selector prints what it will reverse and then does it, with no prompt, and repeating one is a no-op. Needs Captain Memo 0.57.9 or later.

An undone fold stays undone. Every undo, plain --undo included, marks the pair it reversed, and no later pass folds that pair again until captain-memo dedup --allow-refold lifts the mark (captain-memo dedup --refold-blocks lists the marks, read-only). A pair is marked, not a row: a member that is also a near-duplicate of a third row can still fold. Needs Captain Memo 0.57.10 or later; --refold-blocks needs 0.57.11 or later.

The back catalogue. Consolidation normally works on what actually reaches you — an observation that has never once been recalled is not crowding your context, so it is left where it is. On a corpus with years behind it that turns out to be most of the collection, and the repetition in there is just as real. --backlog is a one-off sweep that drops the gate and takes the whole corpus in one pass; afterwards new repetition only accrues as fast as you make it: captain-memo consolidate --semantic --backlog --for 30m, and the same with --themes.


The problem

A log that only grows stops being a log.

Memory systems are graded on what they remember. They should be graded on what they can still find.

Month one

You fix a webhook bug. Three observations get written. You search "webhook" and get exactly those three. The system looks perfect.

Month nine

You've fixed webhooks eleven times. You search "webhook" and get forty rows — eight of which are the same conclusion, written eight different ways, by four different assistants, none aware of the others. The answer is in there. So is everything else.

Deleting is not the fix

The obvious answer — expire old memories, cap the corpus, drop the least-used rows — throws away the thing you kept them for. The observation you touch once a year is often the most expensive one to re-derive. Dreaming does the opposite: it makes a summary the thing search hits first, and moves the detail out of the way without destroying it.

Scattered star-nodes on the left drawn along golden threads into three burning rosette constellations on the right; below them the same rosettes appear again as colourless impressions embossed into the paper — the originals archived, not erased

The originals do not vanish when a theme forms. They are pressed into the record underneath it — dimmed, out of the way of search, and still there when you need the detail back.

The mechanic

Fold the group, archive the parts.

One pass, four steps: read the co-retrieval evidence, find which observations keep getting recalled together, collapse each group into a theme, archive the members inside it.

How dreaming folds memories from several AI assistants into one theme Scattered dots represent observations written by different AI assistants. Lines appear between pairs that get recalled together. Each connected group collapses into a single theme ring, with the original observations dimmed but preserved inside it. Three unconnected observations stay where they are. theme · 6 archived theme · 5 archived
Claude Code Codex Cursor Gemini CLI Lines are co-retrievals — thicker means recalled together more often. The three loners are DBSCAN noise: below --min-pts, deliberately left alone.

The signal

Grouped by what you recall together — not by what shares words.

This is the part that makes dreaming worth doing, and it is the part most systems get wrong. Cluster on text similarity and you get groups that use the same vocabulary. That is not the same as groups that are about the same thing.

Similarity clustering versus co-retrieval clustering on the same six observations On the left, clustering by word overlap groups three observations that all mention the word cache but solve unrelated problems, and misses two that describe the same bug in different words. On the right, clustering by co-retrieval groups the observations that are actually recalled together, regardless of wording. GROUPED BY WORDS "cache" appears in all three CDN cache purge needs the zone id opcache stale until boot.php touched Smarty cache self-heals, don't rm it left out — no shared vocabulary: webhook returns 400 on live keys signature check reads parsed body GROUPED BY CO-RETRIEVAL surfaced together 9 of 12 times webhook returns 400 on live keys signature check reads parsed body THEME Webhook signature checks must use the raw request body, not the parsed one
Same six observations, two grouping signals. The word-based grouping is confidently wrong three times and misses the one pairing that mattered.
The explorer never reads the text

Worth being precise about, because it sounds like an omission and is actually the point: the dream --dry-run explorer scores pairs on co-retrieval and recency only. The explorer leaves the semantic layer out, so what it shows you is the co-retrieval signal on its own. A pass that never looks at the words cannot produce a group that merely shares vocabulary. The shipped consolidation pass adds a cosine confirm on top, so a theme forms only where the observations read alike and you have pulled them up together.


Worked cases

Three groups a similarity search would not have made.

Illustrative examples, in the shape the dry-run reports them: the members that came in, the theme that would come out, and why the grouping happened.

CASE 01 The same bug in four vocabularies

Over nine days you hit one root cause — a payment webhook rejecting live traffic — from four directions, in three different tools. No two observations share a distinctive word. Every time you searched, they came back together.

  • webhook returns 400 on live keys only
  • signature check reads the parsed body
  • express.json() runs before the route
  • test mode passes because secret differs
Theme

Webhook signature verification must run on the raw request body — mount the raw parser before the JSON one, or live keys fail while test keys pass.

Why it grouped: co-retrieval 0.60 (surfaced together 9 of 15 combined surfacings), created within 3 days. Word overlap between members: essentially zero. A similarity clusterer had no way to see this group.

CASE 02 Two assistants, one conclusion

You debugged a deploy problem in Claude Code on Monday and re-hit it in Codex on Thursday, in a different editor, with no shared session. Both wrote observations. Neither knew the other existed — but your searches kept pulling both.

  • CSS deployed but browser shows old file
  • @import version pin inside components file
  • curl returns new, browser returns stale
Theme

A CSS deploy needs three version bumps, not one — outer link tag, inner @import, and the global deploy version. curl hitting the inner URL proves nothing about what the browser fetched.

Why it matters: the theme is built from evidence no single assistant had. Codex contributed the middle observation; the retrievals that bound it to the other two came from Claude Code sessions. Neither tool could have written this theme alone.

CASE 03 The group that was correctly left alone

Three observations from the same afternoon, all about the same file, all sharing vocabulary. A similarity clusterer folds them instantly. Dreaming does not — and it is right not to.

  • invoice total rounds at line 412
  • invoice PDF margin is 14mm
  • invoice list query needs an index
No theme

Reported as noise. Three facts that share a filename and a Tuesday, and nothing else. Folding them would produce a summary no one could ever use.

Why it didn't group: they were never recalled together. Same file, same day, same words — but in nine months of retrievals, no query ever wanted two of them at once. That absence is a signal, and only a co-retrieval pass can read it.


Under the hood

Two numbers, one threshold.

No magic, and nothing you can't check by hand. Every pair of observations gets a distance; DBSCAN groups anything closer than --eps.

Co-retrieval similarity

How often two observations surface together, normalised against how often each surfaces at all — so a popular row can't drag everything into its orbit.

coRet(a,b) = co / (surfa + surfb − co)

Surfaced 100 times each, never together → 0. Surfaced 5 times each, always together → 1.

Temporal similarity

Smooth exponential decay on the gap between when the two were written. Default τ is 7 days.

temp(a,b) = e^(−Δt / τ)

Same day → ≈1. A week apart → 0.37. A month apart → 0.01.

distance(a,b) = 1 − ( 0.625·coRet + 0.375·temp ) // explorer: semantic layer off, weights renormalised

Worked pair — clusters.

surfaced 12 and 12 times, together 9  →  coRet = 9 / (12+12−9) = 0.600
created 2 days apart  →  temp = e^(−2/7) = 0.751
similarity = 0.625·0.600 + 0.375·0.751 = 0.657  →  distance = 0.343
0.343 < eps 0.35 → same cluster ✓

Same two observations, never co-retrieved.

coRet = 0, temp unchanged at 0.751
similarity = 0.625·0 + 0.375·0.751 = 0.282  →  distance = 0.718
0.718 > eps 0.35 → left alone ✗

Written on the same afternoon, about the same file — and still not grouped. Proximity in time is not enough on its own. Co-retrieval is doing the work.

Tunables

--eps 0.35 distance threshold · --min-pts 3 smallest group · --tau-days 7 decay constant · --since 14d look-back window. Loosen with --eps 0.45 --min-pts 2 if a run reports no clusters.

Raise τ when you widen the window

The two interact, and not obviously. At τ=7d and eps=0.35, even a perfect co-retrieval score of 1.0 can only bridge a gap of about 19 days — so a --since 30d run cannot cluster across its own window edges however strong the evidence, unless you raise --tau-days to match. The dry-run prints that ceiling on every run rather than leaving you to work out why the extra fortnight produced nothing.

The pooled corpus

One dream, every assistant.

Twelve assistants write into one local corpus through one MCP server. The dream pass runs over all of it at once — so a theme can be built from evidence that no single assistant ever held.

Twelve assistants writing into one local corpus that a single dream pass consolidates Twelve AI assistant names feed into one local store on your machine, which a single dreaming pass reads and folds into themes that all twelve assistants can then read back. Claude CodeCodexCursor Gemini CLIAntigravitygoose KimiopencodeMistral Vibe VS CodeJetBrainsClaude Desktop YOUR MACHINE observations.db recall-audit.jsonl dream one pass, whole corpus themeseverytool reads Stored on your machine; only the summarizer and embedder you choose see your text.
Rows carry an origin_agent tag so you can see who wrote what — the dream pass deliberately ignores it.

The retrievals pool too

The co-retrieval log records what surfaced together, not who asked. So when Cursor pulls two observations in one search, that pair strengthens — and the theme it eventually produces is one Claude Code will read tomorrow. The assistants shape each other's memory without ever talking to each other.

Cross-vendor is the default, not a mode

There is no flag to scope a dream to one assistant, because the corpus was never split by assistant in the first place. The only partition is --project. That falls out of the architecture rather than being bolted onto it.

Pooled on one captain, shared between captains

Pooled means the AI tools on one captain, one machine. Dreaming never folds another captain's memories into yours. With Captain Fleet, captains share what they know but never merge it, the way colleagues ask each other without copying each other's memory. When your captain recalls something ALPHA wrote, the result is tagged with ALPHA and the time, and the answer names its source: "As per ALPHA, the apples are 11", not "the apples are 11". A newer note from ALPHA only says when it was written, not that it is true, and it never overrides what your captain saw itself. ALPHA's apples may be different apples.

A tall ship at anchor on still water under a night sky whose stars are joined into constellation networks mirrored in the water below

Where the name comes from

We took the name from Anthropic. And moved the idea onto your machine.

Credit where it is due: Dreams shipped in Claude Managed Agents in May 2026 as a research preview — the first time the word named an actual API resource, though Letta and Supermemory use "dreaming" for their own background passes too, so the metaphor is by now a crowded one. A dream reads an agent's memory store alongside its past session transcripts and produces a new, reorganised store — duplicates merged, stale or contradicted entries replaced, fresh insights surfaced — leaving the input untouched. Their framing is the clean one: memory captures what an agent learns as it works; dreaming refines it between sessions.

Anthropic DreamsCaptain Memo Dreaming
RunsAnthropic's cloud, via Managed AgentsYour machine. Reads local SQLite files directly
ReadsOne memory store + 1–100 of that agent's session transcriptsYour whole corpus + the local co-retrieval log
ScopeOne agent at a timeEvery assistant that writes to your local corpus, pooled
Grouping signalA model re-reads the transcripts and synthesisesDBSCAN over co-retrieval + recency. The text is never re-read
OutputA new memory store; keep it or discard itThemes in place, members archived, not deleted
CostAPI tokens per dream, scaling with transcript volumeNo dream service to pay for. Grouping is local arithmetic; the only model call decides whether a group is one fact and names its theme, and goes to the summarizer you chose
StatusResearch preview — request accessShipping — runs by itself, reversible; dream --dry-run is the read-only explorer

The two are not competitors so much as the same idea at different altitudes. Anthropic's version has the enormous advantage of a model actually reading your transcripts, and it will surface insights a distance metric never could. Ours has the advantage of seeing every tool you use, with no dream service to pay for, and grouping on your own disk without sending your corpus anywhere. If you use one assistant and live in the cloud, theirs fits. If you use six and want the corpus on your own disk, that is the gap this fills.

Where this actually sits

Local, cross-assistant consolidation is not rare. The signal is.

It would be easy to claim more here than is true, so here is the honest version. Several projects already run a consolidation pass locally over a corpus shared by many assistants — mcp-memory-service, SuperLocalMemory, shodh-memory, and ByteRover V4 (which also calls its pass "Dream"). That combination is not the differentiator.

What we have not found in any shipped system is a pass that decides what belongs together from which memories actually surface together in real use. Every one we could read the source or docs of groups by re-reading the text — with a model, with embeddings, or with token overlap. (Closed products publish no mechanism, so treat this as "we looked hard and found none", not a proof.)

And the technique itself is old — it is only new here. Grouping things by how often they are fetched together is standard practice elsewhere: co-citation analysis in bibliometrics since 1973, item-to-item collaborative filtering since Amazon's 2003 paper, co-click clustering over search logs since 2000. None of that is our invention. Applying it to an agent's memory is the part nobody had shipped.

SystemRunsPooled across assistantsWhat decides the grouping
Anthropic Dreamscloudno — one agenta model re-reads the transcripts
mcp-memory-servicelocalyesDBSCAN / agglomerative on embeddings
SuperLocalMemorylocalyesentity overlap + temporal proximity
ByteRover V4 "Dream"localyesJaccard token overlap of title + body
shodh-memorylocalyesJaccard over stemmed tokens
MemoryOSlocalpartialwrite-time content similarity
Mem0 · Zep · Cognee · COREmixedpartialan LLM reconciles extracted facts
HeLa-Mem (paper)partialno — one conversationco-retrieval, as a Hebbian graph
Captain Memolocalyesco-retrieval + recency

The closest anyone gets

Two systems come genuinely close, and both deserve naming. SuperLocalMemory already records pairwise co-retrieval on every recall — the telemetry is there — but its consolidation pass never reads it, clustering on entity overlap instead. And HeLa-Mem, a 2026 paper, builds exactly this: edges that strengthen only when two memories appear in the same retrieval result. It is single-conversation and research-stage, not a cross-assistant tool. The idea is not ours alone; the shipped implementation is.

The signal is collected everywhere. It's just thrown away.

This is the genuinely odd part. Three of the systems above already record co-retrieval on every recall — SuperLocalMemory increments pairwise edges, shodh-memory has a CoRetrieved relation type, mcp-memory-service persists the result set of each query. Then none of them lets it decide a grouping. In one case the consumer is dead code that fails on a missing column; in another it is deliberately restricted to strengthening edges that content similarity already created. The data is sitting there, unused, in three separate projects.

What it reads today

Three files, no network.

The Dream block in captain-memo stats shows the inputs the pass would read — not its output. If the audit log is off, dreaming has no co-retrieval signal to work with and falls back to recency alone.

observations.db

The corpus. Non-archived rows inside the look-back window, optionally filtered to one project.

recall-audit.jsonl

One line per retrieval, listing what surfaced together. The co-retrieval signal is built from this. On by default; CAPTAIN_MEMO_RECALL_AUDIT=0 switches it off. Bounded at 32 MB, one rotation kept, and it never leaves the machine.

meta.sqlite3

The index that joins audit entries back to observation ids, so a surfacing event can be attributed to a row.

Preview only — and it means it

--dry-run is required for this command. Consolidation itself ships and runs by itself, but dream stays a read-only explorer: it is how you look at the clustering signal without waiting for a pass. It never contacts the worker, never writes to the database, and never calls the summarizer, so it is safe to run at any time, including while the worker is busy.

That is a deliberate order of operations: a pass that rewrites your memory should be one you have read the output of, several times, before it is allowed to touch anything.

 captain-memo dream --dry-run
  DREAM — DRY RUN ─────────────────────────────────────────
  cluster preview — no writes, no Haiku

  Observations      10103
  Without co-recall 9445   (93.5% of input — high % means audit log is thin)
  eps / minPts / τ  0.35 / 3 / 7d
  Signal weights    semantic=0.000  temporal=0.375  coRet=0.625
  Max pair age gap  19.0d   (pairs further apart cannot cluster at any co-retrieval)

  RESULT ───────────────────────────────────────────────────
  Clusters discovered  87
  Would-archive total  454
  Noise (singletons)   9649

  Cluster 1  ·  18 members  ·  coOcc=98
  Cluster 2  ·  20 members  ·  coOcc=91
  Cluster 3  ·  15 members  ·  coOcc=73
  …

A real run over one busy week. Note the shape: 95% of observations are noise, and the pass proposes archiving 454 of 10,103. Dreaming is meant to fold the few groups that genuinely earned it, not to reorganise everything it can reach. The 93.5% without co-recall is the honest cost of a young audit log — those rows simply have no signal yet, and the pass says so rather than guessing.


Questions

The ones worth asking before you let anything rewrite your memory.

Does dreaming delete my observations?

No. Members of a folded cluster are archived — flagged and linked to the theme that replaced them, still on disk, still readable. The design treats deletion as the one operation a memory system must never perform silently, so it is not performed at all: captain-memo theme undo <id> or dedup --undo puts the members back. dedup --undo can also reverse just some folds (--member, --run, --since), and an undone fold is remembered: no pass folds that pair again until dedup --allow-refold lifts the mark.

What if I switched the audit log off?

Then there is no co-retrieval signal and clustering falls back to recency alone, which is a weak grouping on its own — you'll see most observations reported as noise. It is on by default for exactly that reason; set CAPTAIN_MEMO_RECALL_AUDIT=0 in worker.env only if you want it gone. The log stays on your disk: nothing indexes it into the corpus and nothing relays it anywhere.

Does anything leave my machine?

Only to the summarizer and embedder you chose. Grouping is local arithmetic. The consolidation pass that runs on idle makes one call to the summarizer you chose for each candidate group, carrying the dates, types and titles of that group's observations, and a theme it writes is embedded by your embedder like any other memory. The dream --dry-run explorer sends nothing: it opens three local files read-only and does its own arithmetic, with no network call, no worker contact and no model invocation, not even to your own local model. It runs identically on a laptop with the network cable pulled out.

Isn't someone else already doing this?

Partly, and it's worth being straight about which part. Local consolidation over a corpus shared by several assistants is already done by mcp-memory-service, SuperLocalMemory, shodh-memory and ByteRover V4. What none of them does is group by co-retrieval — they cluster on embeddings, entity overlap or token overlap. The closest anyone comes is SuperLocalMemory, which records pairwise co-retrieval on every recall but never feeds it to its consolidator, and HeLa-Mem, a research paper that builds precisely this signal as a Hebbian graph for a single conversation. See the comparison.

Why not just use embedding similarity? It's simpler.

Because it answers a different question. Embedding similarity tells you which memories are worded alike; that reliably groups three unrelated notes that all happen to say "cache", and reliably misses the two that describe one bug in different words. Co-retrieval tells you which memories your actual work treats as one topic. The dry-run explorer scores co-retrieval and recency only; the shipped consolidation pass adds a cosine confirm on top, so a theme forms only where the observations read alike and you have pulled them up together.

Can I dream over just one assistant's memories?

No, and that's deliberate. The corpus isn't partitioned by assistant, so there's nothing to scope to — the only filter is --project. Pooling is the feature: a theme built from what Codex saw and what Claude Code saw is worth more than either tool's half of it. Pooling stops at your machine, though: another captain's memories are never folded into yours. See shared, never merged.

How is this different from Anthropic's Dreams?

Same name, same intent, different altitude. Theirs runs in the cloud, reads one agent's transcripts with a model, and bills per dream. Ours runs on your disk, groups the whole multi-assistant corpus with a distance metric, and has no dream service to pay for. Its only model call decides whether a group is one fact and names its theme, and it goes to the summarizer you chose. See the comparison above; they're complementary more than competing.