# Claude Code Memory Plugin Long-term semantic memory for [Claude Code](https://docs.claude.com/en/docs/claude-code/overview). Recall happens automatically before every prompt and capture happens automatically after every turn — no MCP tool calls required from the model. Source: [examples/claude-code-memory-plugin](https://github.com/volcengine/OpenViking/tree/main/examples/claude-code-memory-plugin) ## Quick Start ### One-line installer (recommended) ```bash bash <(curl -fsSL https://raw.githubusercontent.com/volcengine/OpenViking/main/examples/claude-code-memory-plugin/setup-helper/install.sh) ``` The script runs on macOS and Linux. It checks dependencies, asks whether you'll connect to a **self-hosted** server or to **Volcengine OpenViking Cloud** (`https://api.vikingdb.cn-beijing.volces.com/openviking`), sets up `~/.openviking/ovcli.conf` (prompting only if missing), clones the OpenViking repo to `~/.openviking/openviking-repo`, adds the `claude` function wrapper to your shell rc, and installs the plugin via `claude plugin install`. Every step is idempotent — re-running is safe. If you'd rather do it by hand, follow the three steps below. ### Manual setup #### 1. Wrap `claude` to inject env from `ovcli.conf` This is the recommended path. The plugin's hooks **and** the bundled MCP server both read env vars, so we set them once — but scoped to the `claude` invocation only, not exported globally. Append to `~/.zshrc` or `~/.bashrc`: ```bash claude() { if [ -f ~/.openviking/ovcli.conf ]; then OPENVIKING_URL=$(jq -r '.url' ~/.openviking/ovcli.conf) \ OPENVIKING_API_KEY=$(jq -r '.api_key' ~/.openviking/ovcli.conf) \ command claude "$@" else command claude "$@" fi } ``` Re-source your rc and verify (use `~/.bashrc` if you're on bash): ```bash source ~/.zshrc # or: source ~/.bashrc type claude # expect: claude is a shell function ``` Inside Claude Code, run `/mcp` after the next start — the OpenViking entry should show your remote URL with valid auth. > **Don't have `ovcli.conf` yet?** See the [CLI section of the Deployment Guide](../guides/03-deployment.md#cli) to set one up. > > **Pure local mode** (`http://127.0.0.1:1933`, no auth)? Skip this step — the plugin uses the local default silently. > > **Why a function instead of `export`?** Globally exported env vars leak into every child process spawned from your shell — npm scripts, build tools, crash dumps, `/proc//environ`. The function-wrapper limits the secret to the `claude` process tree only. #### 2. Install the plugin From the OpenViking repo root: ```bash claude plugin marketplace add "$(pwd)/examples" claude plugin install claude-code-memory-plugin@openviking-plugins-local ``` > The plugin installs at user scope by default — active from any directory. We don't pass `--scope` because older Claude Code 2.0.x builds (e.g. 2.0.76) reject the flag. > > The marketplace entry points Claude Code at the source directory. Edits to `scripts/`, `hooks/`, and config files take effect on the next hook invocation — no reinstall. But moving / renaming / deleting the source dir, or `git checkout`-ing to a branch without these files, breaks the plugin. > > **Claude Code < 2.0?** `claude plugin` shipped in 2.0 (Oct 2025). Older builds still expose `claude mcp add` + the hooks system — the [plugin README's Legacy mode section](https://github.com/volcengine/OpenViking/blob/main/examples/claude-code-memory-plugin/README.md#legacy-mode-claude-code--20) shows how to wire the same functionality by hand, and the one-line installer detects the version and offers it automatically. #### 3. Start Claude Code ```bash claude ``` Inside Claude Code, run `/mcp` to confirm the OpenViking MCP entry shows your remote URL. If the plugin doesn't seem to fire, set `OPENVIKING_DEBUG=1` and check `~/.openviking/logs/cc-hooks.log`. ## Why a function wrapper? The plugin's hooks read `ovcli.conf` directly — but the bundled `.mcp.json` entry **cannot**. Claude Code parses `.mcp.json` itself and only supports `${VAR}` substitution, so config-file values can't transparently reach the MCP server URL or auth headers. Injecting env vars at `claude` invocation is the single path that covers both hooks and MCP. Wrapping in a shell function (rather than a global `export`) keeps the API key out of every other shell child process — see the security note in [the manual setup step 1](#1-wrap-claude-to-inject-env-from-ovcli-conf). **Symptom of misconfiguration**: hooks (auto-recall, auto-capture) work fine because they read `ovcli.conf` via Node, but the on-demand MCP tools (`search`, `read`, `store`, …) silently connect to `http://127.0.0.1:1933` with empty auth headers, and `/mcp` shows the wrong URL. ## Configuration ### Resolution priority Every plugin field follows this chain (highest → lowest): 1. **Environment variables** (`OPENVIKING_*`) 2. **`ovcli.conf`** — connection fields only (`url`, `api_key`, `account`, `user`, `agent_id`) 3. **`ov.conf`** — server config; the plugin reads `server.url`, `server.root_api_key`, and a legacy `claude_code` block if present 4. **Built-in defaults** (`http://127.0.0.1:1933`, no auth) > ⚠️ **Hooks only.** This chain is implemented in `scripts/config.mjs` and consumed by hook scripts. It does **not** apply to MCP server registration — see [Why a function wrapper?](#why-a-function-wrapper) above. ### Key environment variables | Env Var | Default | Description | |--------------------------------------------------|---------------|-----------------------------------------------------------------------| | `OPENVIKING_URL` / `OPENVIKING_BASE_URL` | — | Full server URL | | `OPENVIKING_API_KEY` / `OPENVIKING_BEARER_TOKEN` | — | API key; sent as `Authorization: Bearer ` | | `OPENVIKING_AUTO_RECALL` | `true` | Enable auto-recall on every user prompt | | `OPENVIKING_RECALL_LIMIT` | `6` | Max memories to inject per turn | | `OPENVIKING_RECALL_TOKEN_BUDGET` | `2000` | Token budget for inline content; over-budget items degrade to URI hints | | `OPENVIKING_AUTO_CAPTURE` | `true` | Enable auto-capture; also gates write hooks | | `OPENVIKING_BYPASS_SESSION` | `false` | One-shot: `1`/`true` skips every hook in the current process | | `OPENVIKING_BYPASS_SESSION_PATTERNS` | `""` | CSV of glob patterns matched against `session_id` or `cwd` | | `OPENVIKING_MEMORY_ENABLED` | (auto) | `0`/`false`=force off; `1`/`true`=force on | | `OPENVIKING_DEBUG` | `false` | Write hook logs to `~/.openviking/logs/cc-hooks.log` | For multi-tenant deployments, `OPENVIKING_ACCOUNT`, `OPENVIKING_USER`, and `OPENVIKING_AGENT_ID` set the corresponding `X-OpenViking-*` headers. The full env-var list (recall tuning, capture tuning, lifecycle, debug) is in the [plugin README](https://github.com/volcengine/OpenViking/blob/main/examples/claude-code-memory-plugin/README.md#configuration). ### Bypass a session Use Claude Code in a `/tmp` PoC directory without polluting your long-term memory: ```bash # Persistent: any session whose session_id or cwd matches a pattern export OPENVIKING_BYPASS_SESSION_PATTERNS='/tmp/**,**/scratch/**,/Users/me/Dev/throwaway/*' # Or one-shot: OPENVIKING_BYPASS_SESSION=1 claude ``` When bypass is active, every hook approves immediately without contacting OpenViking. ## Statusline The plugin also renders a one-line OpenViking status under your Claude Code input box. The installer registers it in `~/.claude/settings.json` (CC's plugin manifest doesn't accept a `statusLine` field, so it has to live in user settings). ```text OV ✓ │ ↩ 6 mem (0.92) · 50ms last turn injected 6 memories, top score 0.92 OV ⚠ slow probe missed the 1 s budget (server may be lagging) OV ✗ offline server unreachable OV ⚡ bypass OPENVIKING_BYPASS_SESSION* matched OV ✓ │ ✎ 573/20k · 2 arch pending capture, two archives produced this session OV ✓ │ 🔗 resumed │ +3 today session re-hydrated; 3 archives committed today ``` The hook scripts write small JSON snapshots to `~/.openviking/state/`; the statusline script reads those plus a 5 s shared cache of `GET /health`. The probe is bounded by a 1 s hard timeout; the cache is shared across sessions to prevent stampedes. Set `OPENVIKING_STATUSLINE=off` to silence without removing the registration, or `jq 'del(.statusLine)' ~/.claude/settings.json` to remove. If you already had a custom statusline, the installer prompts replace / skip / manual compose. For the full segment glossary (when each one shows, why one might be missing) and personalization recipes (hide segments, recolor, compose with another statusline, add a custom segment), see [`examples/claude-code-memory-plugin/STATUSLINE.md`](https://github.com/volcengine/OpenViking/blob/main/examples/claude-code-memory-plugin/STATUSLINE.md). The easiest path is to open Claude Code and ask it to read the doc, explain the segments, and tailor anything to your preference. ## Compared to Claude Code's built-in `MEMORY.md` This plugin **complements** Claude Code's native memory system, it doesn't replace it: | Feature | Built-in `MEMORY.md` | OpenViking plugin | |--------------|-----------------------------------|----------------------------------------------------| | Storage | Flat markdown | Vector DB + structured extraction | | Search | Loaded into context wholesale | Semantic similarity + ranking + token budget | | Scope | Per-project | Cross-project, cross-session, cross-agent | | Capacity | ~200 lines (context limit) | Unlimited (server-side storage) | | Extraction | Manual rules | LLM-powered entity / preference / event extraction | | Subagents | Same as parent | Isolated session + typed agent namespace | ## Hook behavior | Hook | Trigger | Action | |-----------------------|--------------------------------------------|---------------------------------------------------------------------------------------------------| | `UserPromptSubmit` | Each user turn | Search OV → rank → inject `` block within a token budget | | `Stop` | Claude finishes a response | Parse transcript → push new user turns to OV session → commit when pending tokens cross threshold | | `SessionStart` | New / resumed / post-compact session | On `resume`/`compact`, fetch the latest archive overview and inject it as additional context | | `PreCompact` | Before Claude Code rewrites the transcript | Commit pending messages so they become an archive before CC mutates the transcript | | `SessionEnd` | Claude Code session closes | Final commit so the last window is archived | | `SubagentStart` | Parent spawns a subagent via Task tool | Derive an isolated OV session ID for the subagent, persist start state | | `SubagentStop` | Subagent finishes | Read subagent transcript → push to isolated session with subagent-typed agent header → commit | `Stop`, `SessionEnd`, and `SubagentStop` use a detached-worker pattern so the user never waits for OpenViking. Disable with `OPENVIKING_WRITE_PATH_ASYNC=false` if you need deterministic ordering. `auto-capture` strips ``, ``, ``, and `[Subagent Context]` blocks before pushing to OV — without this, the recall context the plugin injects this turn would be captured back as part of the user's message next turn. ### OV session ID derivation The OV session ID embeds the CC `session_id` verbatim, so you can map between the two by eye: - Parent: `cc-`, e.g. `cc-7d978bb3-cd9c-4ac6-828d-20965d66b783` - Subagent: `cc-__agent-`, e.g. `cc-7d978bb3-cd9c-4ac6-828d-20965d66b783__agent-abc123` `~/.openviking/state/last-capture.json` shows the live `cc_session_id` and `ov_session_id` for the current session; the CC side's session ID is also the filename of `~/.claude/projects//.jsonl`. ## Troubleshooting | Symptom | Cause | Fix | |--------------------------------------------|----------------------------------------------------------------|----------------------------------------------------------------------------------------------| | Plugin not activating | No `ov.conf` / `ovcli.conf` found | Run the [one-line installer](#one-line-installer-recommended), or set `OPENVIKING_MEMORY_ENABLED=1` plus URL/API_KEY env vars | | Hooks fire but recall is empty | OpenViking server not running, or wrong URL | `curl "$(jq -r '.url' ~/.openviking/ovcli.conf)/health"` | | MCP tools hit `127.0.0.1` instead of remote | `.mcp.json` only resolves `${VAR}`, no `ovcli.conf` integration | See [Why a function wrapper?](#why-a-function-wrapper) | | Remote auth 401 / 403 | Wrong API key or missing tenant headers | Verify `OPENVIKING_API_KEY`; for multi-tenant, also check `OPENVIKING_ACCOUNT` / `OPENVIKING_USER` | | `Stop` hook times out | Server slow + sync write path | Leave `OPENVIKING_WRITE_PATH_ASYNC=true` (default), or raise the `Stop` timeout in `hooks/hooks.json` | ## See also - [Plugin README](https://github.com/volcengine/OpenViking/blob/main/examples/claude-code-memory-plugin/README.md) — exhaustive env-var tables, hook timeouts, debug logging, architecture diagram - [Migration notes](https://github.com/volcengine/OpenViking/blob/main/examples/claude-code-memory-plugin/MIGRATION.md) — upgrading from earlier plugin versions - [MCP Integration Guide](../guides/06-mcp-integration.md) — for MCP tool parameters and other clients - [Deployment Guide → CLI](../guides/03-deployment.md#cli) — `ovcli.conf` setup