pi
Give pi cross-session long-term memory and context takeover. Recall, capture, and takeover run on pi's native extension API: recall happens automatically before every prompt, conversation turns are captured after every turn, and OpenViking can replace committed local history with an archive overview in pi's context hook. The extension uses the official MCP client and registers the server's MCP tools as native pi tools.
Source: examples/pi-coding-agent-extension
How it works
- Session start builds your OpenViking profile block and restores takeover state, including the branch watermark used by
pi -c. The block holds your profile, memory indexes, and an<available-skills>catalog of your own and account-shared OpenViking skills, and it is added to pi's system prompt on every turn; the model reads a skill'sSKILL.mdwithopenviking_readbefore following it. - Every prompt runs semantic recall against the OpenViking server and injects matching memories as hidden context.
- Every turn captures user and assistant messages (including structured tool calls) into an OpenViking session; takeover mode commits once synced-token pressure crosses the threshold and then keeps only the recent live tail in model context.
- Context takeover is enabled by default. The
contexthook injects[OpenViking Session Context]from the latest archive overview before recall is added to the latest user turn. - Model-callable tools are whatever the server's MCP
tools/listreturns, each registered under anopenviking_prefix, includingopenviking_find,openviking_search,openviking_read,openviking_list,openviking_tree,openviking_grep,openviking_glob,openviking_remember,openviking_write,openviking_edit,openviking_add_resource,openviking_add_skill,openviking_list_watches,openviking_cancel_watch,openviking_forget,openviking_health. The extension keeps no tool catalogue of its own, so a server that adds or drops a tool changes pi's tool surface at the next session, with no extension release. The sharedmcpEnabled: falseturns the tool surface off and leaves recall, capture, and takeover running. - Skills: the extension ships
openviking-memory,openviking-skills, andov-experience-memoryfrom itsskills/directory and adds them through pi'sresources_discoverevent, so the model knows when to use which OpenViking tool. They are left out whenmcpEnabledisfalse. - The tool handshake never fails startup. If only the MCP handshake fails, REST recall, capture, and takeover can still work. If the server is unavailable or authentication fails, those features also fail or fall back; the bridge retries on a later turn, so a server started after pi is picked up without a restart.
- A footer status segment (
OV ✓ …) shows connection and capture state, plustools ✗when the handshake failed; the/vikingcommand prints status, including the registered tool count or the last handshake error, and/viking commitforces a commit. - Accidental
read/grep/find/ls/write/editcalls whose path is aviking://URI are blocked with a hint pointing to the matchingopenviking_*tool. Abashcommand that carries aviking://URI still runs, and its result ends with a notice suggestingopenviking_read/openviking_search.
Extension 0.4.0 replaced the seven hand-written viking_* REST tools with this mirrored surface, with no alias period: a --tools / --exclude-tools allowlist that pins the old names must be updated by hand, or those tools silently disappear. Some semantics moved with the names — openviking_remember writes through its own session that the server commits immediately, openviking_read reads file content, with line pagination (the old level="abstract" / "overview" tiers are gone; use openviking_search(mode="context", detail="overview") or openviking_tree(include_abstract=true) for the condensed views), and openviking_forget deletes the URI you give it instead of the strongest match of a query. Context takeover still injects the archive overview itself over REST, so that path is unchanged.
Tool calls and results are captured as dedicated tool parts, and tool_output is reported verbatim. Truncation is the server's job: output larger than tool_output_externalization.threshold_chars (default 20000) is written to the session's tool-result store, and the part keeps a synopsis stub plus tool_output_ref, so the original stays readable through /api/v1/sessions/{id}/tool-results.
Prerequisites
- Node.js 22.19.0+ and npm
- pi coding agent (
npm i -g @earendil-works/pi-coding-agent) - An OpenViking HTTP server — verify with
curl http://localhost:1933/health
Install
pi shares the unified installer with the other memory plugins. It asks which harnesses to install and for your OpenViking credentials; every step is idempotent.
curl -fsSL https://openviking.ai/install | bash
# AI agents: not sure about running this? The script's header says what it does and how to verify it.The installer installs the locked npm dependencies before replacing the extension in ~/.pi/agent/extensions/openviking and configures ~/.openviking/ovcli.conf. That directory is one of pi's auto-discovery roots, so pi loads the extension on the next start — no pi install needed. (Running pi install on the same path would load it twice — once via auto-discovery, once via the packages entry — producing a duplicate /viking command.)
Manual copy from a repo checkout also works:
git clone https://github.com/volcengine/OpenViking.git
cd OpenViking
node examples/memory-plugin-shared/sync.mjs
mkdir -p ~/.pi/agent/extensions/openviking
cp -R examples/pi-coding-agent-extension/. ~/.pi/agent/extensions/openviking/
npm ci --prefix ~/.pi/agent/extensions/openviking --omit=dev --ignore-scriptsThe extension imports its shared modules from a shared/ directory that is not in git; sync.mjs generates it, so run it before copying. After a git pull, run sync.mjs again before copying the update.
Configure
Credentials resolve from OPENVIKING_* environment variables, then ~/.openviking/ovcli.conf, then ~/.openviking/ov.conf — shared with the Claude Code, Codex, and OpenCode plugins. Run the wizard once if you have not configured them yet:
node ~/.pi/agent/extensions/openviking/scripts/setup.mjsBehavior and peer-scoping knobs live in the plugin section of ~/.openviking/ovcli.conf, shared with the other memory plugins. Keys under plugin.pi apply to this extension only and override the shared ones; connection and authentication credentials still come from the sources above:
{
"plugin": {
"recallPeerScope": "all",
"scoreThreshold": 0.35,
"recallTokenBudget": 2000,
"profileTokenBudget": 10000,
"skillCatalog": true,
"skillCatalogTokenBudget": 1200,
"resumeContextBudget": 32000,
"commitTokenThreshold": 20000,
"captureAssistantTurns": true,
"pi": {
"workspacePeer": true,
"peerSource": "git",
"takeoverEnabled": true,
"takeoverTokenThreshold": 30000,
"takeoverKeepRecentTurns": 3,
"takeoverOverviewBudget": 3000,
"takeoverOverviewPollMs": 2000,
"takeoverOverviewPollMax": 15,
"bypassSessionPatterns": []
}
}
}Settings resolve highest priority first: OPENVIKING_* environment variables, the workspace's .openviking/config.json and config.local.json, plugin.pi, plugin, then the built-in defaults. autoCapture: false — the extension's older spelling is syncTurns — stops it sending turns back, and autoRecall: false stops it retrieving.
commitKeepRecentCount (OPENVIKING_COMMIT_KEEP_RECENT_COUNT) is no longer read: commits outside takeover archive all captured messages, and takeover sends the exact message count of the turns it keeps. Delete it from existing config.
profileTokenBudget covers the profile and memory indexes in the session-start block. The <available-skills> catalog has its own budget, skillCatalogTokenBudget (default 1200, env OPENVIKING_SKILL_CATALOG_TOKEN_BUDGET): your own skills come first, then the ones shared under viking://agent/skills, leaving out a shared skill that has the same name as one of yours. When the descriptions do not fit, the catalog lists names only (with a ... +N more tail if even the names do not all fit), and when not even one name fits, a one-line count. skillCatalog: false (OPENVIKING_SKILL_CATALOG=0) or a budget of 0 turns the catalog off; with no skills, or on a server without GET /api/v1/skills, it is left out.
OPENVIKING_PEER_ID outranks every file. Below it the more specific layer wins, so a workspace peer.id or a peerId set in the plugin section takes precedence over ovcli.conf's actor_peer_id and ov.conf's pi.peerId. Any peer named by one of those layers takes precedence over the workspace-derived peer, which is used only when workspacePeer is enabled and no layer names one.
peerSource decides how that workspace peer is derived. The default "git" uses the repository's normalized origin URL (git@github.com:volcengine/OpenViking.git becomes github.com-volcengine-openviking), falling back to the repository root path, so every clone, worktree, and subdirectory of one repository shares a single peer; outside a repository no peer is sent at all, and what is remembered there goes to your user-level space at viking://user/<you>/memories. "cwd" restores the earlier behavior — the working directory with every non-alphanumeric character replaced by - — "none" (like OPENVIKING_WORKSPACE_PEER=0) sends no peer at all, and a template such as "team-{dir}", or a list of templates tried in order, builds your own. To give a directory outside a repository its own memory, set OPENVIKING_PEER_ID for it (Give a Directory Its Own Peer).
Takeover is fail-open: if pending writes cannot flush, commit fails, the archive overview is not ready, or the branch fingerprint no longer matches, pi keeps full local history or falls back to its default compaction. Set OPENVIKING_DEBUG_LOG=/tmp/ov-pi.log when validating boundary advances; the log is JSON Lines, one record per event. OV_DEBUG_LOG is a deprecated alias that still works.
Verify
Start pi. Once connected, the footer shows an OV ✓ status segment, and /viking prints the current session mapping and how many openviking_* tools registered. Ask pi about something you mentioned in an earlier session to confirm recall.
Troubleshooting
| Issue | What to check |
|---|---|
OV ✗ in the footer or "server not reachable" | curl http://localhost:1933/health; check the endpoint in ~/.openviking/ovcli.conf |
| Extension does not load | Confirm ~/.pi/agent/extensions/openviking/index.ts exists (the directory is auto-discovered); re-run the installer. Do not register it with pi install — that loads it twice |
/viking command appears twice (viking:1 / viking:2) | An older installer registered the path with pi install, leaving a redundant packages entry so it loads once via auto-discovery and once via packages; run pi remove ~/.pi/agent/extensions/openviking to drop that entry (edits settings only, leaves the files) |
Load fails with a missing shared/*.mjs module (for example shared/capture-utils.mjs) | The copy was made without running sync.mjs first. Run node examples/memory-plugin-shared/sync.mjs from the repository root and copy again, or re-run the installer |
Import errors mentioning @mariozechner/* | Stale copy from before pi's move to @earendil-works/* — re-run the installer |
| 401 / 403 from OpenViking | Verify OPENVIKING_API_KEY; for trusted-mode deployments, also verify OPENVIKING_ACCOUNT and OPENVIKING_USER |
tools ✗ in the footer, or no openviking_* tools in the session | Run /viking for the full handshake error. A root API key is rejected on /mcp with 403 — create a user or admin key. mcpEnabled: false disables the tools deliberately and is not reported as a failure |
| Recall is empty | Confirm the server has indexed memories and the prompt is longer than minQueryLength |
For the full tool, configuration, and design reference, see the extension README.