Skip to content

Claude Code Memory Plugin

Long-term semantic memory for Claude Code. 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

Quick Start

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 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/<pid>/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 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.

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? above.

Key environment variables

Env VarDefaultDescription
OPENVIKING_URL / OPENVIKING_BASE_URLFull server URL
OPENVIKING_API_KEY / OPENVIKING_BEARER_TOKENAPI key; sent as Authorization: Bearer <key>
OPENVIKING_AUTO_RECALLtrueEnable auto-recall on every user prompt
OPENVIKING_RECALL_LIMIT6Max memories to inject per turn
OPENVIKING_RECALL_TOKEN_BUDGET2000Token budget for inline content; over-budget items degrade to URI hints
OPENVIKING_AUTO_CAPTUREtrueEnable auto-capture; also gates write hooks
OPENVIKING_BYPASS_SESSIONfalseOne-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_DEBUGfalseWrite 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.

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. 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:

FeatureBuilt-in MEMORY.mdOpenViking plugin
StorageFlat markdownVector DB + structured extraction
SearchLoaded into context wholesaleSemantic similarity + ranking + token budget
ScopePer-projectCross-project, cross-session, cross-agent
Capacity~200 lines (context limit)Unlimited (server-side storage)
ExtractionManual rulesLLM-powered entity / preference / event extraction
SubagentsSame as parentIsolated session + typed agent namespace

Hook behavior

HookTriggerAction
UserPromptSubmitEach user turnSearch OV → rank → inject <openviking-context> block within a token budget
StopClaude finishes a responseParse transcript → push new user turns to OV session → commit when pending tokens cross threshold
SessionStartNew / resumed / post-compact sessionOn resume/compact, fetch the latest archive overview and inject it as additional context
PreCompactBefore Claude Code rewrites the transcriptCommit pending messages so they become an archive before CC mutates the transcript
SessionEndClaude Code session closesFinal commit so the last window is archived
SubagentStartParent spawns a subagent via Task toolDerive an isolated OV session ID for the subagent, persist start state
SubagentStopSubagent finishesRead 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 <openviking-context>, <system-reminder>, <relevant-memories>, 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-<ccSessionId>, e.g. cc-7d978bb3-cd9c-4ac6-828d-20965d66b783
  • Subagent: cc-<ccSessionId>__agent-<agentId>, 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/<encoded-cwd>/<session_id>.jsonl.

Troubleshooting

SymptomCauseFix
Plugin not activatingNo ov.conf / ovcli.conf foundRun the one-line installer, or set OPENVIKING_MEMORY_ENABLED=1 plus URL/API_KEY env vars
Hooks fire but recall is emptyOpenViking server not running, or wrong URLcurl "$(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 integrationSee Why a function wrapper?
Remote auth 401 / 403Wrong API key or missing tenant headersVerify OPENVIKING_API_KEY; for multi-tenant, also check OPENVIKING_ACCOUNT / OPENVIKING_USER
Stop hook times outServer slow + sync write pathLeave OPENVIKING_WRITE_PATH_ASYNC=true (default), or raise the Stop timeout in hooks/hooks.json

See also

Released under the Apache-2.0 License.