Skip to content

Commit 8c01e97

Browse files
authored
feat(cc-memory-plugin): persistent session and recall redesign (#1615)
## feat(cc-memory-plugin): implement persistent sessions, native MCP, and async write path This update transitions the Claude Code memory plugin from a one-shot capture model to a persistent, per-session integration with OpenViking. It introduces native MCP support directly from the FastAPI server, significantly expands the tool surface, and optimizes performance via detached async write hooks. ### Core Engineering & Capabilities * **Persistent Sessions:** Reworked lifecycle hooks (SessionStart, PreCompact, SessionEnd) to maintain stable session IDs across the entire Claude Code conversation. * **Native MCP Endpoint:** Replaced the Node.js MCP subprocess with a native `/mcp` endpoint on the OpenViking server. * Expands to 9 specialized tools: `search`, `read`, `list`, `store`, `add_resource`, `forget`, `grep`, `glob`, and `health`. * Propagates identity headers (`X-OpenViking-Account`, `X-OpenViking-User`) through the MCP transport. * **Async Write Path:** Introduced a detached worker pattern for `auto-capture`, `session-end`, and `subagent-stop` hooks. Claude Code no longer blocks on network round-trips to the OpenViking server. * **Multi-Source Recall:** Enhanced `auto-recall` to search across memories, resources, and skills with URI-deduplication and score-based filtering. ### Configuration & Integration * **Unified Auth:** Standardized on `Authorization: Bearer` tokens. * **Config Resolution:** Established a clear priority chain: **Env Vars → ovcli.conf → ov.conf → Defaults**. * Added comprehensive environment variable coverage for all tuning fields (e.g., `OPENVIKING_SCORE_THRESHOLD`, `OPENVIKING_COMMIT_TOKEN_THRESHOLD`). * **One-Line Installer:** Added an interactive bash installer (`install.sh`) that handles dependencies, `ovcli.conf` setup, and marketplace registration, with support for both self-hosted and Volcengine Cloud options. ### Bug Fixes & Refinement * **Self-Injection Prevention:** Implemented block-stripping logic in `auto-capture` to prevent the plugin from re-storing its own injected context blocks into the memory pool. * **Session Bypass:** Fixed a bug where `session-start` and `subagent-start` ignored bypass patterns. * **Subagent Isolation:** Implemented `SubagentStart/Stop` hooks with isolated session IDs and specialized agent headers for memory segregation. * **Docs & Maintenance:** Bumped version to `0.2.2`; added comprehensive agent integration guides; fixed Vue interpolation and markdown fence issues in documentation.
1 parent 6ae4a93 commit 8c01e97

43 files changed

Lines changed: 10145 additions & 2468 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/.vitepress/config.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const sectionNames: Record<string, string> = {
1111
'getting-started': 'Getting Started',
1212
concepts: 'Concepts',
1313
guides: 'Guides',
14+
'agent-integrations': 'Agent Integrations',
1415
api: 'API Reference',
1516
faq: 'FAQ',
1617
about: 'About',
@@ -21,6 +22,7 @@ const zhSectionNames: Record<string, string> = {
2122
'getting-started': '快速开始',
2223
concepts: '核心概念',
2324
guides: '指南',
25+
'agent-integrations': 'Agent 集成',
2426
api: 'API 参考',
2527
faq: '常见问题',
2628
about: '关于',
@@ -73,7 +75,7 @@ function sidebarSection(dir: string, title: string, collapsed = true): DefaultTh
7375

7476
function localizedGuideSidebarItems(locale: 'en' | 'zh'): DefaultTheme.SidebarItem[] {
7577
const labels = locale === 'zh' ? zhSectionNames : sectionNames
76-
const sections = ['getting-started', 'concepts', 'guides']
78+
const sections = ['getting-started', 'concepts', 'guides', 'agent-integrations']
7779

7880
return sections.map((section, index) =>
7981
sidebarSection(`${locale}/${section}`, labels[section], index !== 0)
@@ -95,14 +97,14 @@ const designSidebar: DefaultTheme.SidebarItem[] = [
9597
]
9698

9799
const enNav: DefaultTheme.NavItem[] = [
98-
{ text: navLabels.en.guide, link: '/en/getting-started/01-introduction', activeMatch: '/en/(getting-started|concepts|guides)/' },
100+
{ text: navLabels.en.guide, link: '/en/getting-started/01-introduction', activeMatch: '/en/(getting-started|concepts|guides|agent-integrations)/' },
99101
{ text: navLabels.en.api, link: '/en/api/01-overview', activeMatch: '/en/api/' },
100102
{ text: navLabels.en.faq, link: '/en/faq/faq', activeMatch: '/en/faq/' },
101103
{ text: navLabels.en.changelog, link: '/en/about/02-changelog', activeMatch: '/en/about/' }
102104
]
103105

104106
const zhNav: DefaultTheme.NavItem[] = [
105-
{ text: navLabels.zh.guide, link: '/zh/getting-started/01-introduction', activeMatch: '/zh/(getting-started|concepts|guides)/' },
107+
{ text: navLabels.zh.guide, link: '/zh/getting-started/01-introduction', activeMatch: '/zh/(getting-started|concepts|guides|agent-integrations)/' },
106108
{ text: navLabels.zh.api, link: '/zh/api/01-overview', activeMatch: '/zh/api/' },
107109
{ text: navLabels.zh.faq, link: '/zh/faq/faq', activeMatch: '/zh/faq/' },
108110
{ text: navLabels.zh.changelog, link: '/zh/about/02-changelog', activeMatch: '/zh/about/' }
@@ -247,6 +249,7 @@ export default defineConfig({
247249
'/en/getting-started/': localizedGuideSidebarItems('en'),
248250
'/en/concepts/': localizedGuideSidebarItems('en'),
249251
'/en/guides/': localizedGuideSidebarItems('en'),
252+
'/en/agent-integrations/': localizedGuideSidebarItems('en'),
250253
'/en/api/': localizedReferenceSidebarItems('en'),
251254
'/en/about/': localizedAboutSidebarItems('en'),
252255
'/design/': designSidebar
@@ -277,6 +280,7 @@ export default defineConfig({
277280
'/zh/getting-started/': localizedGuideSidebarItems('zh'),
278281
'/zh/concepts/': localizedGuideSidebarItems('zh'),
279282
'/zh/guides/': localizedGuideSidebarItems('zh'),
283+
'/zh/agent-integrations/': localizedGuideSidebarItems('zh'),
280284
'/zh/api/': localizedReferenceSidebarItems('zh'),
281285
'/zh/about/': localizedAboutSidebarItems('zh')
282286
},
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Agent Integrations Overview
2+
3+
OpenViking can act as the long-term memory and context backend for many agent runtimes. This section collects the integrations that already exist — pick the one that matches your agent.
4+
5+
## Which integration should I use?
6+
7+
| If you use… | Use this |
8+
|-------------|----------|
9+
| **Claude Code** | [Claude Code Memory Plugin](./02-claude-code.md) — auto-recall + auto-capture via hooks, no MCP tool calls required from the model |
10+
| **OpenClaw** | [OpenClaw Plugin](./03-openclaw.md) — context-engine + hooks + tools + runtime manager, deep lifecycle integration |
11+
| **Codex / OpenCode** | [Other community plugins](./04-other-plugins.md) — MCP-only and tool-mechanism variants |
12+
| **Cursor / Trae / Manus / Claude Desktop / ChatGPT / …** | [MCP Integration Guide](../guides/06-mcp-integration.md) — point any MCP-compatible client at the built-in `/mcp` endpoint |
13+
| **Hermes Agent (Nous Research)** | [Hermes — OpenViking memory provider](https://hermes-agent.nousresearch.com/docs/user-guide/features/memory-providers#openviking) — first-class OpenViking memory provider, no plugin install needed |
14+
15+
## Two integration depths
16+
17+
The plugins listed here go beyond what a generic MCP client can do:
18+
19+
- **Generic MCP clients** call OpenViking on demand through tools the model decides to invoke. Setup is one config snippet.
20+
- **Hooks-based plugins** (Claude Code, OpenClaw) drive recall and capture from runtime lifecycle events — every prompt, every turn, session start/end, compact, subagent spawn. The model doesn't need to "remember to recall."
21+
22+
For agents whose runtime exposes hooks or a context-engine slot, the hooks-based path is usually the better default.
23+
24+
## Prerequisite for all integrations
25+
26+
Every integration on this page connects to a running OpenViking server. If you don't have one yet, follow the [Quickstart Guide](../getting-started/02-quickstart.md). The default endpoint is `http://localhost:1933`; remote use requires an API key (see [Authentication](../guides/04-authentication.md)).
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# Claude Code Memory Plugin
2+
3+
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.
4+
5+
Source: [examples/claude-code-memory-plugin](https://github.com/volcengine/OpenViking/tree/main/examples/claude-code-memory-plugin)
6+
7+
## Quick Start
8+
9+
### One-line installer (recommended)
10+
11+
```bash
12+
bash <(curl -fsSL https://raw.githubusercontent.com/volcengine/OpenViking/main/examples/claude-code-memory-plugin/setup-helper/install.sh)
13+
```
14+
15+
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.
16+
17+
If you'd rather do it by hand, follow the three steps below.
18+
19+
### Manual setup
20+
21+
#### 1. Wrap `claude` to inject env from `ovcli.conf`
22+
23+
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`:
24+
25+
```bash
26+
claude() {
27+
if [ -f ~/.openviking/ovcli.conf ]; then
28+
OPENVIKING_URL=$(jq -r '.url' ~/.openviking/ovcli.conf) \
29+
OPENVIKING_API_KEY=$(jq -r '.api_key' ~/.openviking/ovcli.conf) \
30+
command claude "$@"
31+
else
32+
command claude "$@"
33+
fi
34+
}
35+
```
36+
37+
Re-source your rc and verify (use `~/.bashrc` if you're on bash):
38+
39+
```bash
40+
source ~/.zshrc # or: source ~/.bashrc
41+
type claude # expect: claude is a shell function
42+
```
43+
44+
Inside Claude Code, run `/mcp` after the next start — the OpenViking entry should show your remote URL with valid auth.
45+
46+
> **Don't have `ovcli.conf` yet?** See the [CLI section of the Deployment Guide](../guides/03-deployment.md#cli) to set one up.
47+
>
48+
> **Pure local mode** (`http://127.0.0.1:1933`, no auth)? Skip this step — the plugin uses the local default silently.
49+
>
50+
> **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.
51+
52+
#### 2. Install the plugin
53+
54+
From the OpenViking repo root:
55+
56+
```bash
57+
claude plugin marketplace add "$(pwd)/examples" --scope local
58+
claude plugin install claude-code-memory-plugin@openviking-plugins-local --scope local
59+
```
60+
61+
> Local install 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.
62+
63+
#### 3. Start Claude Code
64+
65+
```bash
66+
claude
67+
```
68+
69+
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`.
70+
71+
## Why a function wrapper?
72+
73+
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.
74+
75+
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).
76+
77+
**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.
78+
79+
## Configuration
80+
81+
### Resolution priority
82+
83+
Every plugin field follows this chain (highest → lowest):
84+
85+
1. **Environment variables** (`OPENVIKING_*`)
86+
2. **`ovcli.conf`** — connection fields only (`url`, `api_key`, `account`, `user`, `agent_id`)
87+
3. **`ov.conf`** — server config; the plugin reads `server.url`, `server.root_api_key`, and a legacy `claude_code` block if present
88+
4. **Built-in defaults** (`http://127.0.0.1:1933`, no auth)
89+
90+
> ⚠️ **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.
91+
92+
### Key environment variables
93+
94+
| Env Var | Default | Description |
95+
|--------------------------------------------------|---------------|-----------------------------------------------------------------------|
96+
| `OPENVIKING_URL` / `OPENVIKING_BASE_URL` || Full server URL |
97+
| `OPENVIKING_API_KEY` / `OPENVIKING_BEARER_TOKEN` || API key; sent as `Authorization: Bearer <key>` |
98+
| `OPENVIKING_AUTO_RECALL` | `true` | Enable auto-recall on every user prompt |
99+
| `OPENVIKING_RECALL_LIMIT` | `6` | Max memories to inject per turn |
100+
| `OPENVIKING_RECALL_TOKEN_BUDGET` | `2000` | Token budget for inline content; over-budget items degrade to URI hints |
101+
| `OPENVIKING_AUTO_CAPTURE` | `true` | Enable auto-capture; also gates write hooks |
102+
| `OPENVIKING_BYPASS_SESSION` | `false` | One-shot: `1`/`true` skips every hook in the current process |
103+
| `OPENVIKING_BYPASS_SESSION_PATTERNS` | `""` | CSV of glob patterns matched against `session_id` or `cwd` |
104+
| `OPENVIKING_MEMORY_ENABLED` | (auto) | `0`/`false`=force off; `1`/`true`=force on |
105+
| `OPENVIKING_DEBUG` | `false` | Write hook logs to `~/.openviking/logs/cc-hooks.log` |
106+
107+
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).
108+
109+
### Bypass a session
110+
111+
Use Claude Code in a `/tmp` PoC directory without polluting your long-term memory:
112+
113+
```bash
114+
# Persistent: any session whose session_id or cwd matches a pattern
115+
export OPENVIKING_BYPASS_SESSION_PATTERNS='/tmp/**,**/scratch/**,/Users/me/Dev/throwaway/*'
116+
117+
# Or one-shot:
118+
OPENVIKING_BYPASS_SESSION=1 claude
119+
```
120+
121+
When bypass is active, every hook approves immediately without contacting OpenViking.
122+
123+
## Compared to Claude Code's built-in `MEMORY.md`
124+
125+
This plugin **complements** Claude Code's native memory system, it doesn't replace it:
126+
127+
| Feature | Built-in `MEMORY.md` | OpenViking plugin |
128+
|--------------|-----------------------------------|----------------------------------------------------|
129+
| Storage | Flat markdown | Vector DB + structured extraction |
130+
| Search | Loaded into context wholesale | Semantic similarity + ranking + token budget |
131+
| Scope | Per-project | Cross-project, cross-session, cross-agent |
132+
| Capacity | ~200 lines (context limit) | Unlimited (server-side storage) |
133+
| Extraction | Manual rules | LLM-powered entity / preference / event extraction |
134+
| Subagents | Same as parent | Isolated session + typed agent namespace |
135+
136+
## Hook behavior
137+
138+
| Hook | Trigger | Action |
139+
|-----------------------|--------------------------------------------|---------------------------------------------------------------------------------------------------|
140+
| `UserPromptSubmit` | Each user turn | Search OV → rank → inject `<openviking-context>` block within a token budget |
141+
| `Stop` | Claude finishes a response | Parse transcript → push new user turns to OV session → commit when pending tokens cross threshold |
142+
| `SessionStart` | New / resumed / post-compact session | On `resume`/`compact`, fetch the latest archive overview and inject it as additional context |
143+
| `PreCompact` | Before Claude Code rewrites the transcript | Commit pending messages so they become an archive before CC mutates the transcript |
144+
| `SessionEnd` | Claude Code session closes | Final commit so the last window is archived |
145+
| `SubagentStart` | Parent spawns a subagent via Task tool | Derive an isolated OV session ID for the subagent, persist start state |
146+
| `SubagentStop` | Subagent finishes | Read subagent transcript → push to isolated session with subagent-typed agent header → commit |
147+
148+
`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.
149+
150+
`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.
151+
152+
## Troubleshooting
153+
154+
| Symptom | Cause | Fix |
155+
|--------------------------------------------|----------------------------------------------------------------|----------------------------------------------------------------------------------------------|
156+
| 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 |
157+
| Hooks fire but recall is empty | OpenViking server not running, or wrong URL | `curl "$(jq -r '.url' ~/.openviking/ovcli.conf)/health"` |
158+
| 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) |
159+
| Remote auth 401 / 403 | Wrong API key or missing tenant headers | Verify `OPENVIKING_API_KEY`; for multi-tenant, also check `OPENVIKING_ACCOUNT` / `OPENVIKING_USER` |
160+
| `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` |
161+
162+
## See also
163+
164+
- [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
165+
- [Migration notes](https://github.com/volcengine/OpenViking/blob/main/examples/claude-code-memory-plugin/MIGRATION.md) — upgrading from earlier plugin versions
166+
- [MCP Integration Guide](../guides/06-mcp-integration.md) — for MCP tool parameters and other clients
167+
- [Deployment Guide → CLI](../guides/03-deployment.md#cli)`ovcli.conf` setup

0 commit comments

Comments
 (0)