Skip to content

Latest commit

 

History

History
814 lines (629 loc) · 70.2 KB

File metadata and controls

814 lines (629 loc) · 70.2 KB

Plugin Reference

This page covers advanced plugin behavior, environment variables, and stream reliability controls.

Observer and settings UI

codemem observer settings

Running OpenCode with the plugin

One @codemem/opencode-plugin package serves OpenCode 1.18.29+ through its server() entrypoint and OpenCode 2 through its setup() entrypoint. OpenCode 2 support is validated against the exact stable @opencode/cli@2.0.12 and @opencode/plugin@2.0.12 releases; Codemem labels its OpenCode 2 integration beta. OpenCode 1 supports the capture and recall behavior below. On OpenCode 2 the plugin captures user and assistant messages, terminal usage, tool results, and session lifecycle events. Its automatic recall uses session.context only: the latest user-message ID is required, missing identity skips safely, and retries or tool continuations replay retained context byte-for-byte. Compaction, title, and generate hooks remain isolated. Both the default message surface and legacy system surface work.

  1. Install the configured npm plugin on either host. OpenCode 1 contributors can instead remove that entry temporarily and start OpenCode inside this repo to load the checkout-local V1 source.
  2. Every tooling session creates memory artifacts in SQLite.
  3. Prompt-time memory injection appends volatile recall output to the latest user message by default, preserving the stable system/history prefix for provider prompt caches.
  4. Use codemem stats and codemem recent to confirm ingestion.
  5. Browse the viewer at the printed URL.

OpenCode loads configured npm plugins and project-local .opencode/plugins/ files as separate sources. In this repository, the local wrapper loads the V1 source but is a no-op on OpenCode 2. For OpenCode 1 source testing, temporarily remove the configured npm entry before launching from the checkout so the local wrapper cannot lose the first-registration race. For OpenCode 2 source testing, add this checkout's packages/opencode-plugin directory to the host's cli.json plugins list, temporarily remove the npm entry, and restart the TUI. Restore the npm entry after testing; removing it without adding the package directory leaves Codemem inactive on OpenCode 2.

OpenCode host support, troubleshooting, and rollback

Host support at a glance:

Host Requirement Status
OpenCode 1 1.18.29 or newer (engines.opencode) Supported
OpenCode 2 validated on exact @opencode/cli@2.0.12 and @opencode/plugin@2.0.12 Beta integration

Install on either host with codemem setup --opencode-only (or npx -y codemem setup --opencode-only). Setup writes the singular plugin key on purpose: OpenCode 1 requires it, and OpenCode 2 translates it into its native plugins configuration, so one entry serves both hosts. The manual mem-status, mem-recent, and mem-stats tools keep their hyphenated IDs on both hosts.

Troubleshooting:

  • No automatic recall on OpenCode 2. Recall runs through session.context and requires a non-empty, non-whitespace latest user-message ID. When the ID is missing or blank, that turn skips recall rather than guessing identity; capture and the manual tools continue. Retries and tool continuations replay retained context and do not retrieve again, so a repeated turn without new context is expected.
  • No notifications on OpenCode 2. OpenCode 2 loads @codemem/opencode-plugin/tui automatically beside the server plugin. The companion subscribes to the server plugin's location-scoped RPC notices and replays notices emitted during startup. Restart the TUI after installing or upgrading the plugin; notification delivery is best-effort and does not affect capture or recall.
  • Duplicate registration warning. codemem duplicate plugin registration skipped in ~/.codemem/plugin.log means OpenCode loaded Codemem twice for one project, usually a configured npm entry plus a checkout-local copy. Remove one of them. The first server registration owns notifications as well as capture and recall.
  • Capture looks stalled on either host. Run codemem db raw-events-status and follow the post-restart config sanity checklist; both hosts share the same raw-event pipeline and spool behavior.

Rollback:

  • Stop the OpenCode 2 path. Set CODEMEM_PLUGIN_IGNORE=1 in the environment that launches OpenCode 2, or remove the Codemem plugin entry from that host's config. OpenCode 1 keeps working from the same installed package.
  • Return to OpenCode 1. Launch OpenCode 1.18.29+ with the same config. Both hosts write one raw-event stream and one SQLite database, so switching hosts in either direction needs no storage migration.
  • Pin changes. Codemem only moves its OpenCode 2 pin in a dedicated change that reruns the pinned contract and packed-host smoke tests; see versioning.

Repository-only lint feedback

When OpenCode runs from a codemem source checkout, .opencode/plugins/lint-feedback.js auto-loads repository-owned OpenCode 1 and OpenCode 2 adapters backed by packages/opencode-plugin/src/lint-feedback-core.ts. Both run the installed Biome launcher through Node without a shell and check JavaScript or TypeScript paths included by biome.json when handled by edit, write, apply_patch, or patch, including move destinations; paths outside that configured Biome scope are ignored. They append at most 10 new or worsened diagnostics and leave the edit intact when Biome fails or exceeds its 10-second timeout. Existing diagnostics are a warning-level ratchet rather than a cleanup mandate.

OpenCode 2 support is pinned and host-tested against 2.0.12. Its tool hooks make feedback visible in the successful tool result and isolate overlapping calls by session and call ID. OpenCode 2 exposes no shell post-execution hook, so shell-driven file changes bypass immediate feedback; run pnpm lint:delta -- --base <ref> at a stable checkpoint. Hook limitations never replace or disable the required CI ratchet.

This hook is contributor tooling only. Neither the repository wrapper nor src/lint-feedback.ts is included in the published @codemem/opencode-plugin package, so installing codemem does not activate it. Restart OpenCode after changing the checkout's plugin configuration.

The Biome pilot is the repository's bounded lint experiment. It covers package src trees, the canonical published OpenCode runtime under packages/opencode-plugin/.opencode, the two checkout plugin entrypoints, selected adapter hooks, package JSON, and root TypeScript configuration. The OpenCode runtime keeps its existing two-space formatting for package stability; Biome still applies lint rules, including the complexity-15 and 50-line production thresholds.

The tracked JS/TS inventory at the 0.45.0 baseline is:

Source family Files Biome status
Existing package source and selected hooks/config 770 Covered
Published OpenCode runtime and checkout entrypoints 10 Covered by this extension
E2E harness 36 Excluded from this bounded pilot; exercised by E2E jobs
Root scripts and configuration 22 Excluded except scripts/ci-workflow.test.mjs and vitest.config.ts; exercised by focused script tests
Legacy CLI plugin harness 18 Excluded; compatibility tests run through the plugin smoke job
Package support scripts, tests, and configuration 12 Excluded; package-specific checks remain authoritative
Plugin package shims, smoke scripts, and contract fixture 7 Excluded; packed-artifact and host smoke tests remain authoritative
Other adapter entrypoint 1 Excluded; adapter-normalizer tests remain authoritative
Generated adapter bundles 2 Excluded; generated from packages/core/src/claude-hooks.ts and codex-hooks.ts
Frozen evaluation snapshots 2 Excluded; immutable baseline fixtures
Type declaration files 4 Excluded; checked by TypeScript/package contract tests

Generated normalizer bundles and frozen evaluation snapshots must not be lint-fixed directly. Change their source or regeneration workflow instead.

Expanding coverage leaves the existing 1,592 warnings unchanged and exposes 63 warnings plus one informational diagnostic in the canonical runtime. The new diagnostics break down as follows:

Rule Count
noExcessiveCognitiveComplexity 42
noExcessiveLinesPerFunction 17
noNestedTernary 2
noUnusedFunctionParameters 1
noPrototypeBuiltins 1
noUselessEscapeInRegex (information) 1
Runtime path Diagnostics
packages/opencode-plugin/.opencode/lib/runtime.js 50
packages/opencode-plugin/.opencode/lib/opencode-v2-adapter.js 7
packages/opencode-plugin/.opencode/lib/delegation-context.js 4
packages/opencode-plugin/.opencode/lib/compat.js 2
packages/opencode-plugin/.opencode/lib/raw-event-spool.js 1

Run the same diagnostic comparison outside the editor with:

pnpm lint:delta -- --base <git-ref|auto> [--head <git-ref> | --staged] [--json]

--base is required. Use --base auto to compare from the merge base of an available local remote-default ref (including renamed remotes) and fall back to HEAD; the pre-commit hook uses this mode so commits do not depend on origin/main or include upstream-only changes. Pass --head for a committed ref-to-ref comparison, or pass --staged to compare only the Git index as the pre-commit hook does; those flags cannot be combined. Omit both to freeze the current working tree, including untracked files that are not ignored. The command materializes detached temporary worktrees and never stashes, resets, or checks out over the active worktree. It runs the pinned Biome binary with the head snapshot's policy against both sides, then compares diagnostics only for added, modified, renamed, or deleted paths.

Exit code 0 means no new diagnostics or policy weakening, 1 means regressions were found, and 2 means the comparison could not be trusted. JSON output includes every regression; human output shows the first ten. Appending lint overrides is accepted only when every new override uses non-negated includes and adds error-only rules. Editing or removing existing overrides, adding exclusions or suppressions, weakening rule levels, removing includes, changing Git ignore policy, disabling linting, increasing thresholds, missing refs, tool failures, and malformed or incomplete Biome reports fail closed.

Pull request CI runs this comparison inside the required TypeScript Lint job using the event's immutable base and tested merge commits. The silent CLI invocation uploads one valid, complete JSON report and emits at most ten annotations total; unchanged legacy debt passes. Because enforcement extends an existing required status, it needs no separate branch-protection setting. Comparison results are not cached, so revisions and tool or policy changes cannot reuse a stale result; dependency caching remains lockfile-keyed.

OpenCode prompt-time pack construction and prompt-pack ledger transitions use the long-lived local viewer first. Retryable connection, timeout, endpoint-version, server, or malformed-response failures fall back to the compatible CLI path. Validated request errors are terminal only after a compatible profile handshake; before compatibility is established, a structured request error may indicate a foreign or older process and uses the CLI fallback. Policy and authorization errors remain terminal. A viewer_contract_unsupported response is likewise terminal after a compatible profile handshake and may fall back before one. The HTTP timeout uses CODEMEM_INJECT_HTTP_MAX_TIME_S (default: 2 seconds). Pack and ledger requests include their resolved default or explicit database, identity/config, compression, and embedding targets. The viewer also rejects a cached store identity that no longer matches current database/config resolution. A mismatch uses the local CLI fallback exactly once instead of reading or retrying another local profile. A payload-free profile handshake runs before each POST and returns protocol_version plus min_supported_protocol_version; clients accept overlapping ranges and interpret an older profile without the minimum as a single-version range. Fetch redirects are disabled so prompt-derived request bodies are not replayed to another endpoint.

OpenCode prompt-path benchmark

A 30-run synthetic-fixture benchmark measured the OpenCode 1 plugin path only; it does not measure Claude or Codex latency. The privacy-safe report contains no prompt, memory content, IDs, or paths.

Mode Median / p95 Prompt children
Direct CLI 857.459 / 1001.124 ms —
Healthy Viewer 9.478 / 10.772 ms pack=0, ledger=0, other=0, failed=0
Classified Viewer-unavailable fallback 832.101 / 881.455 ms pack=30, ledger=30, other=0, failed=0

Claude marketplace install

CodeMem's Claude integration is hook-first and distributed through a Claude plugin marketplace source in this repo (.claude-plugin/marketplace.json).

In Claude Code, add the marketplace and install the plugin:

/plugin marketplace add kunickiaj/codemem
/plugin install codemem

The plugin starts MCP with the TS CLI:

  • codemem mcp

Claude's Node/ESM wrapper normalizes each native hook once, then sends the exact normalized envelope to the local server's canonical POST /api/raw-events endpoint. Healthy HTTP ingestion starts no codemem or npx child. On a retryable failure, the wrapper sends the same serialized envelope to:

  • codemem enqueue-raw-event
  • npx -y codemem@<plugin version> enqueue-raw-event

The wrapper never remaps the native payload during fallback, so event identity is identical across HTTP and direct enqueue. Named POST /api/claude-hooks remains a compatibility alias/caller for older packaged and plugin-free CLI paths.

Transcript fallback scans backward through at most the final 16 MiB using a bounded reusable chunk buffer and stops at the latest qualifying assistant record. When that tail starts immediately after a newline, the first complete record is retained; when it starts in the middle of a record, the partial first record is discarded. JSONL records may use LF or CRLF, and UTF-8 characters remain valid across chunk boundaries.

Claude preserves its existing best-effort failure posture: if Viewer HTTP and both command fallbacks fail, the wrapper logs the failure and exits non-zero. It does not maintain a file spool; Codex's separately documented normalized-envelope spool is intentionally adapter-specific.

You can update an existing marketplace install with:

/plugin marketplace update codemem-marketplace

Ingest one Claude hook payload through the installed wrapper:

printf '%s\n' '{"hook_event_name":"SessionStart","session_id":"sess-1"}' | node plugins/claude/scripts/ingest-hook.mjs

Prompt-time retrieval runs through the packaged dependency-free Node hook. It validates the Viewer protocol, database, and runtime identity before sending identity-gated POST /api/pack. Claude prompt retrieval and event ingestion accept only explicit loopback Viewer hosts (localhost, 127.0.0.0/8, or IPv6 loopback); non-loopback hosts are never fetched.

By default, SessionEnd requests a best-effort boundary flush after enqueue rather than waiting for sweeper timing. Set CODEMEM_CLAUDE_HOOK_FLUSH=0 to force enqueue-only behavior, and set CODEMEM_CLAUDE_HOOK_FLUSH_ON_STOP=1 to include Stop boundary flush. Direct Viewer transport keeps the boundary request, durable HTTP retry, and command fallbacks inside one Claude Code host budget (1.5 seconds by default, configurable with CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS), preserves command-fallback time after preprocessing and across both HTTP attempts, and reserves a 50 ms exit margin. The fallback reserve is normally 10% of the host budget, clamped between 500 ms and 5 seconds, but never exceeds half of the post-margin budget. Stop uses one 125-second internal budget under a 130-second packaged hook timeout, leaving startup and shutdown headroom. Override the boundary budget with CODEMEM_CLAUDE_HOOK_BOUNDARY_TIMEOUT_MS; SessionEnd still clamps its request to the live remaining host budget.

The packaged template currently registers these hook events in plugins/claude/hooks/hooks.json:

  • SessionStart
  • UserPromptSubmit
  • PostToolUse
  • PostToolUseFailure
  • Stop
  • SessionEnd

UserPromptSubmit runs scripts/user-prompt-hook.mjs, which:

  • sends the hook payload into capture ingest (ingest-hook.mjs) in the background, and
  • performs a payload-free compatible-profile check, retrieves an identity-gated Viewer pack, returns host-compatible hookSpecificOutput.additionalContext, then records delivery best-effort with a 500 ms cap.

Healthy prompt-time Claude injection starts no codemem or npx prompt child. Retryable transport, version/profile, and local database/runtime identity mismatches start one compatibility chain: codemem claude-hook-inject, then the plugin-version-pinned npx equivalent if needed. Valid request failures after a compatible handshake, policy, authorization, and compatible-profile contract failures do not fall back. Pre-handshake structured request failures use the compatibility chain. Non-loopback Viewer hosts and redirects are rejected. Ledger failure never retries inline or changes already-written hook output.

For Claude hooks, project resolution precedence is:

  1. CODEMEM_PROJECT (if set)
  2. repo/cwd-derived project name (resolve_project(cwd))
  3. payload project fallback (only when cwd is unavailable)

PreToolUse is intentionally deferred in the default template. Current memory extraction uses PostToolUse / PostToolUseFailure (tool_result) as the shipped Claude tool signal.

Codex integration

Codex is a supported integration. The Codex plugin uses the same shared raw-event pipeline as Claude and OpenCode. It is packaged under plugins/codex/ with .codex-plugin/plugin.json, bundled .mcp.json, and hook scripts under plugins/codex/scripts/.

Codex's Node/ESM wrapper adds a timestamp and nonce when the host omitted a timestamp, normalizes exactly once, and sends the exact envelope to POST /api/raw-events. Healthy HTTP ingestion starts no codemem or npx child. After a retryable HTTP failure, it durably spools the normalized envelope before starting this fallback chain:

  • codemem enqueue-raw-event, then the pinned npx equivalent.
  • A successful fallback removes the envelope from ~/.codemem/codex-raw-event-spool; if both fail, it remains there for a later HTTP drain. This is separate from the legacy native-hook spool.

The same serialized envelope—and therefore the same event ID—is used by every transport. Named POST /api/codex-hooks remains a compatibility alias/caller for older packaged and plugin-free CLI paths.

printf '%s\n' '{"hook_event_name":"SessionStart","session_id":"codex-1"}' | node plugins/codex/scripts/ingest-hook.mjs

UserPromptSubmit runs scripts/user-prompt-hook.mjs, which:

  • sends the hook payload into capture ingest (ingest-hook.mjs) in the background, and
  • performs a payload-free compatible-profile check, retrieves an identity-gated Viewer pack, and returns framed hookSpecificOutput.additionalContext; delivery recording is best-effort and capped at 500 ms.

The packaged Codex hook is a dependency-free direct Viewer client. Healthy prompt retrieval starts no codemem or npx child. Retryable Viewer transport, version/profile, and local database/runtime identity mismatches and pre-handshake structured request failures use one local compatibility chain; validated request failures after compatibility is established, policy, authorization, and compatible-profile contract failures fail closed. Prompt and event HTTP reject non-loopback Viewer hosts. The injected pack is framed as codemem reference data, not instructions. The hook has a total 4.5-second prompt-output budget within Codex's 5-second host timeout, and always emits {"continue": true} so a hook failure does not block a session.

printf '%s\n' '{"hook_event_name":"UserPromptSubmit","session_id":"codex-1","prompt":"what did we change","cwd":"/tmp/demo"}' | codemem codex-hook-inject

codemem codex-hook-inject is retained for compatibility and plugin-free setup. It now uses the same profile-validated, identity-gated Viewer HTTP retrieval before falling back to its local database pack. Like the packaged hook, it rejects non-loopback prompt retrieval and fails closed on policy, authorization, and compatible-profile contract errors; local packing is limited to classified compatibility failures. The packaged hook remains the zero-child healthy path.

For Codex hooks, project resolution precedence matches the Claude hook path:

  1. CODEMEM_PROJECT (if set)
  2. repo/cwd-derived project name
  3. payload project fallback (only when cwd is unavailable)

Stop events map the inline last_assistant_message when present, and fall back to the last assistant message in transcript_path so final responses are captured even when the inline field is omitted. This fallback uses the same backward, bounded 16 MiB JSONL scan and record-boundary rules as Claude.

The packaged Codex template registers SessionStart, UserPromptSubmit, PostToolUse, and Stop in plugins/codex/hooks/hooks.json. See docs/plans/2026-05-28-codex-first-class-integration.md for the historical rollout plan and validation gates.

Install, update, and uninstall

Install through Codex's own plugin marketplace — there is no codemem setup step:

codex plugin marketplace add https://github.com/kunickiaj/codemem.git
codex plugin add codemem@codemem
# refresh the marketplace snapshot later:
codex plugin marketplace upgrade
# remove:
codex plugin remove codemem@codemem

The plugin bundles .mcp.json, hooks/hooks.json, and a dependency-free generated normalizer. Its MCP npx launcher requests both codemem and @codemem/embeddings in the same temporary environment so the optional runtime is resolvable. Ingest wrappers use Viewer HTTP without child processes when healthy; codemem and pinned npx are fallback-only. Generated files come from the TypeScript normalizers in packages/core/src/ via node scripts/build-adapter-normalizers.mjs and are protected by a byte-drift test. Validated targets: Codex CLI 0.135+ and current Desktop builds.

Plugin-free install (codemem setup --codex-only)

API-key / non-subscription Codex Desktop greys out plugin installation. For that case, configure Codex directly — no marketplace, no plugin:

npx -y codemem setup --codex-only   # or, with a global install: codemem setup --codex-only

What it does (idempotent; honors CODEX_HOME; backs up existing files; --force to refresh):

  • MCP: appends [mcp_servers.codemem] to <CODEX_HOME>/config.toml if not already present. It uses a durable global codemem mcp command when available; otherwise its npx command requests both codemem and @codemem/embeddings before launching codemem mcp. Setup upgrades the exact older managed npx -y codemem mcp table but leaves custom launchers untouched. The file is never reparsed or reformatted: setup either appends the table or replaces only the managed command and args lines, preserving comments and unrelated servers (including secrets).
  • Hooks: merges SessionStart, UserPromptSubmit (ingest + inject), PostToolUse, and Stop into <CODEX_HOME>/hooks.json, preserving any unrelated user hooks. Hook commands resolve to a direct codemem codex-hook-* call when codemem is on PATH; otherwise their npx fallback requests both packages before invoking the hook command. Prompt injection validates the loopback Viewer profile and retrieves with POST /api/pack first, using the local database only for classified compatibility fallback.

Hooks loaded from the user config layer require a one-time trust approval in Codex (you'll be prompted on first run; MCP recall needs no trust). Codex setup also runs automatically in a plain codemem setup when a Codex home (~/.codex or $CODEX_HOME) is detected.

Troubleshooting

  • No memories and no raw events captured. Confirm the codemem the hooks resolve actually has the Codex commands: codemem codex-hook-ingest </dev/null should print a structured {"error":"read_error",...}, not unknown command. The Codex commands are first published in codemem 0.35.0; the 0.34.0 release on npm predates them, so an older global install (or the npx -y codemem@<plugin version> fallback while the plugin manifest still pins a pre-0.35 version) silently fails and spools. Inspect normalized wrapper envelopes at ~/.codemem/codex-raw-event-spool/, legacy native-hook payloads at ~/.codemem/codex-hook-spool/, and the plugin log at ~/.codemem/plugin.log.
  • Payloads are accumulating in a hook spool. The Viewer is unavailable, rejected the configured target, or could not durably queue the event. Current Claude, Codex, and Pi compatibility commands do not open SQLite for ordinary transport failures; they retain the native payload and drain entries oldest-first through Viewer HTTP after recovery. Claude and Pi terminal boundary events keep serialized direct SQLite ingest and synchronous flush as the final safeguard; lock contention leaves the boundary receipt queued.
  • POST /api/raw-events returns 404. The running viewer predates normalized edge ingress; restart or upgrade it. Older plugin packages may continue using the named compatibility route.
  • Normalized spool backlog drains automatically at one envelope per successful ingest. The wrapper never reads or removes files from the legacy native-hook spool.
  • A model rejects injected context (for example "the conversation must end with a user message"): disable prompt-time injection with CODEMEM_INJECT_CONTEXT=0. Capture/ingest keeps working and recall is still available through the MCP tools.

Pi extension

Pi support is the @codemem/pi-extension pi-package. Install once, then restart pi:

npm i -g codemem
codemem setup --pi-only

Setup appends npm:@codemem/pi-extension@<version> to ~/.pi/agent/settings.json packages (JSONC-safe, idempotent). It also derives unset observer_* keys from pi's API-key providers (cheap-model-first) without copying secrets. Flags:

  • --pi-mcp — opt into MCP via third-party pi-mcp-adapter (writes mcp.json only when the adapter is present; flips pi.tools_mode to mcp-adapter)
  • --pi-extension-path <path> — dev local-path packages entry

Uninstall by removing the packages entry and restarting pi.

Surfaces

Surface Behavior
Ingest Extension POSTs to POST /api/pi-hooks, a compatibility alias that normalizes the payload once into the canonical ingest envelope with source: "pi" — the same event identity as POST /api/raw-events with source: "pi". Falls back to codemem pi-hook-ingest + spool when HTTP is unavailable. Boundary events (session_before_compact, session_shutdown) always flush via the CLI so extraction actually runs
Injection before_agent_start appends a turn-local systemPrompt block (## codemem memories); never returns message
Tools 14 native memory_* tools via pi.registerTool (HTTP preferred, CLI fallback). Default pi.tools_mode: native — no adapter required
Compaction Observe-only: session_before_compact flushes extraction; never returns a custom compaction summary
Fork/resume Re-keys stream identity on every session_start; durable cursors via pi.appendEntry
Project identity Nearest Git root (walks up for a directory .git or a gitdir: worktree file), same walk as the other adapters

Prompt-time pack retrieval prefers the HTTP path: prove GET /api/prompt-pack-profile, then a targeted POST /api/pack (or codemem pi-hook-inject / pack --json fallback). That HTTP pack path is unledgered — no opencode retrieval-ledger row is written for pi injection.

Dashboard tabs are source-agnostic: pi rows appear alongside OpenCode/Claude/Codex with no extra setup. Packs are project-scoped, so memory crosses agents automatically.

Observer derivation caveats (v1)

  • API-key providers only (openai-completions / openai-responses / anthropic-messages). OAuth-only installs surface unconfigured (oauth-only) — never silent 401s.
  • Explicit observer_* config/env always wins over pi-derived values.
  • Setup never copies pi auth.json keys into the codemem config.
  • --pi-mcp requires pi-mcp-adapter; without it setup explains the prerequisite and writes nothing MCP-related.

See packages/pi-extension/README.md for env knobs and lifecycle rules.

Post-restart config sanity checklist

After restarting OpenCode or the viewer, run this quick check when behavior looks off:

  1. Confirm plugin + viewer are talking to the same DB path.
  2. Check backend stats and recent writes (codemem stats, codemem recent).
  3. Verify runner mode and source (CODEMEM_RUNNER, CODEMEM_RUNNER_FROM) match your install strategy.
  4. Confirm injection controls are what you expect (CODEMEM_INJECT_CONTEXT, CODEMEM_INJECT_LIMIT, CODEMEM_INJECT_TOKEN_BUDGET).
  5. If stream mode is enabled, check backlog health (codemem db raw-events-status).

If needed, restart viewer + plugin flow:

codemem serve restart

If you override the viewer bind, keep the plugin and viewer aligned on the same target:

set -lx CODEMEM_VIEWER_HOST 127.0.0.1
set -lx CODEMEM_VIEWER_PORT 38892

The plugin now passes that explicit host/port through when it auto-starts, health-checks, stops, or restarts the viewer. Its liveness monitor requires a successful GET /api/health JSON response identifying service: "codemem-viewer"; ready: false still means the viewer process is live. For compatibility, only a 404 from the health route triggers one bounded probe of the legacy raw-event status endpoint. Raw-event ingest availability keeps its separate preflight behavior, now bounded by a 5-second timeout so a hung viewer socket cannot stall event delivery. Do not run multiple viewers against the same DB/runtime folder unless they intentionally share the same bind target; otherwise viewer.pid ownership becomes ambiguous.

If compatibility toasts appear after restart, follow the runner-specific guidance in Compatibility guidance behavior below.

OpenCode plugin tools exposed to the model

  • mem-status - show viewer URL, log path, stats, and recent entries.
  • mem-stats - show just the stats block.
  • mem-recent - show recent items (defaults to 5).

These are plugin tools callable by the agent/runtime, not user-facing slash commands. OpenCode 2 registers the same hyphenated IDs through tool.transform with codemode: false; a packed-host smoke test verifies the IDs are preserved.

MCP tools exposed to agents

The MCP server exposes memory retrieval and write tools such as memory_search, memory_pack, memory_recent, memory_remember, and memory_forget.

MCP result and annotation contract

All 14 tools publish the standard optional outputSchema and annotations fields; packages/mcp-server/src/tool-contracts.ts is the authority. On success, structuredContent is an object that validates against the declared schema, and content[0].text remains compact JSON for compatible text-only clients. The two representations are identical after JSON serialization, so undefined properties are omitted.

Check isError before reading structuredContent: errors set isError: true, provide only the JSON error text, and omit structuredContent so SDKs do not validate an error against the success schema.

Annotations describe each tool's primary operation on user data and its access beyond the local server; they are not authorization. Retrieval, metadata/schema, pack, and proposal-only distill tools are read-only. Incidental delivery logging, usage records, and caches do not change that classification. memory_remember adds data, while memory_forget is a destructive soft delete and is idempotent only in the no-added-effects sense—repeating it returns not_found. memory_pack, memory_distill_candidates, and memory_remember may load an external model or invoke the observer, so their open-world hints remain enabled. These annotations do not change authentication or transport, and clients may still apply their own prompting policies.

memory_distill_candidates mines recurring lessons into reviewable context candidates. It is read-only and does not modify documentation files. By default an observer-model worthiness pass drops routine-activity clusters (release/CI status, review passes with no findings, context lookups) before the candidates are returned; pass judge: false to skip it. When no observer model is configured the tool returns unjudged output with judged: false and a judge_error in the metadata.

Example agent requests:

  • "Find recurring project lessons worth adding to AGENTS.md."
  • "Run distill for all projects and show top candidates."
  • "Distill without judging so I can see the raw recurrence ranking."

Observer model defaults

  • OpenAI: gpt-6-luna (tier routing uses gpt-6-luna for simple batches and gpt-5.6-terra for rich batches)
  • Anthropic: claude-4.5-haiku (mapped to Anthropic direct API alias claude-haiku-4-5 when using api_http)

Provider/model selection can be overridden with CODEMEM_OBSERVER_PROVIDER and CODEMEM_OBSERVER_MODEL. Custom providers are loaded from OpenCode config.

Observer output rollout

legacy_xml remains the default observer output mode during rollout. Set CODEMEM_OBSERVER_OUTPUT_MODE=auto to request constrained output where Codemem has executable transport evidence: official OpenAI Responses and direct Anthropic API-key calls use the versioned JSON envelope, while OAuth consumers, Claude/Codex sidecars, and unknown gateways preselect XML and report the capability reason.

json_schema is an explicit operator assertion for a compatible custom OpenAI gateway configured by observer_base_url, or an Anthropic gateway configured by CODEMEM_ANTHROPIC_ENDPOINT. Codemem does not infer custom-gateway support from a provider name or URL, and a declared constrained path fails closed on refusal, truncation, missing output, invalid JSON, or local schema failure. It does not silently retry the response as XML. Diagnostics report fixed reason codes and requested/actual mode; they do not copy provider, transcript, or memory content.

The provider-neutral record_memories tool definition is available for future transports and uses the same envelope as tool input. Codemem requires exactly one call and rejects missing, duplicate, wrong-name, or invalid-input calls. No provider/runtime cell uses this transport yet; each cell needs executable request and response contract tests before enablement.

XML is a compatibility contract, not an immediate deprecation. Codemem will retain its prompt, parser, and repair path until a bounded rollout window shows negligible fallback use and every supported access path has a tested intentional contract. Removal will happen only in a later compatibility-breaking release with release notes.

Observer auth modes

Observer connections support API keys, an OpenCode account, and local Claude or Codex sessions.

  • Explicit runtime values: api_key (API key), opencode_v2 (OpenCode account), claude_sidecar (Local Claude session), and codex_sidecar (Local Codex session). API-key mode requires a configured API credential, including for custom endpoints, and excludes cached subscription sign-ins. OpenCode-account mode requires the running local service and never falls back to a direct API key or imports Pi provider/model defaults. Missing credentials or an unavailable service produce a visible observer error.
  • API-key mode with provider opencode accepts OPENCODE_API_KEY only for the built-in OpenCode Zen endpoint; custom endpoints require their own explicitly configured credential. That variable is not used for other providers. Empty or whitespace-only tier model overrides use the provider's tier defaults, or the saved base model for a custom provider.
  • Settings shows restart guidance beside Save changes when an edited value requires it, and reports the actual result after saving. Field disclosures retain the affected scope and existing-data impact without repeating restart warnings; observer timing comes from the save result. Inactive observer tuning still explains when a connection ignores a value. Removing an authentication environment override can change an automatic connection, but does not activate API authentication for an explicitly selected local session. Unused pack limits do not require a restart.
  • Automatic detection has its own connection choice, so you can explicitly select a connection even when it matches the detected one. Saving Automatic clears the pinned runtime and restores detection; its preview ignores the saved runtime pin. The provider remains editable for automatic connections because it can still affect OpenCode requests; model guidance follows that editable provider. API-key and OpenCode-account connections show model catalogs; explicitly selected local sessions do not. Checking observer status refreshes automatic tier routing when apply finishes, without overwriting a connection draft, an edited routing switch, or an explicit configuration/environment setting. An edited routing switch is saved as an explicit choice even when it matches the active automatic value.
  • Authentication recovery guidance uses a server preview that ignores the auth-source environment override without changing the running process. It offers removal/restart recovery only when the saved source would use an API connection; local Claude sessions that remain local keep inactive guidance.
  • If apply finishes while a connection edit is unsaved, Settings keeps the draft's routing. Reverting all connection edits restores the cached active routing before another connection edit can save it. Explicit routing choices and environment-controlled routing remain unchanged.
  • With an Auto provider, custom model prefixes match configured provider IDs case-insensitively. OpenCode account requests retain the configured provider ID and preserve the remaining model ID's case. Direct custom-provider requests strip the same prefixes before applying configured model-ID mappings.
  • Legacy api_http and omitted runtime settings retain their existing routing until you explicitly select a connection. For newly captured OpenCode 2 raw events, implicit OpenCode OAuth routing (including a saved api_http with no explicit credential or custom endpoint) uses the local service. Older unmarked events remain on their prior path; mixed-version streams flush at generation boundaries. Unrelated Settings edits do not migrate a connection.
  • The V2 service uses the chosen provider/model and whichever account is active for that provider in OpenCode; codemem does not choose subscription versus API billing. Settings' OpenCode V2 catalog suggestions are not proof of access through the selected connection. During OpenCode location startup, codemem retries only the exact pre-generation model-selection rejection for up to about eight seconds, without changing the provider or model. Other failures are not retried; a model that remains unavailable fails visibly.
  • OpenCode's stateless one-shot API lacks a provider-enforced output-token cap and reports no usage. The local timeout and response-size limit constrain only codemem's wait and accepted data, not provider generation. The Codex/Claude sidecars and legacy OpenCode OAuth Codex path also have no provider-enforced output cap. To require a provider-side cap, select api_key for OpenAI Responses or Anthropic Messages. Historical recovery does not automatically move to the OpenCode service.
  • claude_sidecar runs observer calls via local Claude runtime auth (no ANTHROPIC_API_KEY required).
  • claude_sidecar uses claude_command (or CODEMEM_CLAUDE_COMMAND) as argv prefix for launching Claude CLI. Default: ["claude"].
  • codex_sidecar runs observer calls via the local codex CLI (codex exec), so Codex / ChatGPT Pro users get memory extraction with no API key — auth is delegated to the Codex CLI (~/.codex). It uses codex_command (or CODEMEM_CODEX_COMMAND) as the argv prefix. Default: ["codex"]. The spawned process runs with --ephemeral --ignore-user-config -s read-only and codemem's own hooks suppressed, so it never recurses into capture.
  • codemem auto-selects codex_sidecar only when no observer_runtime is set, no API key is available from any provider, the OpenCode OAuth cache has no usable credentials, the codex CLI is resolvable, and ~/.codex/auth.json exists. Otherwise set observer_runtime = "codex_sidecar" (or CODEMEM_OBSERVER_RUNTIME=codex_sidecar) explicitly.
  • Default models:
  • api_key and legacy api_http: gpt-6-luna for OpenAI unless observer_model is set.
  • opencode_v2: the selected provider's default model unless observer_model is set; OpenAI uses gpt-6-luna. With OpenAI tier routing enabled, simple/rich defaults are gpt-6-luna / gpt-5.6-terra.
  • claude_sidecar: claude-4.5-haiku unless observer_model is set.
  • codex_sidecar: gpt-6-luna unless observer_model is set; with tier routing enabled, simple/rich defaults are gpt-6-luna / gpt-5.6-terra. Explicit tier models take precedence over an explicit base model, which takes precedence over these defaults. The selected model is passed to codex exec via -m.
  • Anthropic direct API calls accept Anthropic model IDs/aliases; use claude-haiku-4-5-20251001 if you need a pinned snapshot instead of the moving alias.
  • If observer_model is unsupported in Claude CLI, codemem retries once without --model. The same fallback applies to codex_sidecar: an unavailable tier model is retried once without -m.
  • Supported auth sources: auto, env, file, command, none.
  • Supported: API keys and gateway tokens codemem can read directly.
  • Custom provider path does not implicitly fall back to OpenCode/IAP env tokens; use provider key, CODEMEM_OBSERVER_API_KEY, file, or command.
  • For codemem-native custom providers, set observer_base_url (or CODEMEM_OBSERVER_BASE_URL) to avoid relying on OpenCode provider config.

For command-refreshed gateway auth, configure a command token source plus templated headers:

{
  "observer_provider": "your-gateway-provider",
  "observer_base_url": "https://gateway.example/v1",
  "observer_runtime": "api_key",
  "observer_auth_source": "command",
  "observer_auth_command": ["iap-auth", "--audience", "example"],
  "observer_auth_timeout_ms": 1500,
  "observer_auth_cache_ttl_s": 300,
  "observer_headers": {
    "Authorization": "Bearer ${auth.token}"
  }
}

observer_auth_command is direct argv execution (no shell interpolation).

  • Config key type: JSON string array (["cmd", "arg1", "arg2"]).
  • Env var CODEMEM_OBSERVER_AUTH_COMMAND must also be a JSON string array (for example '["iap-auth","--audience","example"]'), not a space-separated command string.

Header template variables:

  • ${auth.token}
  • ${auth.type}
  • ${auth.source}

Command/file token cache behavior:

  • Successful token resolutions are cached for observer_auth_cache_ttl_s.
  • Failed token resolutions are not cached.

Stream-only mode (advanced)

Raw events preserve captured activity independently of observer extraction.

Stream contract:

  • Preflight availability: GET /api/raw-events/status?limit=0 (memory-only on queue-capable Viewers)
  • Event streaming: POST /api/raw-events
  • Non-2xx and network failures are treated as stream failures.
  • The Viewer durably appends accepted raw events to its filesystem inbox, returns 202, and drains that inbox into SQLite outside the request path.
  • OpenCode 1.18.29 session IDs are read from each event's SDK-shaped info or part payload. Assistant messages and usage are captured after either info.finish or info.time.completed; current info.tokens and legacy usage fields are normalized into one token shape.
  • Successful tools arrive through tool.execute.after and record output.output. Failed tools arrive as errored ToolPart updates and are normalized to tool.execute.after raw events with a null result and the reported error. Repeated failure updates for the same session and call are captured once, and their deduplication state is cleared when the session is deleted.
  • Raw-event batches accepted by the viewer are retried by the Viewer-owned inbox drainer before the sweeper processes persisted sessions.
  • The Viewer inbox survives restarts, accepts at most 2,000 retained entries, and reports bounded warnings for corrupt files or a sustained backlog. A full or unwritable inbox returns a safe 503 without exposing its path or event contents. For manual recovery, stop Viewer and inspect ~/.codemem/viewer-raw-event-inbox/<database-path-hash>; archive retained .json entries before removing them, then restart Viewer.
  • After Viewer delivery fails, OpenCode writes the exact normalized envelope to ~/.codemem/opencode-raw-event-spool. It does not start enqueue-raw-event; retained entries retry over HTTP at bounded startup or session boundaries with the same event ID.
  • Viewer degradation logs identify the failed status, post, or backoff stage and distinguish timeout, connection, HTTP-status, and ingest-unavailable causes. Successfully spooled events do not show a user-facing warning. Payloads, target values, subprocess output, paths, endpoints, and addresses remain omitted.
  • Corrupt spool entries are retained for recovery. The spool accepts at most CODEMEM_RAW_EVENT_SPOOL_MAX_ENTRIES .json entries and rejects a new event when full without evicting existing entries; an existing event ID remains idempotent. A repeat with the same ID and identical content except for top-level delivery timestamps keeps the first durable entry instead of falsely warning that it exists only in memory. Different content under the same ID still fails rather than overwriting the retained entry. If a private spool write fails or the spool is full, keep OpenCode running while you repair or restart Viewer and reach a session boundary to retry; restarting OpenCode before the memory-only event is delivered could lose it. If specific entries remain rejected, archive the retained spool for manual recovery; do not delete it until the events are delivered or intentionally discarded.
  • CODEMEM_RAW_EVENTS=0 pauses capture and every OpenCode spool drain. Existing spool files remain untouched until raw events are enabled again.
  • If OpenCode reports that a raw event was not saved for retry after a Viewer timeout, keep the client running until delivery recovers. The plugin records a content-free raw_events.spool.failure line in the local Codemem plugin log with a failure stage and allowlisted code (conflict, full, or a filesystem errno). If that log cannot be written, the same safe category goes to the host log. This distinguishes an existing-entry conflict from a directory, write, or sync error without logging the event ID or transcript. The warning alone does not prove which event was lost; inspect the retained spool and Viewer status before restarting the client.

Delegated brief capture (OpenCode 1)

A verified task brief is context for later work, not evidence of human approval or a new discovery. The OpenCode 1 adapter observes running task parts with callID, state.input.{subagent_type,description,prompt} and state.metadata.{parentSessionId,sessionId}. On the pinned OpenCode 1.18.30 host, chat.message receives the complete resolved message and parts before the host persists those parts individually.

The production adapter requires that hook snapshot together with a matching live task binding. The snapshot must contain exactly one ordinary, non-ignored text part, with the exact task brief and requested child agent. It retains the original message and parts so later in-place hook changes are checked too.

At the prompt-capture boundary, SDK reads cross-check the hook snapshot against the stored child session and message: parent/child IDs, message and part IDs, creation time, current agent and exact text must match. The child message must postdate the task start. A seemingly complete SDK read alone cannot qualify because it may show only partially persisted parts; missing snapshots or task metadata delivered after the hook leave provenance unknown.

The parent assistant's agent is not used as the child's agent.

The plugin freezes optional capture_context outside the event payload and ID seed. Spool replay preserves the same envelope. Ingress stores validated provenance in nullable raw_events.capture_context_json; the first accepted event wins on retries, including when it had no provenance. Invalid optional provenance stays unknown and does not reject valid raw data; existing identity validation remains strict. Old rows stay unknown, and access identity, project, visibility and memory kinds do not change.

Classification revalidates the complete host report against the prompt's exact text and shape, including after adapter normalization. Removing private content can change the prompt hash: that mismatch safely yields unknown provenance, not proof of forgery. A host provenance report never grants access or proves human approval.

A batch containing only proven briefs and harmless lifecycle bookkeeping completes without an observer request or durable memory. Raw events remain under the existing retention policy. Later tool or assistant findings use earlier briefs from the exact same source and stream as labeled observer context; they are not replayed as new events. A partial index supports bounded retrieval without scanning unrelated events.

Recovered briefs follow all new evidence in the observer prompt. Their full text is sanitized before truncation, and the appended context block has a small aggregate cap including its label. Observer clipping can omit old context, but cannot displace the evidence prefix that would have been sent without it.

The recovered delegatedBriefs list is transient observer context, not a duplicate stored in sessions.metadata_json.session_context. Other session-context fields remain intact, including a provenance-labeled firstPrompt when applicable. The context cap preserves complete XML entities and Unicode code points.

Session-context backfill and extraction replay read the same validated raw-event sidecar as normal flushes. Replay refuses proven brief-only input before calling the observer (ContextOnlyReplayError, code delegated_brief_context_only), including batches without a local session mapping; mixed batches keep their labels and may recover earlier same-stream instructions. This refusal does not fabricate a successful extraction evaluation or alter the raw history.

memory extraction-replay presents that context-only result as a non-error outcome with status: "context_only", code: "delegated_brief_context_only", evaluated: false and exit status zero. memory extraction-benchmark records it in summary.contextOnlySkips, increments summary.contextOnlySkipped, and continues the remaining batches and repetitions. The existing runs, output failures and all evaluation, stability, cost, latency and output-rate denominators exclude these skips; summary.scheduledTotal counts the full scheduled workload.

The generated schema includes the nullable sidecar and its partial index. Existing database upgrades remain behind the normal compatibility gates; plain connect() does not add this column to an existing database. Legacy backfill/replay readers treat an absent column as unknown provenance without running a migration.

Bound Limit
Observed task bindings, including replay guards 128 per plugin instance
Binding lifetime 10 minutes
Exact brief text 64,000 characters
Host metadata lookup at capture 200 ms total; sibling failure and disposal abort outstanding reads
Concurrent metadata lookups At most the current binding-cache size; duplicate requests share a lookup
Earlier instructions recovered for an observer batch Latest 4 provenance-bearing events; at most 800 appended characters in aggregate, including label

Missing, late, expired, conflicting, synthetic or multipart provenance remains unknown; capture does not wait for a later metadata event. Replayed updates cannot bind a consumed task to a different message. Distinct later task calls can bind new messages in a reused child session; ambiguous simultaneous matches do not qualify. Restart and disposal discard live bindings but preserve already captured envelopes. Tasks that started before the plugin instance do not qualify. Disposal waits for capture preparation before checking durable delivery, and cancels active host lookups immediately rather than waiting for their timeout. Mixed batches continue normal extraction with proven instructions labeled as context. This is a host-specific provenance check, not a complete language classifier. OpenCode 2, Claude Code, Codex and Pi are unchanged; reviewer recall opt-out is a separate follow-up and is not enabled by this metadata. Restart OpenCode to load an updated plugin.

Stream diagnostics and settings

GET /api/raw-events/status also includes transcript_diagnostics, a per-Viewer-process, per-router-instance counter block scoped explicitly to legacy_compatibility_routes. It counts Claude and Codex compatibility-route transcript reads by the fixed outcomes ok, not_provided, path_rejected, unreadable, no_complete_record, and no_assistant_record. These counters are not persisted, do not include paths or transcript content, and do not describe the normal generated-adapter path through POST /api/raw-events. A skipped legacy Stop response keeps skip_reason: "transcript_unavailable" and may include one of the non-ok outcomes as skip_detail; other mapping skips remain skip_reason: "unsupported_hook".

Suggested settings:

export CODEMEM_RAW_EVENTS_SWEEPER=1
export CODEMEM_RAW_EVENTS_SWEEPER_LIMIT=25
export CODEMEM_RAW_EVENTS_STUCK_BATCH_MS=300000
# optional quiet-period flush before the next periodic sweep
# export CODEMEM_RAW_EVENTS_AUTO_FLUSH=1
# export CODEMEM_RAW_EVENTS_DEBOUNCE_MS=10000
# optional retention
# export CODEMEM_RAW_EVENTS_RETENTION_MS=$((7*24*60*60*1000))

To monitor backlog:

codemem db raw-events-status

If raw-events-status shows batches=error:N (legacy label) or queue=... failed:N for a stream, retry:

codemem db raw-events-retry <session_stream_id>

Hook lifecycle and flush boundaries

The OpenCode 1 plugin uses event hooks and flushes on explicit lifecycle boundaries:

  • tool.execute.after: queue tool event; contributes to force-flush thresholds.
  • session.idle: immediate flush attempt.
  • session.created: flush previous session buffer before switching context.
  • /new prompt boundary: flush before session reset.
  • session.error: immediate flush attempt.

Force-flush thresholds (immediate flush):

  • >=50 tool events, or
  • >=15 prompts, or
  • >=10 minutes session duration.

Failure semantics:

  • Stream POST failures are backoff-gated in plugin runtime (CODEMEM_RAW_EVENTS_BACKOFF_MS).
  • Availability checks are rate-limited (CODEMEM_RAW_EVENTS_STATUS_CHECK_MS) and use the queue-capable Viewer's memory-only readiness response.
  • Each periodic viewer sweep drains a bounded batch from accepted sessions, including sessions that remain active; failed batches are retried by viewer/store queue workers (codemem db raw-events-retry).

Project label normalization

When ingesting plugin payloads, CodeMem stores a normalized project label instead of a full path.

  • Path-like labels are reduced to the basename (for example, /Users/adam/workspace/codemem -> codemem).
  • Windows-style paths are normalized with Windows path rules on every OS runtime.
    • C:\Users\adam\workspace\codemem -> codemem
    • D:/dev/client-demo -> client-demo
    • \\server\share\team\project-x -> project-x
  • CODEMEM_PROJECT still has highest precedence and is normalized the same way.

Multi-adapter project unification

If you run multiple adapters for the same project (for example OpenCode + Claude), set a shared CODEMEM_PROJECT value in both runtimes to guarantee unified project grouping in memory retrieval.

Environment hints

Env var Description
CODEMEM_RUNNER Override auto-detected runner: codemem (global), npx, node (repo/dev), or custom binary name.
CODEMEM_RUNNER_FROM Runner source override: npm package spec for npx (for example codemem@0.20.0-alpha.7), or repo/CLI entry path for node.
CODEMEM_VIEWER Set to 0, false, or off to disable the viewer entirely.
CODEMEM_VIEWER_HOST, CODEMEM_VIEWER_PORT Explicit host/port the plugin-managed viewer should start, probe, stop, and restart. Prompt retrieval accepts loopback hosts only.
CODEMEM_VIEWER_AUTO Set to 0/false/off to disable auto-start (default on).
CODEMEM_VIEWER_AUTO_STOP Set to 0/false/off to keep the viewer running after OpenCode exits (default on).
CODEMEM_PLUGIN_LOG Path for the plugin log file (set 1/true/yes for ~/.codemem/plugin.log; Claude hook failures are logged to this path by default).
CODEMEM_PLUGIN_LOG_PATH Explicit log file path for Claude hook script logging (overrides CODEMEM_PLUGIN_LOG for that script).
CODEMEM_CLAUDE_HOOK_HTTP_CONNECT_TIMEOUT_S Claude hook HTTP enqueue connect timeout in seconds (default 1).
CODEMEM_CLAUDE_HOOK_HTTP_MAX_TIME_S Claude hook HTTP enqueue total timeout in seconds (default 2).
CODEMEM_CODEX_HOOK_HTTP_TIMEOUT_MS Codex hook HTTP enqueue timeout in milliseconds (default 1000).
CODEMEM_CODEX_HOOK_LOCK_DIR Codex hook fallback lock path (default ~/.codemem/codex-hook-ingest.lock).
CODEMEM_CODEX_HOOK_LOCK_TTL_S Seconds before a Codex hook fallback lock is treated as stale (default 120).
CODEMEM_CODEX_HOOK_SPOOL_DIR Legacy native Codex hook fallback spool directory (default ~/.codemem/codex-hook-spool); the normalized wrapper does not read or drain it.
CODEMEM_CODEX_RAW_EVENT_SPOOL_DIR Normalized Codex raw-event envelope spool directory (default ~/.codemem/codex-raw-event-spool).
CODEMEM_CODEX_LOCAL_PACK_ONLY Internal coordination flag set by the packaged Codex wrapper when it invokes the CLI compatibility fallback; skips duplicate Viewer retrieval. Not intended for manual use.
CODEMEM_INJECT_HTTP_MAX_TIME_S Viewer request timeout for OpenCode and Claude prompt retrieval, the per-request cap for packaged Codex, and the total profile-plus-pack HTTP budget for plugin-free Codex (default and maximum 2 seconds for plugin-free Codex). Claude and Codex ledger completion has a separate fixed 500 ms cap; packaged Codex also enforces a total 4.5-second output budget.
CODEMEM_INJECT_MAX_CHARS Max chars returned as Claude/Codex additionalContext (default 16000).
CODEMEM_PLUGIN_CMD_TIMEOUT Milliseconds before a plugin CLI call is aborted (default 20000).
CODEMEM_MIN_VERSION Minimum required CLI version for plugin compatibility warnings (default 0.9.20).
CODEMEM_BACKEND_UPDATE_POLICY Compatibility and release-notification policy: notify (default), auto, or off.
CODEMEM_INSTALL_KIND Internal/advanced release-guidance detection override (npm-global, pnpm-global, mise, npx, docker, repo-dev, pinned, or unknown). Markers do not prove ownership or enable installation.
CODEMEM_CODEX_ENDPOINT Override Codex OAuth endpoint.
CODEMEM_PLUGIN_DEBUG Set to 1, true, or yes to log plugin lifecycle events.
CODEMEM_PLUGIN_IGNORE Skip all plugin behavior for this process on either OpenCode host.
CODEMEM_INJECT_CONTEXT Set to 0 to disable memory pack injection (default on).
CODEMEM_INJECT_SURFACE OpenCode injection surface: message by default; set system for the legacy system-prompt transform.
CODEMEM_INJECT_LIMIT Max memory items in injected pack (default 8).
CODEMEM_INJECT_RETAINED_TOKEN_BUDGET Opt-in approximate cap for retained OpenCode automatic message blocks, off by default. Only an explicit positive safe integer (for example 8000) enables it; unset, invalid, or nonpositive values disable it. Count full wrapped blocks currently retained, including replay/reconstruction. Compaction notifications do not reset allowance. Does not apply to legacy system injection or explicit MCP recall.
CODEMEM_INJECT_TOKEN_BUDGET Positive approximate token cap for OpenCode's complete wrapped injection (default 800). OpenCode reserves its context prefix before sending the remaining budget through Viewer or CLI; unset, zero, negative, and invalid values use 800. An override too small to leave a positive pack budget injects nothing because 0 means unlimited to generic pack calls. Pack accounting uses ceil(characters / 4), not the provider tokenizer.
CODEMEM_USE_OPENCODE_RUN Use opencode run for observer generation (default off).
CODEMEM_OPENCODE_MODEL Model for opencode run (default gpt-6-luna).
CODEMEM_OPENCODE_AGENT Agent for opencode run (optional).
CODEMEM_OBSERVER_PROVIDER Force openai, anthropic, or a custom provider key (optional).
CODEMEM_OBSERVER_MODEL Override observer model (default gpt-6-luna or claude-4.5-haiku).
CODEMEM_OBSERVER_API_KEY API key for observer model (optional).
CODEMEM_CLAUDE_COMMAND JSON argv array for Claude CLI invocation used by claude_sidecar (default ["claude"]).
CODEMEM_OBSERVER_RUNTIME Observer connection: api_key, opencode_v2, claude_sidecar, or codex_sidecar. Omitted or legacy api_http values retain automatic routing.
CODEMEM_OBSERVER_OUTPUT_MODE Observer output contract: legacy_xml (default); auto enables JSON Schema only on proven direct API cells and preselects XML elsewhere; json_schema explicitly asserts compatible custom OpenAI (observer_base_url) or Anthropic (CODEMEM_ANTHROPIC_ENDPOINT) gateway support. Malformed constrained output fails closed without XML reparsing.
CODEMEM_ANTHROPIC_ENDPOINT Anthropic Messages API endpoint override. With provider anthropic, API-key auth, and output mode json_schema, this explicitly opts a compatible endpoint into constrained output.
CODEMEM_OBSERVER_AUTH_SOURCE Observer auth source (auto, env, file, command, none).
CODEMEM_OBSERVER_AUTH_FILE Path to token file used when auth source is file.
CODEMEM_OBSERVER_AUTH_COMMAND Command argv as a JSON string array used when auth source is command.
CODEMEM_OBSERVER_AUTH_TIMEOUT_MS Command auth timeout in milliseconds (default 1500).
CODEMEM_OBSERVER_AUTH_CACHE_TTL_S Cache TTL for command/file auth resolution in seconds (default 300).
CODEMEM_OBSERVER_HEADERS JSON object of templated observer headers, e.g. {"Authorization":"Bearer ${auth.token}"}.
CODEMEM_OBSERVER_MAX_CHARS Max observer prompt characters (default 12000).
CODEMEM_RAW_EVENTS_BACKOFF_MS Backoff window after stream failure before retrying stream POSTs (default 10000).
CODEMEM_RAW_EVENTS_STATUS_CHECK_MS Minimum interval between stream availability preflight checks (default 30000).
CODEMEM_RAW_EVENTS_HARD_MAX Hard upper bound for queued or in-flight raw-event deliveries under sustained failure pressure (default 2000). Events rejected at delivery capacity remain in the bounded in-memory queue for flush or disposal retry instead of adding promise or detached-batch work.
CODEMEM_RAW_EVENT_SPOOL_DRAIN_LIMIT Max valid saved envelopes attempted per spool drain; corrupt entries do not consume this limit (default 20).
CODEMEM_RAW_EVENT_SPOOL_MAX_ENTRIES Max .json entries in the OpenCode raw-event spool (default 2000). A full spool rejects new event IDs without evicting existing entries; the failed write remains only in the bounded in-memory queue.
CODEMEM_RAW_EVENTS_AUTO_FLUSH Set to 1 to enable viewer-side debounced flush of streamed raw events (default off).
CODEMEM_RAW_EVENTS_DEBOUNCE_MS Debounce delay before auto-flush per session (default 60000); the periodic sweeper may drain the session sooner.
CODEMEM_RAW_EVENTS_SWEEPER Set to 1 to enable periodic sweeper flush for sessions with unflushed events (default on).
CODEMEM_RAW_EVENTS_SWEEPER_INTERVAL_MS Sweeper tick interval (default 30000).
CODEMEM_RAW_EVENTS_SWEEPER_INTERVAL_S Config/env interval in seconds used by Settings UI (default 30; overridden by CODEMEM_RAW_EVENTS_SWEEPER_INTERVAL_MS when set).
CODEMEM_RAW_EVENTS_SWEEPER_LIMIT Max sessions with unflushed events to process per sweeper tick (default 25).
CODEMEM_RAW_EVENTS_STUCK_BATCH_MS Mark flush batches older than this many ms as error (default 300000).
CODEMEM_RAW_EVENTS_RETENTION_MS If >0, delete raw events older than this many ms (default 0, keep forever).
CODEMEM_CLAUDE_HOOK_FLUSH Set to 0 to disable immediate SessionEnd boundary flush (default on for SessionEnd; Stop still requires CODEMEM_CLAUDE_HOOK_FLUSH_ON_STOP=1).
CODEMEM_CLAUDE_HOOK_FLUSH_ON_STOP Set to 1 to flush on Claude Stop hooks in addition to SessionEnd (default off).
CODEMEM_CLAUDE_HOOK_BOUNDARY_TIMEOUT_MS Override the direct Viewer request wait for SessionEnd, clamped inside CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS. For opt-in Stop flushing, this sets the whole boundary budget; the first request reserves fallback time within it (default total: 125000 ms).

Compatibility guidance behavior

When the plugin detects CLI/runtime version mismatch, it shows guidance based on runner mode:

  • CODEMEM_RUNNER=codemem: run npm install -g codemem (the CLI installs its optional semantic runtime), then restart OpenCode. On Linux, prefix with ONNXRUNTIME_NODE_INSTALL=skip to avoid downloading the unused GPU provider (see the semantic-runtime install notes).
  • CODEMEM_RUNNER=npx: the compatibility warning recommends moving to a global codemem install, which includes its optional semantic runtime. Clear explicit CODEMEM_RUNNER and CODEMEM_RUNNER_FROM overrides so the plugin detects that install, then restart OpenCode. To keep using npx instead, update CODEMEM_RUNNER_FROM to the desired codemem@<version> spec; the plugin pairs the matching @codemem/embeddings version automatically. On Linux, see the CPU-only semantic-runtime install notes in the README.
  • CODEMEM_RUNNER=node: pull latest repo changes and run pnpm build, then restart OpenCode
  • custom/unknown runner: update the underlying codemem binary or package source, then restart OpenCode

Update policy:

  • CODEMEM_BACKEND_UPDATE_POLICY=notify (default): show warning toast with suggested action
  • CODEMEM_BACKEND_UPDATE_POLICY=auto: try a best-effort auto-update for eligible compatibility-floor mismatches and fresh same-channel releases observed for at least 24 hours, then warn if still outdated
    • skipped for node dev-mode runners
    • skipped when CODEMEM_RUNNER_FROM is pinned to a fixed package/version
    • skipped for pnpm-global, mise, Docker, unknown, stale, unsupported-channel, cross-channel, or downgrade states
  • CODEMEM_BACKEND_UPDATE_POLICY=off: no compatibility toast (logging still records mismatch)

After its startup delay, the plugin also runs codemem update check --json through the same argv-based CLI runner. The CLI derives alpha, beta, rc, or latest from the installed version. notify and auto show a best-effort toast at most once per newly discovered release on that channel in the current OpenCode process; off skips this release check. Under explicit auto, an eligible result executes a paired, version-pinned public-registry install for codemem and @codemem/embeddings, then verifies the active CLI version before a plugin-owned Viewer is restarted. On Linux, both plugin-owned auto-update paths preserve the existing child environment and set ONNXRUNTIME_NODE_INSTALL=skip for the install; other platforms keep the existing environment behavior. Current, unavailable, malformed, ineligible, and timed-out results are ignored or shown as guidance without delaying plugin startup. The plugin-owned installer is best effort: it does not use the CLI install lock or Windows npm-shim handling, and plugin-owned auto-update is disabled on Windows. Avoid starting simultaneous automatic updates from multiple OpenCode sessions; use codemem update install when serialized installation is required.

Mise-managed CLIs are detected from a semver-qualified mise install path or normalized MISE_DATA_DIR path and receive global mise use -g npm:codemem@<exact-version> guidance. Only the user's explicit codemem update install command may execute that argv-only update. It first compares bounded machine-readable active and global mise source records, requires a user-owned global source, and canonicalizes current install-path ownership. It then pins public npm registries for the spawned command and writes the exact release into mise's primary global config. Verification allows the source to move from user conf.d or the recognized user-level ~/.config/mise.toml file to config.toml but requires the post-update global version or requested_version before running mise exec -- codemem version outside the invoking project; plugin background auto-update never treats mise as eligible.

pnpm-global CLIs are recognized from canonical virtual-store path evidence, which can provide release notifications and the exact paired package command but cannot prove ownership. Only an explicit codemem update install may mutate that installation: from a neutral directory it uses bounded, no-shell pnpm root -g, pnpm bin -g, and pnpm list -g --depth 0 --json checks to prove the root is exactly the list root (pnpm 12) or its node_modules directory (pnpm 9–11), and that the list root owns the active Codemem package. It installs exact matching codemem and @codemem/embeddings packages from the public npm registry and verifies both registered package state and that launcher. The updater never runs a build-approval command; pnpm 9 still runs install scripts by default, while newer releases apply their configured build-script policy. Plugin background auto-install never treats pnpm-global as eligible.

Docker images set CODEMEM_INSTALL_KIND=docker so release guidance cannot mistake the bundled global npm package for a host npm installation. Docker deployments never self-update; rebuild and restart the image with the desired CODEMEM_VERSION instead.

Compatibility checks do not block plugin startup.