---
title: Global memory
---

# Global memory

## What it is

A progressive-disclosure, agent-written memory library built on the Context Dock native store —
modeled on Omniscio's own docs system (CLAUDE.md → MEMORY.md → topic files), but live and
agent-facing. **Milestone 1 is the read path** (browse a budget-aware tree + load any memory at the
size you choose); **Milestone 2 is the write path** — agents remember from their own work; and
**Milestone 3 adds search** — find memories by meaning + keyword. It is **gated off by default**
(`globalMemoryEnabled` / the `global-memory` unreleased feature). The engine is agent-facing (CLI
routes + first-message injection); a user-facing **Memory panel** under Agent Tools (desktop) is the
one place to see and edit what the agents remember — see [The Memory panel](#the-memory-panel-see--edit-your-memories) below.

### The idea

- A **memory** is just a Context Dock store doc — it already has three compression levels
  (full / medium / short = original / keyPoints / summary), a per-level token count, a one-line
  summary, and tags. Nothing new is stored.
- Memories are organized into a **tree**: fixed top **branches** (About You / This Project /
  People) → **tag-groups** → memories. Every node shows how many memories it holds and what they'd
  cost to load at each level, so the agent only drills in when it's worth the tokens.
- Groups are keyed on a **standard set of 17 tags** (merge, testing, git, cloud, bug-fix, …) rather
  than whatever tag the model happened to invent — see [How the tree groups](#how-the-tree-groups).
- A tiny **always-loaded root map** is injected into each session's first message (when on), so the
  agent knows memory exists and how to reach it — capped (~6k tokens), and nothing at all when off.

## Where to find it

### The Memory panel (see + edit your memories)

When the system is on, a **Memory** item appears in the left sidebar under **Agent Tools** (desktop).
It's the one place you can look at everything the agents have remembered and curate it:

- A searchable **list** of your memories on the left; click one to read it at any level
  (full / key points / summary) on the right.
- **Edit** a memory's tags or **delete** one you don't want kept — it reuses the ContextDock Library's
  detail pane, so it's familiar and consistent.
- It shows ONLY the agent-written memories (rows minted with a `mem_` id), never your imported
  course/curriculum docs — those stay in the ContextDock Library.

It reuses the ContextDock-native store under the hood (no new data, no new backend — just a widened
access gate so the panel works with only Global Memory on), and it's **desktop-only** for now (the
local store isn't wired to the mobile bridge yet). It's part of the same `global-memory` feature, so
it stays hidden until you turn Global Memory on.

## How it behaves

### How the tree groups

Memories file under a **standard tag**, not the freeform one the model wrote. The distiller invents
its own tags per session, so grouping on those directly shattered the library — on the real store
(1,495 memories, 2,390 different tags) there were **713 top-level folders, and 522 of them held a
single memory**. A library where every memory is its own folder is one you can't browse.

So every tag is folded onto a closed list of 17 standard tags — `merge`, `testing`, `git`, `cloud`,
`bug-fix`, `infrastructure`, `build`, `automation`, `process`, `tech-debt`, `security`, `ui`, `docs`,
`data`, `performance`, `i18n`, `preferences` — plus a `general` catch-all. `ready-to-merge`,
`auto-lander`, `rebase` and `merge-conflict` all land in **merge**; `verification`, `typecheck` and
`guard-lane` all land in **testing**. On the same library that turns 713 folders into **18 — with no
single-memory folders left at all** — and 95% of memories land somewhere meaningful.

Two things worth knowing:

- **Nothing is rewritten.** The fold happens when the tree is drawn, so your memories keep their
  specific tags for reading and searching — the standard tag is only the filing key. No migration,
  and the existing library regroups instantly.
- **Both levels fold.** When a group gets too big it splits by a second tag — folding that one too
  is what stops the mess from just moving down a level (734 single-memory subfolders became 25).
- **A tag about two things files under the first one.** `cloud-testing` is both cloud and testing;
  it files under **cloud** because the leading word is what it's mainly about.

The distiller is also _shown_ the standard list so new memories arrive already using it, but that's
a nicety: the fold happens on read either way, so a model that ignores the list still files correctly.

### What an agent sees + does

Five routes on the local control server (`http://127.0.0.1:19519`, bearer `$AMC_CLI_TOKEN`):

- `GET /memory/map` — the branch map: each branch with its memory count + short/medium/full token cost.
- `GET /memory/open?node=<node>` — drill in. `about-you` lists that branch's tag-groups;
  `about-you/auth` lists the memories in a group (each with id, one-line summary, tags, cost).
- `GET /memory/load?id=<id>&level=short|medium|full` — return one memory's body at the chosen level
  (falls back to an available level if the requested one wasn't generated; budget-capped via the
  Context Dock assembler).
- `POST /memory/remember` (M2) — write a memory mid-work; it's scoped (About You / this project /
  People), deduped against what's already stored, and linked to the current session.
- `GET /memory/search?q=<query>` (M3) — find memories by meaning AND keyword (hybrid); returns
  ranked hits with their ids to then load.

**These were MCP tools until 2026-09-02** (`memory_map`, `memory_open`, `memory_load`,
`memory_remember`, `memory_search`), served by a pure-Node subprocess spawned once per session.
That subprocess is retired: the routes run in the main process, which already owns the database, so
the whole per-session process disappeared and nothing about the behaviour changed. If you are
hunting for a memory MCP tool, it no longer exists — call the route.

The root map is also injected as text at the top of the first message, so an agent that never calls
a route still knows the library is there — and that injected text names the exact command.

### How auto-write works (Milestone 2)

- **The agent that did the work writes the memory.** At the end of its work, every session is asked
  — quietly, in its system prompt — whether anything about *you* is worth keeping. It asks exactly
  two things: did you reveal something about yourself, and did you correct it. It is explicitly
  **forbidden from asking what the session accomplished**, because in a fleet where agents finish
  tasks all day that question produces a changelog, not a memory. The default is to save **nothing**,
  and that is the normal outcome.
- **Why it moved off a separate summarizer.** The old session-end distiller only ever saw a
  truncated transcript, so it could not tell whether a fact was already written down in the repo's
  own docs — and 75% of the library ended up as project notes duplicating git-tracked documentation.
  The agent that did the work *can* answer that, because it knows what it just wrote.
- **The bar it applies.** Save what stops you having to correct or remind it again; nothing that will
  be stale in a week; facts, never instructions to itself ("you prefer short replies" ✓, "always
  reply briefly" ✗ — an instruction gets re-read as an order later); and knowledge about the codebase
  goes in the repository's own docs, never here.
- **Session-end distiller (legacy, still available).** The original background pass that reads a
  finished session's transcript and extracts memories with a separate model. Set
  `memoryDistillerModel` to `off` to disable it, or to any model to run it alongside the capture
  above.
- **Configurable model (A/B testing).** Which model the distiller runs on is a flippable setting,
  `memoryDistillerModel` — Haiku (default), Sonnet, Opus, DeepSeek V4 Flash, GPT-5.6 Luna, or `off` — set
  over the CLI (no UI toggle). The Claude tiers run on your API-key account; the DeepSeek/Luna tiers
  route through the app's bundled OpenRouter lane, so they need **no extra key**. It reuses the shared
  cross-provider utility path (`llmProviderService.chat`), so the daily cost cap covers every model.
- **A mid-work `POST /memory/remember`** lets an agent record something the moment it matters, without
  waiting for session end.
- **Scope.** Each memory is `about-you` (cross-project facts about the user), `people`, or
  `project:<id>`. A project memory only surfaces under that project; About You / People are global.
- **Dedup.** A write matches an existing memory by content-hash or normalized title _within the same
  scope_ and updates it instead of duplicating (meaning-based dedup arrives with M3's embeddings).
- **Safe by construction.** The distiller is **injection-fenced** (the transcript is treated as pure
  data — instructions inside it are ignored — and the output is strict, validated JSON), so a memory
  can never smuggle a prompt-injection into future sessions. It is **cost-capped** (a per-account
  daily USD limit, default $1, `memoryDistillerDailyCapUsd`) and no-ops quietly when the feature is
  off, the chosen model's provider has no credential (the Claude tiers need your API-key account; the
  DeepSeek/Luna tiers use the bundled key so they need none), the cap is hit, or the session is too short.

### Milestone 1 model (deliberately simple)

- **No persistent node table.** The tree is a fixed scope scaffold (a code constant) + tag-derived
  groups computed on read. Deep, auto-splitting "index of indexes" arrives with the janitor (M4),
  which is the first thing that actually needs a stored node tree.
- **Migrated library = About You.** Existing store docs (pre-M2) read as **About You** (the user's
  personal library); untagged docs fall under an _Imported library_ group. New memories carry the
  scope the writer assigned.

### Milestone 4 — janitor + nesting (built)

A daily background **janitor** tends the library, and it owns the **persistent nested index**
(`memory_index_nodes`) — the "index of indexes" branches that auto-split when they grow. It stays
**gated off** with the rest of the feature, and it **NEVER deletes**: everything it tidies away is
_archived_ (a distinct, fully recoverable state — deleting is a user-only action). An archived memory
drops out of the agent's active browse and `GET /memory/load` (and reappears if restored), so the tidying
is actually visible instead of silent.

Each run does four passes, cheapest-first:

- **NEST** (pure, free): rebuild the nested tree — group by tag, split any group over ~20 memories
  or ~8k tokens into sub-tag subgroups (max three levels), stamp each memory's leaf. Deterministic,
  so the tree never churns between runs; it's a _rebuildable projection_, never a second source of
  truth (drop it and the on-read scaffold still shows every memory).
- **DEDUP** (mechanical, free): collapse exact/title duplicates — keep the richest survivor, union
  its tags, keep the earliest provenance. A loser whose _unique_ text isn't already on the survivor
  is **co-filed** (left live), never hidden.
- **PRUNE** (mechanical, free): archive empty-body memories only.
- **AI lane** (opt-in, ~$1/day cap): a cheap model (Haiku) does meaning-based near-duplicate
  matching (candidates from cosine neighbours over the M3 embeddings — read defensively, degrades to
  mechanical when they're absent) and stale judgement. Every prompt is injection-fenced (memory
  content = untrusted data) and every verdict is schema-validated (a bad one is dropped). Confirmed
  near-dups merge + archive their losers; stale memories archive — all recoverable.

The scheduler is a daily cron (hourly tick + a once-per-day file), single-flight, feature-gated so
it costs nothing while off.

### How search works (Milestone 3)

- **Hybrid.** `GET /memory/search` runs two engines and merges them: **meaning-based** (an on-device
  embedding model, `all-MiniLM-L6-v2`, cosine similarity) and **keyword** (SQLite FTS5). A memory
  found by both ranks highest.
- **Free.** Embeddings run locally — M3 makes no LLM/API call and needs no cost cap.
- **Always works.** Keyword search is instant (a trigger-synced FTS index, no model). If the
  embedding model isn't ready, search degrades to keyword-only rather than failing.
- **Stays fresh.** A background pass at session end embeds new or changed memories (detected by a
  content hash), so search keeps up as the library grows — harvested from the MemPalace engine.

### Roadmap

- **All milestones (M1–M4) are built:** read path (browse + load), auto-write (session-end distiller
  - `POST /memory/remember`), search/RAG (hybrid `GET /memory/search` + on-device embeddings), and the janitor
  - persistent nested index.

## For agents

### Where it lives

- Types + scaffold: `src/shared/memory-types.ts`. Pure tree: `src/main/services/memory/tree.ts`.
- Agent routes: `src/main/services/cli/cli-server-memory-routes.ts`. Root injector:
  `src/main/services/memory/root-snapshot.ts`.
- Write path (M2): `src/main/services/memory/store-write.ts`, `distiller.ts`,
  `memory-write-scheduler.ts`, `memory-distiller-cost.ts`; distiller model picker
  `src/shared/memory-distiller-model.ts` (setting `memoryDistillerModel`).
- Search (M3): `src/main/services/memory/memory-retrieval.ts`,
  `src/main/db/queries-contextdock-embeddings.ts`, `memory-embedding-scheduler.ts`.
- Memory panel (UI): `src/renderer/src/features/memory/` (`MemoryView`,
  `memory-filter.ts`), manifest `src/shared/integrations/memory.ts`, widened store gate
  `src/main/services/feature-flag-gates.ts:requireContextDockStoreAccess`.
- Contract + invariants: [global-memory-system-contract.md](../../.claude/memory/contracts/global-memory-system-contract.md).

## Related

[Flashcards](flashcards.md) is the other local, agent-adjacent store in this library; the KMS vault is where your own hand-written notes live.
