Skip to content

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's SKILL.md with openviking_read before 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 context hook 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/list returns, each registered under an openviking_ prefix, including openviking_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 shared mcpEnabled: false turns the tool surface off and leaves recall, capture, and takeover running.
  • Skills: the extension ships openviking-memory, openviking-skills, and ov-experience-memory from its skills/ directory and adds them through pi's resources_discover event, so the model knows when to use which OpenViking tool. They are left out when mcpEnabled is false.
  • 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, plus tools ✗ when the handshake failed; the /viking command prints status, including the registered tool count or the last handshake error, and /viking commit forces a commit.
  • Accidental read/grep/find/ls/write/edit calls whose path is a viking:// URI are blocked with a hint pointing to the matching openviking_* tool. A bash command that carries a viking:// URI still runs, and its result ends with a notice suggesting openviking_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.

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

bash
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-scripts

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

bash
node ~/.pi/agent/extensions/openviking/scripts/setup.mjs

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

json
{
  "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 ​

IssueWhat 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 loadConfirm ~/.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 OpenVikingVerify OPENVIKING_API_KEY; for trusted-mode deployments, also verify OPENVIKING_ACCOUNT and OPENVIKING_USER
tools ✗ in the footer, or no openviking_* tools in the sessionRun /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 emptyConfirm the server has indexed memories and the prompt is longer than minQueryLength

For the full tool, configuration, and design reference, see the extension README.

See also ​

Open source under the AGPL-3.0 License. Font licenses