Skip to content

feat(pi): add a memsearch plugin for the pi coding agent - #650

Open
jenkins5 wants to merge 14 commits into
zilliztech:mainfrom
jenkins5:feat/pi-plugin
Open

feat(pi): add a memsearch plugin for the pi coding agent#650
jenkins5 wants to merge 14 commits into
zilliztech:mainfrom
jenkins5:feat/pi-plugin

Conversation

@jenkins5

Copy link
Copy Markdown

Summary

Adds pi (earendil-works/pi) as a fifth platform plugin. It follows the existing plugin contract exactly — journals in <project>/.memsearch/memory/, collection name from the shared derive-collection.sh, the shared summarize.txt prompt, and the shared maintenance-runner.py — so memories written from pi are searchable from Claude Code / Codex / OpenCode / OpenClaw and vice versa.

plugins/pi/ is a TypeScript pi extension (registerTool + lifecycle hooks), closest in shape to the OpenClaw plugin.

Tools memory_search (L1), memory_get (L2), memory_transcript (L3)
Hooks session_start, before_agent_start, agent_settled
Skills memory-recall, memory-config, memory-to-skill
Transcript transcript.py — tree-aware pi JSONL parser
Summarizer memsearch-managed provider → pi -p → truncated raw text
Maintenance project_review, user_profile, memory_to_skill via the shared runner

Design decisions worth a look

Capture on agent_settled, not agent_end. pi may auto-retry, auto-compact, or drain queued messages after agent_end fires, which would record the same exchange more than once. agent_settled fires only when pi will not continue on its own. Capture is additionally skipped when the session leaf has not moved, and serialized through a queue so a slow summary can't interleave the journal with the next turn.

Tree-aware transcripts. pi sessions are a tree (id / parentId), and /fork or /clone put several branches in one file. Reading in line order would interleave abandoned branches with the live conversation, so transcript.py walks parents from a target entry to yield exactly one root-to-leaf path. Anchors record turn:<leafId> to pin the tree position for drill-down.

pi is re-invoked through process.argv[1], not PATH. Under a version manager the pi on PATH is a shim that refuses to run when the working directory has no project-local install — the common case. The plugin uses the running process's own node binary and entry script, and hands that invocation to the maintenance runner via MEMSEARCH_PI_BIN.

No shell for maintenance, including skill distillation. pi's --tools is a tool-level allowlist, so there is no equivalent of the Claude Code plugin's narrow Bash(memsearch transcript:*) hole. Native maintenance therefore runs with --no-tools; distilled skills lose exact-command fidelity, which seemed the better trade for an unattended background run. Happy to revisit if you'd rather match Claude Code's behaviour.

No file watcher, matching the OpenCode and OpenClaw plugins — the index is refreshed at session start and after each capture.

Changes outside plugins/pi/

Kept as small as I could. Two are worth calling out:

  • src/memsearch/cli.py — adds pi to the config init wizard. config init builds the plugins table from a hardcoded platform list and save_config overwrites the file, so without this a user who had configured [plugins.pi] would silently lose it the next time they ran the wizard.
  • plugins/_shared/scripts/maintenance-runner.py — a pi branch in run_native_provider plus the argparse choice. Copies re-synced to all five plugins; the existing byte-equality test now covers pi.

The rest is registration and counts: config.py (PluginsConfig + key maps), scripts/sync-prompts.sh, README badge and platform lists, docs/platforms/ comparison table, mkdocs nav, CONTRIBUTING, CLAUDE.md.

Testing

  • pytest — 287 passed, 7 skipped
  • plugins/pi npm test — 12 passed (turn extraction, cold-start preview, noise filtering)
  • node --check on both entrypoints, plus a real module import; npm pack --dry-run
  • Maintenance runner exercised end-to-end with --platform pi (all three tasks correctly report disabled by default), and its native command construction verified against paths containing spaces
  • New pi job added to .github/workflows/test.yml

Notes

Not published to npm — the README installs from a local clone, because pi's git source resolves whole repositories rather than subdirectories. Version left at 0.1.0; happy to align it with whatever you'd prefer for a first release.

jenkins5 added 14 commits July 25, 2026 16:49
- add plugins/pi with a session_start hook that ensures the onnx default config and runs an initial background index
- copy derive-collection.sh byte-identical from claude-code so pi shares collections with the other agent plugins
- add package.json declaring the pi extension and skill resources
- register memory_search and memory_get tools wrapping the memsearch CLI
- inject recent daily journals into the system prompt via before_agent_start
- add a memory-recall skill guiding the search-then-expand flow
- extract journal preview helpers into context.ts and memoize the CLI and collection lookups
- add a `pi` field to PluginsConfig plus entries in both plugin key/field maps, so `--plugin pi` resolves its config instead of raising KeyError
- mention pi in the two `--plugin` help strings
- sync shared prompt templates into plugins/pi
- capture the settled turn via agent_settled, summarize it, and append to today's journal with a session anchor
- fall back from the memsearch-managed LLM to `pi -p`, then to truncated raw text
- invoke pi through the running process's node binary and entry script, since the PATH entry is a Volta shim that fails outside a project install
- close child stdin when nothing is piped, otherwise `pi -p` waits for an EOF that never arrives
- add MEMSEARCH_DEBUG to surface failures that the best-effort catch blocks swallow
- add transcript.py, which resolves one root-to-leaf branch via id/parentId so abandoned branches are excluded from pi's tree-structured sessions
- register the memory_transcript tool wrapping it
- record the session leaf id as `turn:` in the journal anchor so drill-down can target the exact tree position
- extend the memory-recall skill to describe all three layers
- add memory-config covering diagnosis, plugins.pi.* keys, provider routing, prompt overrides, and troubleshooting
- add memory-to-skill covering candidate capture, review, install, and history mining
- point history mining at memory_transcript, since pi stores transcripts as JSONL rather than in a database
- document pi's own skill discovery paths for install targets
- reset the session heading flag and last-captured leaf on session_start, since /new, /resume and /fork reuse the same process and would otherwise file new turns under the previous session's heading
- skip capture when the leaf is unchanged, so an agent_settled that fires without a new exchange does not append the turn twice
- serialize captures through a queue, because summarizing takes long enough for the next turn to settle mid-write and interleave the journal
- resolve plugins.pi.summarize.provider once per session and only shell out to `memsearch summarize` when it names a real provider
- an unset or "native" provider means the host agent summarizes, so the previous unconditional attempt paid a CLI start-up on every capture just to fail
- add plugins/pi/README.md and docs/platforms/pi/ covering install, architecture, and the three memory tools
- add a pi job to the test workflow, mirroring opencode plus a transcript.py syntax check
- add unit tests for turn extraction, journal previews, and noise filtering
- register pi across the mkdocs nav, root README, platform comparison, CONTRIBUTING, and the version table
- note that project-scope installs stay inert until the project is trusted, which surprised us during testing
- pi's git source resolves whole repositories, not subdirectories, so `git:…#plugins/pi` fails to clone; document clone-then-local-path instead
- note that a local-path install records the path rather than copying, so `git pull` updates the plugin in place
- derive the collection from the repository root rather than the working directory; starting pi in a subdirectory produced a different collection than the other plugins, silently breaking cross-agent sharing
- honor MEMSEARCH_DIR as a global scope, matching the claude-code and codex plugins; the memory-config skill already documented it while the code ignored it
- resolve scope once per working directory, since the git lookup spawns a process
The pi plugin documented plugins.pi.project_review / user_profile /
memory_to_skill but shipped no runner, so enabling them did nothing.

Add a pi branch to the shared maintenance runner and wake it after each
capture, the same way the OpenClaw and OpenCode plugins do. Native tasks
re-invoke pi in print mode with everything stripped off but the prompt,
using the invocation the plugin already resolves for summarization —
resolving `pi` from PATH breaks under a version-manager shim.

Skill distillation gets no shell here: pi's allowlist is tool-level, so
the Claude Code plugin's narrow Bash(memsearch transcript:*) hole has no
equivalent, and an unattended run is the wrong place for an unscoped one.
`config init` builds the plugins table from a hardcoded list and
save_config overwrites the file, so a user who had configured
[plugins.pi] lost it the next time they ran the wizard.
Summarization spawned `pi -p` with no flags, so every captured turn was
saved as a real session. `pi -r` filled up with summarizer runs whose
first message is the note-taker prompt, crowding out the user's actual
conversations.

Add the flags each sibling plugin already has an equivalent of
(claude-code's --no-session-persistence/--tools "", codex's --ephemeral,
opencode's isolated config): --no-session --no-tools --no-skills
--no-context-files, shared by summarization and native maintenance. The
summarizer had full read/bash/edit/write access it never needed.

Extensions stay enabled — custom providers are registered by extensions,
so --no-extensions would break summarizing behind a proxy or on a
self-hosted deployment. Guard session_start with IS_CHILD_PROCESS
instead, which was the one hook still doing work in children.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant