Skip to main content
Glama
README.md
# codemem

[![CI](https://github.com/kunickiaj/codemem/actions/workflows/ci.yml/badge.svg)](https://github.com/kunickiaj/codemem/actions/workflows/ci.yml)
[![npm version](https://img.shields.io/npm/v/codemem)](https://www.npmjs.com/package/codemem) [![npm downloads per month](https://img.shields.io/npm/dm/codemem?label=npm%20downloads%2Fmonth)](https://www.npmjs.com/package/codemem) [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)

**The code is still there. The reasoning usually isn’t.**

codemem is persistent coding memory across sessions, machines, and teammates for [OpenCode](https://opencode.ai), [Claude Code](https://claude.ai/code), and Codex. It captures decisions, dead ends, and repository-specific traps, then automatically brings relevant context into later prompts.

- **Automatic context injection** — relevant memories reach the agent without asking it to search; unchanged memories already in OpenCode context are not repeated
- **Optional sync and sharing** — peer-to-peer sync carries selected project memory across machines; share project knowledge with a teammate or Team when it helps
- **Local-first storage** — memories live in SQLite on your machine; observer processing uses your configured model provider and can incur costs or consume plan usage
- **Hybrid retrieval** — FTS5 BM25 lexical search + sqlite-vec semantic search, merged and re-ranked
- **Automatic injection for OpenCode 1 and 2** — the plugin injects context into every prompt, no manual steps; the OpenCode 2 integration is beta
- **Claude Code plugin support** — install from the codemem marketplace source
- **Built-in viewer** — browse memories, sessions, and observer output in a local web UI
- **Remote MCP access** — advanced single-user self-hosting can expose an OAuth-protected Streamable HTTP MCP endpoint to configured remote clients; keep the localhost viewer private ([guide](docs/remote-mcp-oauth.md))

<picture>
  <source media="(prefers-color-scheme: dark)" srcset="docs/images/docs-feed-dark.png">
  <img alt="codemem Feed showing invented coding decisions, discoveries, and fixes" src="docs/images/docs-feed-light.png">
</picture>

*Synthetic project memories, not real session data. Explore the [Feed, Facts, and Projects walkthrough](docs/user-guide.md#explore-the-viewer) to see how to inspect what was captured.*

## Quick start

**Prerequisites:** Node.js 24.15+ and npm (or pnpm). Native database support
covers macOS x64/arm64, Linux x64/arm64 (glibc 2.34+ or musl), and Windows x64.
32-bit targets, including Linux armv7, are not supported.

**Linux:** set `ONNXRUNTIME_NODE_INSTALL=skip` in your shell and the environment that launches OpenCode, Claude Code, or Codex **before the first package install**. This avoids downloading the unused ONNX Runtime GPU provider while keeping CPU inference. Setup-managed `npx` launchers cannot set it themselves.

### OpenCode

Codemem requires OpenCode 1.18.29 or newer. One installed
`@codemem/opencode-plugin` package serves both host generations: OpenCode 1 calls
its `server()` entrypoint and OpenCode 2 calls its `setup()` entrypoint. OpenCode 2
support is validated against the exact stable `@opencode/cli@2.0.2` and
`@opencode/plugin@2.0.2` releases; Codemem labels its OpenCode 2 integration
**beta** until it has shipped through a full release cycle.

On OpenCode 2 the plugin captures user and assistant messages, terminal usage,
tool results, and session lifecycle events, and exposes the same `mem-status`,
`mem-recent`, and `mem-stats` tools. Automatic recall runs through
`session.context`: the latest user-message ID is required, and a missing or blank
ID skips recall safely. Each identified turn performs one fresh retrieval; retries
and tool continuations replay retained context byte-for-byte. Compaction, title,
and generate hooks stay isolated. Both the default message surface and legacy
`CODEMEM_INJECT_SURFACE=system` surface work. See
[OpenCode host support, troubleshooting, and rollback](docs/plugin-reference.md#opencode-host-support-troubleshooting-and-rollback).

1. Install the OpenCode plugin and MCP config:

```text
npx -y codemem setup --opencode-only
```

2. Restart OpenCode.

`npx` uses a downloaded or cached package to configure the OpenCode host; it does not create a durable `codemem` CLI installation. The configured plugin manages backend execution independently on both hosts, so no global install is required for automatic capture and context injection.

Setup writes the singular `plugin` key on purpose. OpenCode 1 requires that key, and OpenCode 2 translates it into its native `plugins` configuration, so one config entry works on both hosts. Keep one Codemem entry; if OpenCode loads Codemem twice for one project, the first registration wins and later copies skip their hooks with a warning.

3. Verify:

```text
# Works on fresh installs (no global codemem needed)
npx -y codemem stats
npx -y codemem db raw-events-status
```

That's it. On either host, the plugin captures activity, builds memories, and injects relevant context from here on.

### Try a fresh-session recall

After completing a task, check that its decision appears in the local viewer at `http://localhost:38888`. Start a new OpenCode session in the same project and ask about that decision without supplying the answer. For example, if your task involved a database migration:

```text
What decision did we make about the database migration, and why?
```

Verify the answer against the stored decision and the original task evidence. If capture is still pending or recall is empty, inspect the local state:

```text
npx -y codemem status
npx -y codemem db raw-events-status
```

### Observer access and external-model costs

The observer is the model that turns captured activity into memories. It needs a configured runtime and usable authentication: `api_http` uses your provider credentials; sidecar runtimes use Claude or Codex authentication. Local storage does not mean local-only processing: captured context is sent to the configured model, and calls can incur charges or consume plan usage. See [configuration](#configuration) for options.

<details>
<summary>Installation, upgrades, and runtime details</summary>

codemem keeps one minimum Node.js version across published packages and workspace tooling.

If you want `codemem` on your `PATH` for manual commands and semantic retrieval, install the CLI globally. The CLI installs its matching embedding runtime by default:

```text
# Linux: skip the unused ONNX Runtime GPU provider download
env ONNXRUNTIME_NODE_INSTALL=skip npm install -g codemem

# Apple silicon macOS and Windows
npm install -g codemem
```

For a smaller keyword-only install, use `npm install -g codemem --omit=optional` and set `CODEMEM_EMBEDDING_DISABLED=1` in every Codemem process. The flag is required because npm also omits sqlite-vec's optional platform package; the CLI then remains functional with FTS5.

An npm-capable manager, such as pnpm or mise's `npm:codemem` backend, can also provide the durable CLI. Ensure `codemem` is on your `PATH`, then run `codemem setup --opencode-only`. For a pnpm-global CLI, `codemem update check` reports the exact paired `pnpm add -g codemem@<version> @codemem/embeddings@<version>` command. Canonical pnpm virtual-store detection provides guidance but does not prove ownership. An explicit `codemem update install` re-checks the active package and global-root ownership with bounded, neutral-directory `pnpm root -g`, `pnpm bin -g`, and `pnpm list -g --depth 0 --json` calls before it mutates anything; it accepts pnpm 9–11's `<list-root>/node_modules` root and pnpm 12's `<list-root>` root only. It then installs exact matching packages with default and `@codemem` registry pins and verifies registered package state plus the specific pnpm-bin launcher. The updater never runs a build-approval command: pnpm 9 still runs install scripts by default, while newer pnpm releases apply their configured build-script policy. For a mise-managed CLI, `codemem update check` reports the exact global update command. An explicit `codemem update install` confirms from bounded `mise ls --json` output that the active tool source matches a global source under the user's home directory and that its install path owns the running entry. It writes the exact release into mise's primary global config, confirms the global configured version, and runs the configured tool through `mise exec` outside the invoking project rather than through a possibly stale or locally overridden `PATH` entry.

Upgrade a durable CLI with the package manager that installed it, then rerun the corresponding setup command for an existing setup-managed integration. Setup replaces its old managed `npx -y codemem mcp` launcher and codemem MCP entries detected as UV/UVX-based so both packages share one runtime; other custom MCP commands remain unchanged. The host plugin is managed independently. Claude marketplace installs use the plugin's bundled MCP configuration and do not require a separate `setup --claude-only` step.

Generated MCP configurations use the global `codemem` binary when available. Otherwise, setup-managed `npx` launchers request both packages in one temporary environment. Restart the updated host and any `codemem serve` process after an installation change. An already-running Claude or Codex MCP host keeps lexical-only recall until it restarts; restarting `codemem serve` does not restart that child.

The semantic runtime is pinned to CPU inference on every platform. ONNX Runtime 1.24.3 does not ship a macOS x64 binary, so Intel Macs continue with FTS5 keyword retrieval when semantic runtime initialization fails.

</details>

<details>
<summary>OpenCode recall and source-checkout details</summary>

OpenCode plugin and CLI are now split intentionally:

- `@codemem/opencode-plugin` — OpenCode plugin package
- `codemem` — CLI and MCP commands
- `@codemem/embeddings` — optional semantic embedding runtime installed by the CLI

OpenCode treats configured npm plugins and checkout-local `.opencode/plugins/` files as separate
sources. The repository wrapper loads the checkout source on OpenCode 1 and acts as a no-op on
OpenCode 2. Before dogfooding OpenCode 1 source changes, temporarily remove the configured npm
plugin so it cannot initialize before the checkout wrapper; restore the npm entry after testing.
On OpenCode 2, the configured npm plugin remains the only active Codemem instance and loads the
package's `./tui` companion, which displays injection, compatibility, persistence, and update
notices emitted by the server plugin. Notices remain best-effort and never affect capture or recall.

When dogfooding unpublished TUI changes from a source checkout, add the checkout's
`packages/opencode-plugin` directory to the OpenCode 2 `cli.json` `plugins` list, temporarily
remove the configured npm plugin, restart the TUI, and restore the npm entry after testing.

OpenCode 2 support needs no storage change. Both hosts write the same raw-event stream and SQLite
database, so switching between OpenCode 1 and OpenCode 2, or setting `CODEMEM_PLUGIN_IGNORE=1` in
the OpenCode 2 environment to stop the V2 path, requires no database migration.

Capture follows the OpenCode 1.18.29 runtime contract: completed assistant
messages use `info.time.completed`, token usage comes from `info.tokens`, and
successful and failed tool executions are both recorded. OpenCode reports
successful tools through `tool.execute.after` and failed tools through errored
tool parts; Codemem normalizes both into the shared raw-event stream without
double-counting repeated failure updates.

OpenCode 1 also preserves verified delegation provenance for individual task briefs.
A proven brief-only batch stays in raw storage without an observer call or learned
memory; later findings recover bounded earlier instructions from the same raw stream.
Recovered instructions follow new evidence so observer clipping does not replace
current findings with old task briefs.

Missing or ambiguous provenance retains normal extraction. See
[delegated brief capture](docs/plugin-reference.md#delegated-brief-capture-opencode-1)
for matching rules and limits. Restart OpenCode after updating the plugin.

Automatic OpenCode recall carries the host session ID through Viewer or CLI into
Core assembly. Summary memories are eligible only from the exact mapped session;
durable facts from other sessions remain eligible. If the mapping is not ready,
automatic recall omits summary continuity rather than guessing from project, a
missing host identity, or sibling IDs. Explicit `pack` and MCP requests keep
their existing behavior. See
[the requester-session contract](docs/opencode-retained-recall.md#requester-session-continuity).

</details>

### Claude Code (marketplace install)

1. In [Claude Code](https://claude.ai/code), add the codemem marketplace source and install the plugin:

```text
/plugin marketplace add kunickiaj/codemem
/plugin install codemem
```

2. Restart Claude Code.

The plugin bundles its MCP configuration and capture/context-injection hooks, and starts MCP with the TS CLI (`codemem mcp`). No preliminary `codemem setup --claude-only` command or global CLI install is required. The prerequisites and observer-access requirements above still apply.

<details>
<summary>Claude and Codex adapter transport details</summary>

Claude and Codex plugins normalize native hooks at the plugin edge and send the resulting envelope to the canonical `POST /api/raw-events` endpoint. New ingestion requests include the intended database path and runtime identity target; Viewer rejects a mismatch before writing, and the client uses its existing identity-correct command fallback. On a retryable Viewer failure, Codex persists that exact envelope before attempting command fallbacks and removes the spool only after a fallback succeeds; Claude uses the command fallbacks without a file spool. Claude `SessionEnd` asks Viewer to finish boundary extraction best-effort inside the host's 1.5-second default exit budget, reserving command-fallback time after preprocessing and across both HTTP attempts. `Stop` flushing remains opt-in and uses a 130-second host timeout for its 125-second internal extraction budget. Transcript fallback reads at most the final 16 MiB: it preserves the first record when the tail starts immediately after a newline, but discards the first fragment when the tail starts in the middle of a record. The checked-in dependency-free normalizers are generated from the TypeScript implementations in `packages/core/src/claude-hooks.ts` and `packages/core/src/codex-hooks.ts`. Named Viewer hook routes remain compatibility aliases/callers for older packaged and plugin-free CLI paths; requests that omit targeting fields remain accepted for 0.41 compatibility.

Claude and Codex `UserPromptSubmit` hooks are dependency-free direct Viewer clients. They perform a
payload-free compatible-profile check, retrieve an identity-gated `POST /api/pack` response, return
host-compatible `additionalContext`, and record delivery best-effort (capped at 500 ms). Healthy retrieval
starts no `codemem` or `npx` child. Retryable Viewer/version/profile failures—including structured
request errors before a compatible handshake—use one local compatibility chain. Validated request errors
after compatibility is established, plus policy, authorization, and compatible-profile contract failures,
fail closed.
Prompt and event HTTP reject non-loopback Viewer hosts without fetching them. Codex reserves a total
4.5-second prompt-output budget within its 5-second host timeout.

</details>

### Codex

Codex installs through its own plugin marketplace:

1. Add the codemem marketplace and install the plugin:

```text
codex plugin marketplace add https://github.com/kunickiaj/codemem.git
codex plugin add codemem@codemem
```

2. Restart Codex.

The Codex plugin bundles its MCP config (`codemem mcp`), hooks, and generated normalizer. Healthy hook ingestion uses Viewer HTTP directly and starts no `codemem` or `npx` child; those commands are fallback-only. A global install remains optional and reduces fallback latency. Validated targets are Codex CLI 0.135+ and current Desktop builds.

**API-key Codex Desktop (marketplace unavailable):** When plugin installation is greyed out (non-subscription / API-key Desktop), configure codemem without the plugin surface:

```text
npx -y codemem setup --codex-only
```

This merges `[mcp_servers.codemem]` into `~/.codex/config.toml` and writes `~/.codex/hooks.json` (SessionStart, UserPromptSubmit, PostToolUse, Stop) — backing up existing files and preserving unrelated entries. Restart Codex and approve the one-time prompt to trust the codemem hooks. MCP recall works immediately. If `codemem` is on your `PATH` the hooks call it directly; otherwise they use an `npx` launcher that requests both `codemem` and `@codemem/embeddings`. Honors `CODEX_HOME`; re-runnable (use `--force` to refresh).

Codex hook ingestion shares the same raw-event pipeline as Claude and OpenCode through normalized `POST /api/raw-events`. After a retryable HTTP failure it writes the exact envelope to `~/.codemem/codex-raw-event-spool`, attempts the `codemem enqueue-raw-event` command fallbacks, and removes the spooled envelope only after success. That spool is separate from the legacy native-hook spool. `UserPromptSubmit` runs capture ingest in the background and injects memory context via `additionalContext`; disable injection with `CODEMEM_INJECT_CONTEXT=0`. See [docs/plugin-reference.md](docs/plugin-reference.md) for details and troubleshooting.

> Was this repository previously installed as `opencode-mem`? See the [rename migration guide](docs/rename-migration.md). It covers this repository's former name, not importing data from [`tickernelz/opencode-mem`](https://github.com/tickernelz/opencode-mem).

## How it works

Adapters hook into runtime event systems (the OpenCode 1 plugin and Claude hooks). They capture tool calls and conversation messages, flush them through an observer pipeline that produces typed memories, and surface retrieval context for future prompts.

> The workflow below illustrates the OpenCode 1 hook names. OpenCode 2.0.2 uses
> `session.context` for the same automatic recall behavior, with the latest
> user-message ID required for safe turn identity.

```mermaid
sequenceDiagram
participant OC as OpenCode 1
participant PL as codemem plugin
participant VW as viewer HTTP
participant ST as MemoryStore
participant DB as SQLite

OC->>PL: tool.execute.after events
OC->>PL: experimental.chat.messages.transform
PL->>VW: POST /api/pack with shaped query
VW->>ST: build_memory_pack
ST->>DB: FTS5 BM25 lexical search
ST->>DB: sqlite vec semantic search
ST->>ST: merge rerank and section assembly
ST-->>VW: pack text
VW-->>PL: pack JSON
PL->>OC: inject codemem context
```

**Retrieval** combines two strategies: keyword search via SQLite FTS5 with BM25 scoring and semantic similarity via sqlite-vec embeddings. In the pack-building path, results from both are merged, exactly deduplicated, and re-ranked using recency and memory-kind boosts. Near-related memories stay fully rendered by default; use compact rendering or `CODEMEM_PACK_COMPRESSION=ids` only when you intentionally want ID-based expansion via `memory_get_observations`.

**Injection** happens automatically. The plugin builds a query from the current session context (first prompt, latest prompt, project, recently modified files), asks the long-lived local viewer to build the pack, and appends the result to the latest user message via `experimental.chat.messages.transform`. Before sending prompt-derived POST data, it performs a payload-free viewer/profile handshake and rejects redirects. Retryable viewer transport, version, database-target, effective identity/config-target, compression-setting, embedding-setting mismatch, or pre-handshake structured request failures fall back to the existing CLI path; structured request errors become terminal only after compatibility is established. Prior injected message blocks are replayed byte-for-byte on later turns so provider prompt caches can keep the stable prefix. Set `CODEMEM_INJECT_SURFACE=system` to use the legacy system-prompt surface. Raw-event capture uses a separate queue-first path: Viewer durably accepts the envelope before SQLite ingestion and returns `202`; if Viewer is unavailable, OpenCode saves the exact envelope to a private local spool and retries it over HTTP without launching a per-event CLI process. Bounded database, identity, contract, and connection notices omit target values, paths, payloads, subprocess output, and addresses. Each retrieval and current-request cache reuse is recorded through the viewer-backed local evidence ledger with bounded memory identities, machine-readable reason codes, delivery status, and safe repository-relative working-set paths; retryable ledger transport failures retain the CLI fallback. Repository-contained absolute tool paths are converted to repository-relative `/` paths before retrieval; outside-repository, traversing, blank, and overlong paths are omitted. Prompts, pack text, memory content, and absolute paths are not copied into the ledger, historical message reconstruction creates no new attempts, and recording failures never block injection. After a plugin restart, usable context also remains fail-open when fresh ledger-identity repair fails; fallback bytes are injected without attributing delivery to either the conflicted or failed attempt.

Raw-event degradation logs identify the Viewer stage and safe cause without exposing endpoints, paths, payloads, or subprocess output. Successfully spooled events do not show a user-facing warning; persistence failures and capacity exhaustion still do.

OpenCode defaults to an approximate 800-token injection budget and reserves room for its `[codemem context]` prefix before requesting the pack. The estimate is `ceil(characters / 4)`, not the provider's tokenizer. Set `CODEMEM_INJECT_TOKEN_BUDGET` to a positive override; unset, zero, negative, and invalid values use the default. A positive override too small to leave pack capacity injects nothing instead of forwarding `0`, which means unlimited to the generic pack CLI.

Automatic message recall has an opt-in retained-context ceiling, off by default. Set `CODEMEM_INJECT_RETAINED_TOKEN_BUDGET` to a positive safe integer such as `8000` to enable it; unset, zero, or invalid values leave it off. The per-pack default remains 800 tokens. Present historical blocks, including reconstructed blocks after restart, stay byte-identical even above a lowered ceiling. Allowance and local measurements count only blocks from the current hook session; foreign-session entries are preserved but do not consume its budget. Only actual message removal releases allowance; a compaction notification does not. The legacy system surface remains per-pack only, and explicit MCP recall is unaffected. See [retained recall lifecycle](docs/opencode-retained-recall.md).

New automatic message blocks omit unchanged memories already retained using renderer-owned IDs, content fingerprints, and spans. Changed facts remain eligible; missing legacy metadata defaults to eligibility rather than guessing from Markdown.

Exact continuation prompts can skip new injection when working context and retrieved facts are unchanged. Retrieval still checks for changed facts; explicit recall and substantive short prompts are not classified as continuations.

Named-topic continuations keep their topic query: `next`, `continue`, and `resume` alone no longer request a broad backlog pack. Automatic topical misses stay empty instead of adding unrelated recent memories or summaries; existing conversation context can stand without a fresh block. Explicit task browsing retains recent-memory fallback; explicit automatic recap may fall back only to the requester's own summary, while manual packs still support recent-memory browsing. See [topical retrieval and limits](docs/opencode-retained-recall.md#topical-retrieval).

Automatic non-task requests reject a semantic-only batch when scoped keyword retrieval finds no support. This deliberately misses useful paraphrases rather than treating nearest neighbors as confidence-qualified matches; manual semantic retrieval, supported hybrid batches, and direct file-reference retrieval remain available. All automatic requests skip timeline neighbor expansion; manual timeline browsing remains supported.

A bare `Continue` also produces no automatic items when ordinary retrieval has no eligible match. This is the result of retrieval without broadening or fallback, not a phrase denylist; an explicit topic query can still retrieve eligible facts under the same access rules.

With local plugin logging enabled, `inject.recall` records new/retained token estimates, duplicate counts, and bounded reason codes without content or identifiers. These measurements describe hook delivery, not provider usage or answer quality. Empty packs inject no headings; their artifact identity still distinguishes changed evaluations in Health. Rejected recall measurements do not trigger CLI fallback or mark a healthy Viewer unavailable.

Health's collapsed **Automatic recall (advanced)** panel shows durable local duplicate hit rates, estimated injection tokens avoided, and metadata gaps. It summarizes at most the newest 1,000 eligible OpenCode retrieval attempts from the last 30 days, filtered to currently visible selected memories. Only recorded fresh evaluations enter the hit-rate denominator; old clients and failed recording mean unknown coverage, not zero savings. When host IDs are missing, evaluation identity uses local turn information rather than prompt text alone and resets at each reported session creation. See [measurement definitions and limits](docs/opencode-retained-recall.md#local-measurements).

The profile response advertises a closed compatibility range from
`min_supported_protocol_version` through `protocol_version`. OpenCode accepts
overlapping ranges, including legacy single-version profiles. Database/runtime
identity mismatch falls back locally once without reading or retrying that Viewer.
Validated request, policy, and authorization failures after a compatible handshake fail closed without a CLI child. A delivery receipt rejected specifically as `viewer_contract_unsupported` retries once against the same Viewer without optional measurement fields; other contract failures remain terminal.

**Memories** are typed — `bugfix`, `feature`, `refactor`, `change`, `discovery`, `decision`, `exploration` — with structured fields like `facts`, `concepts`, `files_read`, and `files_modified` that improve retrieval relevance. Low-signal events are filtered at multiple layers before persistence.

For architecture details, see [docs/architecture.md](docs/architecture.md).

## CLI

| Group | Command | Description |
|-------|---------|-------------|
| **Core** | `codemem status` | Local operational roll-up (`--json` supported) |
| | `codemem stats` | Database statistics |
| | `codemem stats --attribution` | Bounded local retrieval-attribution diagnostics (`--json` supported) |
| | `codemem recent` | Recent memories |
| | `codemem search <query>` | Search memories |
| | `codemem pack <context>` | Build a context-aware memory pack |
| | `codemem pack trace <context>` | Inspect retrieval and pack assembly for a manual query |
| | `codemem distill` | Mine recurring memories into reviewable context candidates |
| | `codemem embed` | Backfill semantic embeddings |
| **Memory** | `codemem memory show <id>` | Print a memory item as JSON |
| | `codemem memory forget <id>` | Deactivate a memory item |
| | `codemem memory remember` | Manually add a memory |
| | `codemem memory inject <context>` | Raw pack text for prompt injection |
| | `codemem memory export <output>` | Export memories by project |
| | `codemem memory import <file>` | Import memories (idempotent) |
| **Viewer** | `codemem serve [start\|stop\|restart]` | Launch / manage the web viewer |
| **Sync** | `codemem sync enable\|disable` | Enable or disable peer-to-peer sync |
| | `codemem sync status` | Device info and peer health |
| | `codemem sync pair` | Advanced/legacy device pairing |
| | `codemem sync once` | Run one immediate sync pass |
| | `codemem sync doctor` | Diagnose sync configuration issues |
| | `codemem sync bootstrap` | Bootstrap sync from a peer snapshot |
| **Updates** | `codemem update install` | Install an eligible release from the installed channel |
| | `codemem update check` | Check npm for a newer release on the installed channel (`--json` and `--refresh` supported) |
| **Coordinator** | `codemem coordinator` | Self-hosted coordinator admin (groups, devices, invites) |
| **Database** | `codemem db prune-memories` | Deactivate low-signal memories (`--dry-run` to preview) |
| | `codemem db prune-observations` | Deactivate low-signal observations |
| | `codemem db backfill-tags` | Populate missing `tags_text` values |
| | `codemem db raw-events-status` | Show raw-event queue status |
| **Config** | `codemem config` | View or update configuration |
| | `codemem setup` | Interactive first-run setup |
| **Plumbing** | `codemem mcp` | MCP stdio server; best-effort starts the local viewer unless `CODEMEM_VIEWER=0` or `CODEMEM_VIEWER_AUTO=0` is set |
| | `codemem mcp http` | Local Streamable HTTP MCP server (`POST /mcp`, loopback-only by default) |

Run `codemem --help` for the human-facing command list. Adapter plumbing commands (`claude-hook-*`, `codex-hook-*`, `enqueue-raw-event`, and `prompt-pack-ledger`) remain executable for packaged-plugin and stale-client compatibility but are hidden from help and shell completion. `show`, `forget`, and `remember` still work as hidden top-level aliases. `export-memories` and `import-memories` remain visible but are deprecated — they warn on stderr and will be hidden from help and completion in a future release; use `codemem memory export` / `codemem memory import`.

Use `codemem status` to answer whether the local database, viewer, sync, maintenance,
semantic index, raw-event ingestion, and observer need attention. It is observational:
it does not create a missing database, repair state, inspect credentials, or contact
peers, coordinators, registries, or non-loopback hosts. Use `codemem status --json`
for the stable machine-readable report. `codemem stats` remains the inventory and
usage command; use `sync status`/`sync doctor`, `maintenance status`, and
`db raw-events-status` for subsystem detail.

`codemem update check` is read-only: it derives `alpha`, `beta`, `rc`, or `latest` from the
installed version and reports the latest validated release on that same channel with
installation-specific guidance. Results are cached for six hours;
pass `--refresh` to force a registry request or `--json` for one channel-aware status object.
The Viewer Health page reads the same status from `/api/update-status`. The OpenCode plugin
checks it after startup and shows at most one best-effort notification for each newly discovered
same-channel release. `notify` is the default. `codemem update install` performs the explicit, fail-closed
installation for proven npm-global, pnpm-global, and mise-managed CLIs. pnpm-global detection uses
canonical virtual-store evidence only, so the installer re-proves root and active-package ownership
with bounded neutral-directory pnpm queries before installing exact paired packages from the public npm registry;
it verifies both package state and the exact pnpm-bin launcher and never approves package builds.
Mise updates require matching active and
global machine-readable source records under the user's home directory plus matching install-path
evidence. A recognized user-level `~/.config/mise.toml` source may instead migrate when the bounded
global query succeeds with an empty result. Updates pin public default and `@codemem` npm registries, run
`mise use -g npm:codemem@<exact-version>` without a shell to write the exact release into the primary
global config, and verify with
an exact target in the post-update global `version` or `requested_version`, followed by
`mise exec -- codemem version` outside the invoking project. Linux also sets
`ONNXRUNTIME_NODE_INSTALL=skip`. Bare
`codemem update` remains non-mutating. Explicit plugin
`auto` policy may run a paired, version-pinned public-registry install of `codemem` and
`@codemem/embeddings` only
after the CLI reports a fresh, validated same-channel npm release observed for at least 24 hours
and an installation whose npm-global origin can be proven. Mise is never eligible for background
auto-update because changing its global tool configuration requires a direct user command. pnpm-global
is also never eligible for plugin background auto-install; use the direct `codemem update install`
command. Pinned, cross-channel, unsupported-channel,
downgrade, repository-development, stale, Docker, and unknown installs refuse execution. Set
`CODEMEM_BACKEND_UPDATE_POLICY=off` to disable release checks.
On Linux, plugin-owned auto-updates preserve the OpenCode environment and set
`ONNXRUNTIME_NODE_INSTALL=skip` for the install to avoid the unused GPU-provider download.
Docker guidance is always rebuild-and-restart guidance, never an in-container update.

Pack rendering defaults to self-contained context. For token-constrained experiments, `codemem pack <context> --compact` renders an index plus top details. Near-related compression is controlled by `--compression-mode off|compact|ids` (or `CODEMEM_PACK_COMPRESSION`); MCP `memory_pack` exposes the same setting as `compression_mode`. Use `ids` only when the agent can follow up with `memory_get_observations`.

### Distill recurring lessons

`codemem distill` finds repeated discoveries and decisions that may be worth promoting into project or user context.

```text
codemem distill --explain               # ranked candidates + evidence
codemem distill --all-projects --json   # machine-readable
codemem distill --no-judge              # skip the observer-model worthiness judgment
codemem distill --draft                 # draft an AGENTS.md rule for the top candidate and show a diff
codemem distill --draft --apply         # write it after confirmation
```

Candidate mining is deterministic, and by default an observer-model worthiness pass then drops clusters of recurring routine activity (release/CI status, review passes with no findings, context lookups) that recurrence scoring cannot distinguish from real lessons. Without a configured observer model the command falls back to unjudged output with a warning; `--no-judge` opts out entirely. `--draft` uses your configured observer model to turn the top candidate into a single `AGENTS.md` rule and renders a unified diff; nothing is written. `--apply` writes that rule into a codemem-managed `## Distilled lessons` block (delimited by `<!-- codemem:distilled:begin/end -->` markers, so all distilled edits stay in one place) after prompting for confirmation.

## MCP tools

To give the LLM direct access to memory tools (search, timeline, pack, distill candidates, remember, forget):

```text
codemem setup --opencode-only
```

This updates your OpenCode config to install the plugin and register the MCP server. Restart OpenCode to activate.

The standalone `codemem-mcp-ts` binary runs the same stdio server used by `codemem mcp`. Viewer autostart is on by default for both invocation paths; set `CODEMEM_VIEWER=0` or `CODEMEM_VIEWER_AUTO=0` to disable. MCP autostart and the `serve start`/`stop`/`restart` lifecycle identify a running viewer through `GET /api/health` (service discriminator `codemem-viewer`), with one bounded `GET /api/stats` compatibility probe when an older viewer returns `404`.

For local HTTP transport testing, run `codemem mcp http`. It listens on `127.0.0.1:38889` by default and exposes Streamable HTTP at `POST /mcp`; use `--host`, `--port`, and `--db-path` to override those values. OAuth discovery metadata and Dynamic Client Registration are available at `/.well-known/oauth-authorization-server`, `/.well-known/oauth-protected-resource/mcp`, and `/register`; set `--public-url` or `CODEMEM_MCP_HTTP_PUBLIC_URL` to the externally reachable `/mcp` URL so advertised endpoints use the public origin. `/authorize` redirects through a configured upstream OIDC provider before issuing public-client authorization codes, `/token` supports PKCE S256 exchange, and `/oauth/revoke` revokes access tokens. When a public URL or OIDC configuration is present, `POST /mcp` requires a valid bearer token; local-only HTTP mode remains unauthenticated for development and still applies loopback Host/Origin checks. Non-loopback binds are rejected unless you explicitly pass `--unsafe-public` or set `CODEMEM_MCP_HTTP_UNSAFE_PUBLIC=1`.

## Configuration

Config resolution precedence for runtime commands is:

1. explicit `CODEMEM_CONFIG`
2. workspace-scoped config derived from `CODEMEM_RUNTIME_ROOT` or `CODEMEM_WORKSPACE_ID`
3. legacy global config at `~/.config/codemem/config.json{c}`

Environment variables still override file values once a config file has been selected.

Codemem config mutations use a same-directory lock and atomic replacement. Concurrent Codemem writers fail with a retryable conflict instead of silently losing an update; malformed or unreadable existing config is left unchanged. External editors do not participate in the lock, so Codemem checks that the file has not changed again immediately before replacement. Existing mode, owner, and group metadata is retained on POSIX systems. Atomic replacement also retains ACLs and extended attributes on macOS and on GNU/Linux systems whose `/bin/cp` supports explicit metadata preservation; a metadata-copy failure on those systems aborts the save before rename. Windows, non-GNU Linux, and other platforms retain the existing mode behavior, but Node does not provide a portable API for preserving their extended ACL metadata.

Common overrides:

| Variable | Purpose |
|----------|---------|
| `CODEMEM_DB` | SQLite database path |
| `CODEMEM_INJECT_CONTEXT` | `0` to disable automatic context injection |
| `CODEMEM_INJECT_SURFACE` | `message` (default) to inject near the latest OpenCode user message; `system` for the legacy OpenCode system-prompt surface |
| `CODEMEM_INJECT_TOKEN_BUDGET` | Positive approximate token cap for OpenCode's complete injected context (default `800`); unset, zero, negative, and invalid values use the default |
| `CODEMEM_VIEWER_HOST`, `CODEMEM_VIEWER_PORT` | Host/port the plugin-managed viewer should start, probe, and restart |
| `CODEMEM_VIEWER_AUTO` | `0` to disable auto-starting the viewer |
| `CODEMEM_MCP_HTTP_HOST`, `CODEMEM_MCP_HTTP_PORT` | Host/port for `codemem mcp http` |
| `CODEMEM_MCP_HTTP_PUBLIC_URL` | Public `/mcp` URL advertised in MCP OAuth metadata |
| `CODEMEM_MCP_OIDC_ISSUER_URL`, `CODEMEM_MCP_OIDC_CLIENT_ID`, `CODEMEM_MCP_OIDC_CLIENT_SECRET` | Upstream OIDC provider used before MCP OAuth code issuance |
| `CODEMEM_MCP_OAUTH_ALLOWED_SUBJECT`, `CODEMEM_MCP_OAUTH_ALLOWED_EMAIL` | Single-user allowlist for upstream OIDC identity; at least one is required when OIDC is configured |
| `CODEMEM_MCP_HTTP_UNSAFE_PUBLIC` | `1`, `true`, or `yes` to allow non-loopback MCP HTTP binds |

Viewer note:

- The plugin manages one explicit viewer target per runtime. If you run multiple viewers, give each one its own DB/runtime folder instead of sharing `viewer.pid` state next to the same SQLite file.
- The OpenCode plugin monitors viewer liveness through `GET /api/health`. When an older viewer returns `404`, it makes one compatibility probe to the legacy raw-event status endpoint; raw-event ingest preflight remains separate and is bounded by a 5-second timeout.

The viewer includes a grouped Settings modal (`Connection`, `Processing`, `Device Sync`) with shell-agnostic labels and an advanced-controls toggle for technical fields.
- Settings show effective values (configured or default) and only persist changed fields on save.
- The viewer HTTP service is intended for localhost-only use. It does not currently provide a general-purpose auth/session layer for safe public exposure.

Observer runtime/auth:

- Runtime options: `api_http`, `claude_sidecar`, and `codex_sidecar`.
- `api_http` defaults to `gpt-5.1-codex-mini` (OpenAI path) unless you set `observer_model`.
- Anthropic direct API calls accept Anthropic model IDs/aliases. codemem maps the common Claude shorthand `claude-4.5-haiku` to Anthropic's direct API alias `claude-haiku-4-5`; you can also set a pinned snapshot like `claude-haiku-4-5-20251001` explicitly.
- `claude_sidecar` defaults to `claude-4.5-haiku`; if the selected `observer_model` is unsupported by Claude CLI, codemem retries once with Claude's CLI default model.
- `codex_sidecar` uses the local Codex CLI's authentication and defaults to `gpt-5.1-codex-mini` unless `observer_model` is set. See [observer auth modes](docs/plugin-reference.md#observer-auth-modes) for configuration and automatic selection rules.
- `claude_sidecar` command is configurable with `claude_command` (`CODEMEM_CLAUDE_COMMAND`) as a JSON argv array.
  - Config file example: `"claude_command": ["wrapper", "claude", "--"]`
  - Env var example: `CODEMEM_CLAUDE_COMMAND='["wrapper","claude","--"]'`
- Auth sources: `auto`, `env`, `file`, `command`, `none`.
- `observer_auth_command` must be a JSON string array (argv), not a space-separated string.
  - Config file example: `"observer_auth_command": ["iap-auth", "--audience", "example"]`
  - Env var example: `CODEMEM_OBSERVER_AUTH_COMMAND='["iap-auth","--audience","example"]'`
- Header templates support `${auth.token}`, `${auth.type}`, and `${auth.source}` (for example `Authorization: Bearer ${auth.token}`).
- Queue cadence is configurable with `raw_events_sweeper_interval_s` (seconds) in Settings/config.

## Export and import

Share project knowledge with teammates or back up memories across machines.

```text
# Export current project
codemem memory export project.json

# Import on another machine (idempotent, safe to re-run)
codemem memory import project.json --remap-project ~/workspace/myproject
```

See `codemem memory export --help` and `codemem memory import --help` for full options. The legacy top-level `export-memories` / `import-memories` forms still work but emit a deprecation warning.

## Sharing and devices

Share selected project memories with a teammate, or use the same Project-first model to understand your own devices. The viewer's normal workflow is **Projects → Sharing → Devices → Health**; open **Sharing → Teams** to manage ongoing Team membership and inherited Project access. Sync internals live under **Advanced**.

### Share projects with a teammate

For ongoing collaboration:

1. Assign exact Projects to a **Team**.
2. Invite people to join it.

Team onboarding links Identities and devices. The invitation does not assign Projects to the Team, but a new member inherits every current and future Project assigned to it. Review the Team's Projects before sending or accepting the invitation. Use **Share exact Projects** to send a separate direct Project invitation to one Identity. Team sharing must already be configured, but accepting the direct invitation does not add the recipient to the Team.

For a legacy Team that needs setup, finish the reviewed setup on any upgraded device. The first valid finish becomes the Team's shared result; other upgraded devices apply it locally and stop showing that setup task. To finish setup, the device must list the Team's coordinator group in `sync_coordinator_groups`; scope-backed discovery can show a Team for review but cannot complete its setup. See [Set up an existing Team](docs/user-guide.md#set-up-an-existing-team) for recovery and compatibility details.

For a direct share, choose **Create an invitation → Share exact Projects**:

1. Choose or enter the teammate's **Identity display name**.
2. Select the exact projects to share and review each existing-memory count.
3. Confirm that existing memories **and future activity** from those projects will share, then send the one expiring invite.
4. The recipient reviews and accepts the invitation, then confirms their Identity and device display names. Codemem establishes trust and Project access, then starts the first sync.

Only the reviewed canonical projects are shared—similarly named or sibling projects are not included. A memory marked **Only me** stays local even when its project is shared. Removing access stops future sharing; memories already copied to another device may remain there.

### Add, disable, or restore devices

When an Identity adds another device, codemem shows the exact Projects it will inherit from direct shares and Team policies. Existing exclusions stay excluded. Review that list before sending the add-device invitation; acceptance links the new device to the same Identity without widening Project access.

Disabling a device's enrollment for one coordinator group revokes future delivery only for that group's Projects. The global identity device stays active in **Devices** and can retain access through other groups. In **Advanced → Team administration**, re-enable that group enrollment; the next owner reconciliation pass then restores only the Projects currently authorized through direct shares and Team policies for that group. An offline device simply waits: it keeps its access and catches up when it reconnects. A separate global identity-device revocation removes the device from the active **Devices** list. Neither action remotely erases copied memories.

### Check devices and health

**Devices** is read-only. Each card shows the device's **Owning Identity**, whether it is available, and the Projects it receives:

- **Direct** — the Project was shared with that Identity.
- **Team** — the Identity receives the Project through a Team policy.
- **Waiting** — acceptance, setup, or delivery is waiting; an offline device resumes on reconnect.
- **Needs attention** — setup reached a terminal failure; use the displayed retry action.

Use **Health** for the current status. Globally revoked identity devices are omitted from the active Devices list. A device disabled only for one coordinator group remains listed; use **Advanced → Team administration** to review or re-enable that group enrollment. Removing access prevents future delivery, but cannot erase a copy already delivered to another device.

### Advanced and compatibility

Manual pairing, actor assignment, Spaces, grants, project mappings, and coordinator administration remain available for same-person devices, existing integrations, and diagnostics. They are not required for normal teammate sharing. Existing `#sync` and `#sync/diagnostics` links remain supported as Advanced compatibility routes. See [the user guide](docs/user-guide.md#advanced-operator-and-compatibility-guidance).

### Advanced and legacy device pairing

Use manual pairing only for a same-person device, an existing integration, or a compatibility workflow:

```text
codemem sync enable        # generate device keys
codemem sync pair          # generate pairing payload
codemem serve start        # start it; use serve stop/restart for lifecycle management
codemem sync once          # run one immediate sync pass
```

Legacy pairing and legacy coordinator invitations do not grant project access by themselves. For advanced access details, compatibility, and recovery, see [the user guide](docs/user-guide.md).

For cross-network setups where peer addresses change frequently or mDNS does not cross VPN/network boundaries, codemem also supports optional coordinator-backed discovery with a self-hosted coordinator. The preferred deployment path is the built-in `codemem coordinator` service; see [docs/coordinator-discovery.md](docs/coordinator-discovery.md).

## Semantic recall

Embeddings are stored in sqlite-vec and written automatically when memories are created. Use `codemem embed` to backfill existing memories. A custom `CODEMEM_EMBEDDING_MODEL` requires `CODEMEM_EMBEDDING_REVISION`; mutable branches and tags resolve to their canonical commit before vectors are labeled. Set `CODEMEM_EMBEDDING_OFFLINE=1` with an explicit 40-character commit to load only cached model files without a Hub request. Changing the model or revision triggers a background rebuild and uses keyword search until incompatible migrations finish. If sqlite-vec cannot load, keyword search still works.

## Alternative install methods

<details>
<summary>Local development, npx, git install</summary>

### Local development

```text
pnpm install
pnpm build
pnpm run codemem --help
```

### Via npx (no install)

```text
npx -y codemem stats
```

### Plugin for development

On OpenCode 1, temporarily remove the configured npm plugin, then start OpenCode inside the codemem
repo directory to auto-load the V1 plugin source from `.opencode/plugins/`; restore the npm entry
after testing. On OpenCode 2, that repository wrapper is a no-op: keep the configured npm plugin
for normal use, or follow the source-checkout steps above to load `packages/opencode-plugin`
explicitly while testing unpublished changes.

The repository's `.opencode/plugins/lint-feedback.js` auto-loads contributor-only OpenCode 1 and OpenCode 2 adapters backed by the shared lint-feedback implementation in `packages/opencode-plugin/src/`. The repository-owned entrypoint pins the local Biome command, runs it before and after JavaScript or TypeScript edits covered by `biome.json`, appends only new or worsened diagnostics to successful `edit`, `write`, or patch results, and preserves edits with one warning if linting fails or times out. OpenCode 2 shell commands have no post-execution hook, so changes made through shell commands require an explicit `pnpm lint:delta -- --base <ref>` checkpoint. Use `--staged` to inspect only the Git index, as the pre-commit hook does; the hook uses `--base auto` to compare from the merge base of a local remote-default ref, falling back to `HEAD`. `--staged` cannot be combined with `--head`. The wrapper and lint-feedback sources are excluded from `@codemem/opencode-plugin`; installing codemem does not enable this feedback hook.

</details>

## Documentation

- [Architecture](docs/architecture.md) — data flow, retrieval, observer pipeline, design tradeoffs
- [Coordinator-backed discovery](docs/coordinator-discovery.md) — self-hosted cross-network peer discovery
- [User guide](docs/user-guide.md) — Projects, Sharing, Devices, Health, and Advanced operations
- [Coordinator deployment](docs/coordinator-deployment.md) — advanced operator deployment and discovery
- [Coordinator E2E runbook](docs/coordinator-e2e-runbook.md) — advanced coordinator validation
- [Plugin reference](docs/plugin-reference.md) — plugin behavior, env vars, stream reliability
- [Rename migration guide](docs/rename-migration.md) — this repository's former `opencode-mem` name; not an importer for `tickernelz/opencode-mem`
- [Contributing](CONTRIBUTING.md) — development setup, tests, linting, releases

TDQS

B3.3/5.0

Scored across 14 tools

Disambiguation4/5

Most tools are clearly distinct: memory_search vs memory_search_index differ by return format, memory_get vs memory_get_observations differ by cardinality, and memory_pack is explicitly for context building. A couple of retrieval tools could be confused, particularly memory_timeline and memory_expand, which both involve surrounding context.

Naming Consistency4/5

All tools share the memory_ prefix and use lowercase snake_case, making the set feel predictable. Minor inconsistency exists in mixing verb forms (memory_search, memory_expand, memory_get) with noun or adjective forms (memory_schema, memory_recent, memory_timeline), but the overall pattern is still coherent.

Tool Count4/5

14 tools is on the higher end of the well-scoped range, and most tools serve a distinct retrieval or lifecycle purpose. The count is slightly heavy due to many specialized read/search variants, but it remains reasonable for a memory-management server.

Completeness3/5

The core memory lifecycle is mostly covered: create via memory_remember, read via memory_get/get_observations/search/recent, and soft-delete via memory_forget. However, there is no memory_update or memory_edit tool, which is a notable gap for managing evolving memories.

Maintenance

ActivityActive
ResponsivenessSlow