Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a11e50d
feat(retrieval): assemble auto-recall context server-side via /search…
t0saki Jul 27, 2026
425e63a
refactor(retrieval): give context tiers a per-category default
t0saki Jul 29, 2026
0868e8f
feat(codex): inject profile context on session start
t0saki Jul 31, 2026
ae33996
fix(retrieval): raise rewrite timeout default to 30s
t0saki Jul 31, 2026
c5e1916
docs(agents): document low-latency recall settings
t0saki Jul 31, 2026
40c6894
fix(codex): prefer luna as recall compressor fallback
t0saki Jul 31, 2026
38ded4c
refactor(plugins): unify recall compression setting
t0saki Jul 31, 2026
be0d8c6
feat(plugins): enable recall compression by default
t0saki Jul 31, 2026
20ddaf8
Merge remote-tracking branch 'origin/main' into feat/search-context-a…
t0saki Jul 31, 2026
bc868bb
docs(agents): use absolute links in image docs
t0saki Jul 31, 2026
cf34f2a
fix(retrieval): address context assembly review feedback
t0saki Aug 3, 2026
55d141a
test: trim redundant context assembly coverage
qin-ctx Aug 3, 2026
e7c8b99
Merge origin/main into feat/search-context-assembly
t0saki Aug 3, 2026
de8e1a5
fix(retrieval): address second-round context assembly review
t0saki Aug 4, 2026
a5d5a39
Merge remote-tracking branch 'origin/main' into feat/search-context-a…
t0saki Aug 4, 2026
fffa1d1
chore(plugins): sync shared modules into the zcode snapshot
t0saki Aug 4, 2026
e364d89
fix(retrieval): align context quotas and plugin defaults
t0saki Aug 4, 2026
2f0b401
fix(retrieval): preserve recall compatibility
t0saki Aug 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jobs:
examples/codex-memory-plugin/scripts/ov-credentials.test.mjs \
examples/codex-memory-plugin/scripts/recall-compressor-profile.test.mjs \
examples/codex-memory-plugin/scripts/auto-recall.test.mjs \
examples/codex-memory-plugin/scripts/session-start-commit.test.mjs \
examples/claude-code-memory-plugin/scripts/marketplace.test.mjs \
examples/claude-code-memory-plugin/scripts/skill-experience.test.mjs \
examples/claude-code-memory-plugin/scripts/uri-guard.test.mjs \
Expand Down
33 changes: 33 additions & 0 deletions docs/en/agent-integrations/01-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,36 @@ OpenViking can act as the long-term memory and context backend for many agent ru
## Prerequisite for all integrations

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

## Low-latency recall

Query expansion and recall-result compression are two independent, optional model calls. Disable both in the Agent plugin when response latency matters most; semantic retrieval, budgeting, tier degradation, and cross-turn dedup continue to work.

The same environment variables apply to both the Claude Code and Codex memory plugins:

```bash
export OPENVIKING_RECALL_QUERY_EXPANSION=off
export OPENVIKING_RECALL_COMPRESS=off
```

Both plugins have a local compression path, but expose it differently:

- Claude Code defaults to `recallCompress=auto`: it prefers local `claude -p` (Sonnet with low effort) and falls back to an OpenViking server digest when the local CLI is unavailable. `client` forces local-only compression, while `server` forces server-only compression.
- Codex calls local `codex exec` by default, trying `gpt-5.3-codex-spark` first and then `gpt-5.6-luna` with low effort. It does not enable server-side compression.

The shared default is `recallCompress=auto`. `OPENVIKING_RECALL_COMPRESS=off` disables compression in both plugins; Codex interprets `auto` or `client` as enabling its local compressor. The old Claude Code variable `OPENVIKING_RECALL_REWRITE` remains supported for compatibility, but new configurations should use the unified name.

The same settings can live in `~/.openviking/ovcli.conf`:

```json
{
"url": "https://openviking.example.com",
"api_key": "your-api-key",
"plugin": {
"recallQueryExpansion": "off",
"recallCompress": "off"
}
}
```

Environment variables take precedence over `ovcli.conf`. Restart the Agent after changing these settings so its hook processes reload the configuration. These are plugin-client settings; the server's `ov.conf` does not need to change.
2 changes: 2 additions & 0 deletions docs/en/agent-integrations/02-claude-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ Configuration priority: Environment variables > `ovcli.conf` > `ov.conf` > Built
| `OPENVIKING_MEMORY_ENABLED` | (auto) | Force on/off |
| `OPENVIKING_DEBUG` | `false` | Write logs to `~/.openviking/logs/cc-hooks.log` |

If recall latency matters most, see [Low-latency recall](./01-overview.md#low-latency-recall) for the environment-variable and `ovcli.conf` settings that disable query expansion and result compression.

For multi-tenant deployments, configure `OPENVIKING_ACCOUNT` and `OPENVIKING_USER`. The complete list of environment variables is available in the [plugin README](https://github.com/volcengine/OpenViking/blob/main/examples/claude-code-memory-plugin/README.md#configuration).

</details>
Expand Down
10 changes: 7 additions & 3 deletions docs/en/agent-integrations/04-codex.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Codex Memory Plugin

Equip [Codex](https://developers.openai.com/codex) with persistent memory across sessions. Install it once, and memories will be automatically recalled with every prompt, captured after each turn, and committed before compaction. The plugin also connects Codex to OpenViking's `/mcp` endpoint, enabling the model to call tools such as `find`, `search`, `recall`, and `remember` directly.
Equip [Codex](https://developers.openai.com/codex) with persistent memory across sessions. Install it once, and your OpenViking profile and memory index are loaded at session start, relevant memories are recalled with every prompt, new turns are captured after each response, and sessions are committed before compaction. The plugin also connects Codex to OpenViking's `/mcp` endpoint, enabling the model to call tools such as `find`, `search`, `recall`, and `remember` directly.

Source: [examples/codex-memory-plugin](https://github.com/volcengine/OpenViking/tree/main/examples/codex-memory-plugin) | [Blog: Motivation & demo](https://blog.openviking.ai/post/openviking-coding-agent/)

Expand Down Expand Up @@ -44,11 +44,11 @@ Prerequisites: Node.js >= 22, Codex >= 0.130.0, and the `plugin_hooks` feature e

## Verify

Launch `codex`; the plugin should seamlessly recall memories on every prompt. Set `OPENVIKING_DEBUG=1` to write events to `~/.openviking/logs/codex-hooks.log`.
Launch `codex`; on the first prompt of a session, the `SessionStart` hook should load your profile, and the plugin should then recall relevant memories for every prompt. Set `OPENVIKING_DEBUG=1` to write events to `~/.openviking/logs/codex-hooks.log`.

## How it works

The plugin integrates with Codex's lifecycle by hooking into key events. It searches OpenViking and injects relevant memories before every prompt (`UserPromptSubmit`), appends new turns to the session after each response (`Stop`), and commits the full transcript before compaction (`PreCompact`) to ensure memory extraction processes the entire conversation. Upon starting a fresh session, it also cleans up any orphaned sessions from previous runs.
The plugin integrates with Codex's lifecycle by hooking into key events. On `SessionStart` (`startup`, `clear`, or `resume`), it injects `profile.md` plus URI and abstract indexes for `preferences/` and `entities/` through the same shared, CJK-aware profile builder used by the other coding-agent integrations. It then searches OpenViking and injects relevant memories before every prompt (`UserPromptSubmit`), appends new turns to the session after each response (`Stop`), and commits the full transcript before compaction (`PreCompact`) to ensure memory extraction processes the entire conversation. Upon starting a fresh session, it also cleans up any orphaned sessions from previous runs. A resumed session may combine the fixed profile block with its latest archive digest.

> **Known limitation**: Codex does not fire a hook upon `SIGTERM`, `Ctrl+C`, or `/exit`. Orphaned sessions are recovered during the next `SessionStart` via the idle-TTL sweep (30 minutes) or the active-window heuristic.

Expand All @@ -63,10 +63,14 @@ Credential source: active `ovcli.conf` wins by default (`OPENVIKING_CLI_CONFIG_F
| `OPENVIKING_API_KEY` | — | API key (sent as `Authorization: Bearer`) |
| `OPENVIKING_CLI_CONFIG_FILE` | `~/.openviking/ovcli.conf` | Active CLI config to use for hooks, MCP, and child `ov` commands |
| `OPENVIKING_CREDENTIAL_SOURCE` | `auto` | Set `env` to force env-var credentials instead of active ovcli config |
| `OPENVIKING_NO_AUTO_INJECT` | `false` | Disable fixed session-start profile/background injection without disabling per-prompt recall |
| `OPENVIKING_PROFILE_TOKEN_BUDGET` | `10000` | CJK-aware token budget for `profile.md` plus `preferences/` and `entities/` indexes |
| `OPENVIKING_CODEX_ACTIVE_WINDOW_MS` | `120000` | SessionStart active-window threshold |
| `OPENVIKING_CODEX_IDLE_TTL_MS` | `1800000` | SessionStart idle-TTL sweep threshold |
| `OPENVIKING_DEBUG` | `false` | Write logs to `~/.openviking/logs/codex-hooks.log` |

If recall latency matters most, see [Low-latency recall](./01-overview.md#low-latency-recall) for the environment-variable and `ovcli.conf` settings that disable query expansion and Codex's local result compression.

Additional tuning options (e.g., `OPENVIKING_RECALL_LIMIT`, `OPENVIKING_CAPTURE_ASSISTANT_TURNS`) are documented in the [plugin README](https://github.com/volcengine/OpenViking/blob/main/examples/codex-memory-plugin/README.md#tuning-the-plugin).

</details>
Expand Down
4 changes: 2 additions & 2 deletions docs/en/api/01-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -470,14 +470,14 @@ This catalog follows the routes actually mounted by the server. Each group headi
| POST | `/api/v1/sessions/{session_id}/messages` | Add one message |
| POST | `/api/v1/sessions/{session_id}/messages/batch` | Add messages in a batch |
| POST | `/api/v1/sessions/{session_id}/used` | Record context or skills actually used |
| POST | `/api/v1/search/recall` | Recall memory as injection-ready context |
| POST | `/api/v1/search/recall` | Deprecated: thin preset over `/search` `mode="context"` |
Comment thread
ZaynJarvis marked this conversation as resolved.
Outdated

### [Retrieval](06-retrieval.md) and [Relations](13-relations.md)

| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/v1/search/find` | Semantic search |
| POST | `/api/v1/search/search` | Context-aware search |
| POST | `/api/v1/search/search` | Context-aware search; `mode="context"` returns assembled, injection-ready context |
| POST | `/api/v1/search/grep` | Content pattern search |
| POST | `/api/v1/search/glob` | File pattern matching |
| GET | `/api/v1/relations` | Get resource relations |
Expand Down
165 changes: 165 additions & 0 deletions docs/en/api/06-retrieval.md
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,171 @@ openviking search "similar poster" --image ./poster.png --uri "viking://resource

---

### search(mode="context")

Assemble retrieval results into an injection-ready context block. `mode="list"` (the default) returns the ranked hit list and behaves exactly like the previous `search()`; `mode="context"` opens the assembly face: budgeting, tier degradation, cross-turn dedup and the optional LLM digest all happen server-side in one request.

#### 1. Implementation Overview

Injecting context every turn used to mean searching per type, reading each hit back, and stitching the block together client-side. With assembly on the server, a plugin sends one request and every harness shares one budgeting, degradation and dedup implementation.

**Pipeline**:
1. **L1 query understanding**: optional bounded intent expansion from the session's recent messages (at most 3 queries, timeout fuse, falls back to the original query)
2. **L0 retrieval**: bucketed per `quotas`, or a single whole-scope search when quotas are off
3. **L2 assembly**: tier filling inside the token budget (everyone at their category's default tier first, then leftover budget deepens in score order); an oversized tier falls back instead of being truncated
4. **L3 rewrite**: optional digest with URI citations (timeout fuse; on failure the unrewritten `rendered` is still returned)

**Code entry points**:
- `openviking/server/routers/search.py:_search_context()` - HTTP route branch
- `openviking/retrieve/context_assembler/pipeline.py:assemble_context()` - assembly orchestration
- `openviking/retrieve/context_assembler/budget.py:plan_entries()` - budgeting and tier filling
- `openviking/retrieve/context_assembler/tiers.py` - overview extraction per source type

#### 2. Parameters

**L0 retrieval domain**: `query`, `image_url`, `context_type`, `limit`, `score_threshold`, `filter`, `tags`, `since`/`until` behave as in list mode. `target_uri` is not supported in context mode yet (returns 400); `level` is ignored because `detail` governs tiers.

**L1 query understanding**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `session_id` | str | None | Required to enable query expansion and server-side dedup |
| `query_expansion` | `off` \| `auto` | `auto` | Bounded session-aware expansion; falls back to the original query without a session or on failure |

**L2 assembly**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `max_tokens` | int | 1600 | The single budget parameter, estimated with a CJK-aware heuristic (codepoint ≥ 0x3000 counts 1.5 tok/char, otherwise chars/4) |
| `quotas` | object | None | Bucketed sampling; keys are `events`/`entities`/`preferences`/`experiences`/`resources`/`skills`. `limit` is ignored once active |
| `purpose` | `chat` \| `coding` | None | Preset bucket ratios; applies only when `quotas` is not given |
| `detail` | `abstract` \| `overview` \| `full` \| object | None | Pins every entry to one tier. Omitted, each category takes its default tier (below). Also accepts a per-category object such as `{"events":"overview","preferences":"abstract"}`; categories left out keep their default. `"auto"` is a deprecated spelling and behaves as if omitted |
| `dedup_turns` | int | 0 | Cooldown window in turns; needs `session_id`. Ledger lives at `{session_uri}/.recall_log.json` |
| `exclude_uris` | string[] | [] | Stateless dedup fallback, up to 200 entries, unioned with `dedup_turns` |
| `peer_scope` | `actor` \| `all` | `all` | `actor` searches only the current actor peer |
| `other_peer_penalty` | number \| object | per-category defaults | Score penalty applied to other-peer hits |

**L3 rewrite**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `rewrite` | bool \| `auto` | `false` | Server-side digest rewrite; `auto` engages only when a query_planner model is configured |
| `rewrite_max_bullets` | int | 6 | Digest bullet ceiling (1–20) |

**Tier rules**

- **Default tier per category**: with `detail` omitted, each category lands on the tier below. Only `events` reads a file; every other category costs no read

| Category | Default tier | Leftover budget may reach | Why |
|----------|--------------|---------------------------|-----|
| `events` | overview | full | The one memory type whose body is long enough for `# Summary` extraction to be a real compression |
| `entities` / `preferences` / `experiences` | abstract | abstract | Short bodies, and the writer stores the whole body in the abstract scalar, so abstract already is the complete file |
| `resources` / `skills` | abstract | abstract | The 256-char abstract from semantic processing; bodies can be large or carry credentials, so deepening is opt-in |
| Directory hits | overview | overview | A directory has no abstract, so it reads the `.overview.md` sidecar; a full tier is meaningless for a subtree |

- **Floor**: every result carries at least its `uri`. When a category's default tier yields nothing usable — a resource that never went through semantic processing, or an abstract that busts the per-entry cap — the entry falls back to overview instead of degrading to a bare pointer
- **Explicit `detail`**: pins every entry to that tier as both start and ceiling; entries that do not fit still step down a tier rather than being truncated
- **Overview by source type**: memory files use the leading `# Summary` section, code files use class and function signatures (reusing `code_outline`), long documents use the heading tree plus first paragraph
- **Per-entry cap**: `max_tokens ÷ candidate_count × 2`, applied to every tier except the bare `uri`; a tier exceeding it falls back to the previous tier rather than being truncated. If budget is still left over, one final deepening pass ignores the cap and is bounded only by `max_tokens`

#### 3. Examples

**HTTP API**

```bash
# Basic context assembly
curl -X POST http://localhost:1933/api/v1/search/search \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENVIKING_API_KEY" \
-d '{"query":"what changed on this branch","mode":"context","max_tokens":1600}'

# Session-aware: query expansion plus cross-turn dedup
curl -X POST http://localhost:1933/api/v1/search/search \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENVIKING_API_KEY" \
-d '{
"query":"continue that refactor",
"mode":"context",
"session_id":"cc-1a2b3c",
"query_expansion":"auto",
"dedup_turns":5,
"purpose":"coding",
"max_tokens":3000
}'

# With the server-side digest rewrite
curl -X POST http://localhost:1933/api/v1/search/search \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENVIKING_API_KEY" \
-d '{"query":"tier design","mode":"context","max_tokens":3000,"rewrite":true}'
```

**Response**

```json
{
"status": "ok",
"result": {
"entries": [
{
"uri": "viking://user/default/memories/events/2026/07/14/tier_design.md",
"category": "events",
"score": 0.45,
"detail": "full",
"text": "# Summary\nTiers now take a per-category default\n...",
"origin": "self"
},
{
"uri": "viking://user/default/memories/entities/software/openviking_fs.md",
"category": "entities",
"score": 0.43,
"detail": "abstract",
"text": "OpenViking FS storage layer...",
"origin": "self"
}
],
"rendered": "<memory uri=\"viking://user/default/memories/events/2026/07/14/tier_design.md\" type=\"events\" score=\"0.45\" detail=\"full\">\n# Summary\n...\n</memory>",
"digest": "",
"stats": {
"candidates": 13,
"returned": 13,
"dropped": 0,
"deduped": 0,
"max_tokens": 3000,
"used_tokens": 2510,
"per_entry_cap": 462,
"detail": null,
"tier_counts": {"full": 4, "overview": 2, "abstract": 7},
"fill": {"floor_tokens": 1890, "overview_upgrades": 0, "full_upgrades": 4, "spare_upgrades": 0},
"query_expansion": "used",
"rewrite": "off",
"rewrite_usage": null,
"excluded": 0,
"dedup": {"turns": 5, "status": "ok", "cooled": 2, "turn": 34}
}
}
}
```

| Field | Type | Description |
|-------|------|-------------|
| `entries[].uri` | string | Entry URI, always present at every tier, expandable with the MCP `read` tool |
| `entries[].category` | string | `events`/`entities`/`preferences`/`experiences`/`resources`/`skills` |
| `entries[].detail` | string | Tier actually served: `full`, `overview`, `abstract` or `uri` |
| `entries[].text` | string | Body for that tier; empty at the `uri` tier |
| `rendered` | string | Flat XML context block, ready to inject |
| `digest` | string | Digest when the rewrite succeeded, empty string otherwise |
| `stats` | object | Budget usage, tier distribution, expansion and rewrite status, dedup ledger state; carries `retrieval_errors` when a retrieval scope failed, so a broken index is distinguishable from having no relevant memories |

**Validation rules**

- Any context-only parameter sent explicitly under `mode="list"` → 400
- `target_uri` under `mode="context"` → 400
- Unknown `quotas` key → 400
- Fields ignored in context mode (`level`, and `limit` once quotas are active) are reported in `stats.ignored`

---

### grep()

Search content by pattern (regex).
Expand Down
Loading
Loading