coding-agents: add pi as a harness, and give Prime Agent the companion skill (#3772) - #3775
Open
feniix wants to merge 7 commits into
Open
coding-agents: add pi as a harness, and give Prime Agent the companion skill (#3772)#3775feniix wants to merge 7 commits into
feniix wants to merge 7 commits into
Conversation
… shares Prime Agent is a fork of pi and its extension API is pi's: the same `extensions` array in settings.json, the same `before_agent_start` / `agent_end` events, the same `registerTool`. The adapter for it was written as if that API were Prime Agent's own, so a second host on the same API had nowhere to plug in. Move it to harness/pi-extension.ts behind `createPiExtension(harness)`, mirroring how harness/plugin-entry.ts already serves the opencode family, and leave prime-agent.ts as a thin entrypoint that names its harness. The message normalizer moves with it: the shape it parses is pi's, not the fork's, so transcript-prime-agent.ts becomes transcript-pi.ts (PiMessage, readPiMessages). No behaviour change — the harness is still "prime-agent", and the adapter tests move across unchanged apart from being parameterised by harness name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
pi (@earendil-works/pi-coding-agent) is the agent Prime Agent was forked from, and the extension adapter extracted in the previous commit already fits it unchanged. This wires it up as a harness of its own: src/pi.ts passes "pi" to createPiExtension, the installer registers the built dist/pi.js in the `extensions` array of ~/.pi/agent/settings.json, and the registry, tsup entry map and Docker E2E setup gain the matching entries. pi and Prime Agent now share one installer factory: they differ only in config directory and which bundle reports which harness, so the two configs are proved independent by test rather than by two near-identical code paths. A separate entrypoint per host is what keeps each attributable — its own `harnesses.<name>` config section and its own agent stamped on every document it retains — while both still share one bank per repo, as every coding agent here does by default. The control plane gains the `pi` logo entry so those documents render a mark instead of a bare metadata chip. Not routed through our package.json `pi` manifest key: both hosts read that same key, so it can only ever name one bundle, and it stays pointed at Prime Agent's. The README says so, since the package route would otherwise mis-attribute pi. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…al disk
Both hosts persist every session as JSONL — a `{type:"session", id, cwd}`
header followed by `{type:"message"}` entries wrapping the same message objects
the live `agent_end` event hands over — so `install <harness>
--import-conversations` can now backfill a repo's history instead of reporting
"no local history reader", which is what it did for both.
readPiTranscript shares renderMessage with the live path rather than
re-deriving the normalization, so an imported session and a live one reach the
bank identically, injected memory stripped either way.
Only the file layout differs between the two hosts, which is all the per-host
code there is:
pi sessions/--<encoded cwd>--/<timestamp>_<uuid>.jsonl
Prime Agent sessions/<uuid>.jsonl, flat
pi's folder name is prefiltered on because that store holds one folder per
working directory — 34k on the machine this was written on — but attribution
comes from the header `cwd` in both cases, never the folder name: the encoding
is not injective (`/a/b` and `/a-b` collide), the same trap the Claude reader
documents. Sessions recording no cwd are skipped and counted.
Both hosts are done together deliberately: they share a schema, so adding a
reader to one would leave the other as exactly the sibling gap that keeps
biting us.
Verified against the real stores: 202 pi sessions attributed to one repo with
zero unattributed, in 760ms across the full tree.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
piHistory prefilters session folders by encoded name and hands each match to readdirSync, so a regular FILE named like a session folder threw ENOTDIR straight out of importLocalHistory — which documents that it never throws, and whose caller (installer.ts importConversations) has no catch. One junk file in ~/.pi/agent/sessions killed the whole --import-conversations run rather than costing that entry. Guard the per-directory listing, as dshHistory already does for the same case. The regression test fails with ENOTDIR without it. claudeHistory has the identical exposure — it is the reader this one was modelled on — and is deliberately left alone here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rypoint's harness name
Two gaps a review of the pi harness turned up.
pi discovers skills from `~/.pi/agent/skills` and the shared `~/.agents/skills`,
but nothing installed one for it — so a pi-only user got no companion skill,
while a user who also runs Codex or dsh picked ours up by accident from the
shared root. Install into pi's OWN directory: uninstallSkill removes by a fixed
name, so writing to the shared root would make `uninstall pi` delete Codex's and
dsh's copy. Prime Agent reads `~/.prime/agent/skills` the same way and could
take the skill too; it deliberately does not yet, and a test pins that so the
difference does not read as an oversight.
The harness name an entrypoint reports is a bare string literal — it picks the
`harnesses.<name>` config section, feeds `{harness}` bank templating, and is
stamped on every document that host retains — and nothing checked it. The
registry, the installer and the control plane's logo map are three separate
hand-maintained lists, so `createPiExtension("prime-agent")` in src/pi.ts would
have shipped green. Assert it over the whole family instead, enumerated from
PLUGIN_ENTRYPOINTS rather than a fourth list, covering both idioms in use (the
shared-factory argument, and the module constant Cline and dsh declare).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes vectorize-io#3772. Prime Agent discovers `~/.prime/agent/skills` and the shared `~/.agents/skills` exactly as pi discovers its own, but nothing installed a skill for it — so it only ever picked ours up by accident, from the shared root, when the user also ran Codex or dsh. It already shares pi's installer factory, so this is the skills directory it was missing. Into its OWN root, not the shared one: uninstallSkill removes by a fixed directory name, so writing to `~/.agents/skills` would make `uninstall prime-agent` delete Codex's and dsh's copy. With both hosts passing one, `skillsDir` stops being optional — no caller omits it now, so the parameter and the two `if (skillsBase)` guards become required and unconditional. The uninstall log line stops under-reporting: it removes a skill as well as the extension entry. The test that pinned prime-agent as deliberately skill-less is replaced by the pi cases generalised over both hosts, plus one asserting the two roots stay independent — uninstalling pi must not take Prime Agent's skill with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Strix Security ReviewWarning This pull request has 17 commits after the last Strix review ( No security issues found. Updated for Reviewed by Strix |
6 tasks
# Conflicts: # hindsight-docs/docs-integrations/coding-agents.md # hindsight-integrations/coding-agents/README.md # hindsight-integrations/coding-agents/src/installer.ts # skills/hindsight-docs/references/sdks/integrations/coding-agents.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3772.
Prime Agent discovers
~/.prime/agent/skillsand the shared~/.agents/skillsexactly as pi discovers its own, but nothing installed a skill for it — so it only ever picked ours up by accident, from the shared root, when the user also ran Codex or dsh.The change for #3772
Into its own root, not the shared one:
uninstallSkillremoves by a fixed directory name, so writing to~/.agents/skillswould makeuninstall prime-agentdelete Codex's and dsh's copy. There's a test for that.With both hosts passing one,
skillsDirstops being optional — no caller omits it now — so the parameter and its twoif (skillsBase)guards become required and unconditional, and the uninstall log line stops under-reporting (it removes a skill as well as the extension entry).The test that pinned prime-agent as deliberately skill-less is replaced by the pi cases generalised over both hosts, plus one asserting the two roots stay independent: uninstalling pi must not take Prime Agent's skill with it.
Why this PR carries more than that one-liner
piFamilyInstallerdoes not exist onmain— it arrives with the pi harness work in the first five commits, which are not yet on their own PR. #3772's fix is one argument to a factory that this branch introduces, so the two cannot be separated without rewriting the fix against the older hand-writtenprimeAgentinstaller block.Read it as the pi harness, then #3772 on top. The last commit (
a5fd6f17e) is the fix the issue asks for; the rest is its prerequisite:dadc31aa7a19b0bde4776f53218fde3bb3317ab072245a5fd6f17eEach commit builds and passes on its own (verified by checking each out in a worktree).
Background on the pi commits
pi (
@earendil-works/pi-coding-agent) is the agent Prime Agent was forked from, and the two expose the identical extension API — sameextensionsarray insettings.json, samebefore_agent_start/agent_endevents, sameregisterTool, samepkg.pimanifest key, same stored-session schema. So the existing Prime Agent adapter fits pi unchanged; it just had no seam to plug into. The refactor adds one (createPiExtension(harness), mirroringcreatePluginEntryfor the opencode family), and pi becomes a thin entrypoint alongside Prime Agent's.Both hosts still share one bank per repo — the default
coding-agent::{gitProject}is harness-neutral on purpose — so a separate entrypoint per host is about attribution (harnesses.<name>config section,metadata.harnesson retained documents, control-plane logo), not about splitting memory.Two notes for reviewers:
pimanifest key stays pointed at Prime Agent's bundle. Both hosts read the samepkg.pi.extensions, so it can only ever name one. The README tells pi users to runinstall pirather thanpi install npm:…, which would otherwise report pi's sessions asprime-agent.claudeHistoryhas the sameENOTDIRexposurefde3bb331fixes for pi and is deliberately untouched here — filed separately as coding-agents: --import-conversations dies on a stray file in ~/.claude/projects instead of skipping it #3771.Verification
tsc,./scripts/hooks/lint.shand./scripts/hooks/check-unused.shclean.ENOTDIRfor the history guard;expected 'prime-agent' to be 'pi'for the entrypoint guard).skill/SKILL.md, the docs page, the docs-skill mirror) regenerated per commit, so each one is self-consistent.