Skip to content

feat(agent): add pi coding agent integration in-tree#1170

Open
dipree wants to merge 9 commits intomainfrom
feat/pi-agent-builtin
Open

feat(agent): add pi coding agent integration in-tree#1170
dipree wants to merge 9 commits intomainfrom
feat/pi-agent-builtin

Conversation

@dipree
Copy link
Copy Markdown
Contributor

@dipree dipree commented May 9, 2026

Adds a built-in Pi integration. Ports the external entire-agent-pi plugin in-tree so Pi ships in every Entire CLI binary.

What it does

After entire enable --agent pi:

Capability How
Lifecycle hooks Embedded TS extension at .pi/extensions/entire/index.ts forwards session_start / before_agent_start / agent_end / session_shutdown to entire hooks pi <event>
Checkpoints One per before_agent_startagent_end pair
Transcript capture Pi's native JSONL is read live during the turn and copied to <repo>/.entire/tmp/pi/<id>.json at agent_end
Files touched TranscriptAnalyzer extracts paths from write / edit toolCall arguments
Token attribution TokenCalculator sums per-message usage (input / output / cacheRead / cacheWrite / api_call_count)
v2 compact transcript Branch-aware compactPi dispatched via isPiFormat sniffer; output is byte-compatible with the external plugin
Resume pi --session <id> (specific) or pi --continue (empty)

Why it's not just a copy of the external plugin

Property External This PR
DetectPresence which pi repo-local .pi/ only (matches Claude/Gemini/OpenCode convention)
FormatResumeCommand hard-coded pi --continue honours session ID
Session staging dir .entire/tmp .entire/tmp/pi/ (avoids AgentForTranscriptPath collision with other agents)
session_shutdown recovery clears cache without reading reads cache first so SessionEnd carries the right ID
NPM type import @mariozechner/pi-coding-agent (stale) @earendil-works/pi-coding-agent
Tree-aware parsing per-agent (private) shared cmd/entire/cli/agent/pi/pijsonl/ so agent + compactor stay in sync

Tests

40+ unit tests + integration suite + Vogon E2E canary. mise run check green.

Out of scope

TextGenerator, SkillDiscoverer/AgentReviewer, Vogon canary, ExtractSummary. Easy follow-ups; not required for the core "track sessions, checkpoint, attribute tokens, compact" loop.

Commits

  1. feat(agent): add pi coding agent integration in-tree — initial port
  2. fix(agent/pi): address review feedback — critical compactor bug (active-branch resolution on truncated data → abandoned-branch leak), pijsonl extraction, scanner buffer 1 MB → 10 MB, foreign-file rejection in InstallHooks, GetSessionDir repo-root resolution, doc fixes

Adds a built-in Pi (https://github.com/earendil-works/pi-mono) integration
under cmd/entire/cli/agent/pi/. Ported from the previously external
github.com/entireio/external-agents/agents/entire-agent-pi plugin, adapted
to the in-tree Agent / HookSupport / TokenCalculator / TranscriptAnalyzer
interfaces so it ships in every entire CLI binary alongside Claude Code,
OpenCode, etc.

What ships
----------
- Embedded TypeScript extension (entire_extension.ts) installed at
  .pi/extensions/entire/index.ts via `entire enable --agent pi`.
  Idempotent install, marker-based ownership detection,
  prod/local-dev placeholder substitution.
- Lifecycle hook handler mapping Pi-native events (session_start,
  before_agent_start, agent_end, session_shutdown) to normalised
  lifecycle types. Snake_case names match Pi's pi.on() events; the
  TS extension forwards them verbatim.
- Token attribution from per-assistant-message usage objects
  (input/output/cacheRead/cacheWrite/api_call_count).
- File extraction from write/edit toolCall arguments.
- Prompt extraction from user-message text.
- Branch-aware compactor (transcript.jsonl on entire/checkpoints/v2/main)
  that produces byte-compatible output with the external plugin.

Active-branch resolution
------------------------
Pi sessions are tree-shaped: every entry has parentId, and forks/branches
accumulate entries from BOTH branches in the JSONL file.
resolveActiveBranch walks parentId pointers from the most-recent message
to the root and returns the active set; everything else (token usage,
file extraction, prompts, compact transcript) filters by it. Without
this, abandoned-branch tool calls would inflate every metric.

Captured-transcript handoff
---------------------------
On agent_end, captureTranscript() copies Pi's native session JSONL into
<repo>/.entire/tmp/pi/<session-id>.json. Pi can later be reconfigured
or the ~/.pi directory wiped without losing Entire's checkpoint
transcript.

Important: the captured-transcript dir is deliberately namespaced under
.entire/tmp/pi (not .entire/tmp/) — the framework's
agent.AgentForTranscriptPath iterates every registered agent's
GetSessionDir() to identify which agent owns a given transcript path,
and a broader .entire/tmp/ claim from Pi would shadow paths used by
other agents' integration tests and tooling.

session_id cache
----------------
Pi's before_agent_start may fire before session_start completes, and
agent_end / session_shutdown may fire after Pi tears down its session
manager and stops sending session_id payloads. We cache the active
session ID at session_start in <repo>/.entire/tmp/pi/pi-active-session
and recover it on later hooks. session_shutdown reads the cache before
clearing it so SessionEnd events still carry the right session ID.

Compact format
--------------
isPiFormat sniffer + compactPi added to cmd/entire/cli/transcript/compact/.
Branch-aware (drops abandoned tool_use blocks), normalises tool names
(write/edit/read → Write/Edit/Read for parity with Claude's compact
format), splices toolResult messages into the matching assistant
tool_use block by toolCallId with status: success|error, preserves
input_tokens/output_tokens from the pi usage object.

tool_call interception in the embedded TS extension
---------------------------------------------------
Bash subprocesses spawned by Pi inherit a TTY but cannot answer hook
prompts. The extension prepends `export GIT_TERMINAL_PROMPT=0` to bash
commands so agent-driven `entire` calls are non-interactive.

Wiring
------
- AgentNamePi = "pi", AgentTypePi = "Pi" in registry.go
- Blank-import of cmd/entire/cli/agent/pi in hooks_cmd.go for hook
  subcommand discovery
- compact.Compact dispatcher routes Pi format

Tests
-----
- 30+ unit tests covering: linear/branching/multi-fork sessions, flat
  (no parentId) transcripts, cycle protection, offset-past-end, extract*
  family with branch filtering, ChunkJSONL/ReassembleJSONL round-trip,
  hook install idempotency + foreign-file rejection, all 4 hook→event
  mappings, session_id cache lifecycle including the recovery path on
  empty session_shutdown payloads, captureTranscript round-trip,
  isPiFormat sniffer (6 cases), compactor format-fidelity vs the
  external plugin's expected output.
- All existing tests still pass: unit + integration + Vogon E2E canary.

Verified end-to-end against ~/.local/bin/entire (mise run install):
fired session_start → before_agent_start → agent_end → session_shutdown
with a synthetic Pi JSONL and a write toolCall; got 1 checkpoint,
files_touched=[demo.txt] (transcript-derived), token_usage input=95
output=40 cache_creation=500 cache_read=700 api_call_count=2,
captured transcript at .entire/tmp/pi/<id>.json, session-id cache
cleared after shutdown. After git commit + checkpoints_v2 enabled,
v2 compact transcript on refs/entire/checkpoints/v2/main contains
{agent: "pi", input_tokens, output_tokens} per assistant line.

Out of scope (intentionally deferred)
-------------------------------------
- TextGenerator (pi -p mode)
- SkillDiscoverer/AgentReviewer for `entire review`
- Vogon-style canary
- ExtractSummary (no SummaryExtractor interface in-tree; entire has its
  own summarize package)
@dipree dipree requested a review from a team as a code owner May 9, 2026 17:39
Copilot AI review requested due to automatic review settings May 9, 2026 17:39
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 61c0f61. Configure here.

Comment thread cmd/entire/cli/transcript/compact/pi.go Outdated
Comment thread cmd/entire/cli/transcript/compact/pi.go Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an in-tree integration for the Pi coding agent, including hook installation, lifecycle event parsing, transcript analysis (tokens/files/prompts), and Pi-specific transcript compaction into Entire’s normalized transcript.jsonl format.

Changes:

  • Introduces a new built-in pi agent implementation (hooks, lifecycle mapping, transcript parsing, token usage, file/prompt extraction).
  • Adds Pi JSONL format detection + compaction support under cmd/entire/cli/transcript/compact/.
  • Wires Pi into the agent registry and hooks command via blank import.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
cmd/entire/cli/transcript/compact/pi.go Implements Pi JSONL → Entire compact transcript conversion with active-branch filtering and tool result inlining.
cmd/entire/cli/transcript/compact/pi_test.go Unit tests for Pi compaction (linear, branching, tool name normalization, StartLine, format sniffing).
cmd/entire/cli/transcript/compact/compact.go Routes Pi transcripts to the new Pi compactor via isPiFormat.
cmd/entire/cli/hooks_cmd.go Registers Pi hooks support by blank-importing the Pi agent package.
cmd/entire/cli/agent/registry.go Adds AgentNamePi / AgentTypePi constants.
cmd/entire/cli/agent/pi/pi.go Core Pi agent identity, presence detection, session dir, transcript chunking, and resume command formatting.
cmd/entire/cli/agent/pi/transcript.go Pi transcript parsing for token usage, modified files, prompts, and transcript position.
cmd/entire/cli/agent/pi/transcript_test.go Tests for Pi transcript parsing + agent behaviors (files/prompts/tokens, branching, chunking, registration).
cmd/entire/cli/agent/pi/lifecycle.go Maps Pi lifecycle hooks to Entire events, implements transcript capture + session-id caching.
cmd/entire/cli/agent/pi/lifecycle_test.go Tests for lifecycle parsing, session-id cache behavior, and transcript capture.
cmd/entire/cli/agent/pi/hooks.go Installs/uninstalls the embedded Pi TypeScript extension into .pi/extensions/entire/index.ts.
cmd/entire/cli/agent/pi/hooks_test.go Tests hook installation idempotency, local-dev substitution, uninstall, and ownership detection.
cmd/entire/cli/agent/pi/entire_extension.ts Embedded TS extension that forwards Pi events to entire hooks pi <event> and disables git terminal prompts for bash tool calls.

Comment thread cmd/entire/cli/transcript/compact/pi.go Outdated
Comment thread cmd/entire/cli/agent/pi/transcript.go Outdated
Comment thread cmd/entire/cli/agent/pi/transcript.go Outdated
Comment thread cmd/entire/cli/agent/pi/hooks.go
Comment thread cmd/entire/cli/agent/pi/lifecycle.go Outdated
Comment thread cmd/entire/cli/agent/pi/lifecycle.go Outdated
Comment thread cmd/entire/cli/agent/pi/lifecycle.go
Comment thread cmd/entire/cli/agent/pi/pi.go
dip and others added 5 commits May 9, 2026 22:20
Critical fix
------------
- compactPi was calling piResolveActiveBranch on truncated content (after
  StartLine slicing). When StartLine skipped past the fork-point entries,
  the active-branch tree fell apart and abandoned-branch tool calls
  leaked into the compact transcript. Active-branch resolution and
  tool-result collection now both run on the full buffer; only the
  emit loop sees the truncated tail. Regression test:
  TestCompact_Pi_StartLineDoesNotLeakAbandonedBranches.

Other changes
-------------
- Extract shared pi-format primitives into a new
  cmd/entire/cli/agent/pi/pijsonl package: Entry/Message/ContentItem/
  Usage types, ResolveActiveBranch, SkipLines, CountLines, NewScanner,
  DecodeStringContent. The pi agent and the v2 compact dispatcher now
  share the same implementation, so a fix to one lands in both.
- Bump JSONL scanner buffer from 1 MB to 10 MB. Pi tool calls embed
  full file contents in arguments and 1 MB was too tight; matches
  Codex/Copilot/Droid scanners.
- InstallHooks now refuses to overwrite a foreign file at
  .pi/extensions/entire/index.ts unless --force is passed. A user's
  own extension at the same path is no longer silently clobbered when
  re-running `entire enable --agent pi`. Test:
  TestInstallHooks_RefusesForeignFileWithoutForce.
- GetSessionDir resolves the worktree root via paths.WorktreeRoot when
  repoPath is empty, instead of os.Getwd(). Callers running from a
  subdirectory of the repo now get the correct repo-local staging
  path.
- Fix doc comments in lifecycle.go (captureTranscript path) to reference
  the actual .entire/tmp/pi/<id>.json namespace, matching the runtime
  behaviour.
- Fix CountLines doc comment: it counts newline-terminated (or final
  unterminated) lines, including blanks — that's what SkipLines and
  the StartLine offset use.
- Drop now-unused message types and helper duplicates from
  agent/pi/transcript.go and transcript/compact/pi.go.

Skipped (intentionally)
-----------------------
- Validating piHookPayload.Type matches the CLI subcommand name. The
  subcommand is the routing source of truth; tightening this would
  reject valid Pi payloads if Pi adds a new event field.
GetSessionDir / ResolveSessionFile previously returned the per-repo
.entire/tmp/pi/ cache, which is only populated after the agent_end
hook copies the live JSONL there. That meant `entire session attach
<id>` for a Pi session that was never hooked (or whose hook capture
failed) couldn't find the transcript at all — defeating attach's
documented "hooks failed / weren't installed" recovery use case.

Move resolution to Pi's native store at
<piHome>/sessions/<encoded-repo-path>/<timestamp>_<id>.jsonl, with
the encoded path matching Pi's own naming scheme
(/Users/foo/repo → --Users-foo-repo--). Honor PI_CODING_AGENT_DIR
(Pi's own override) and add ENTIRE_TEST_PI_SESSION_DIR for test
isolation. Add GetSessionBaseDir so attach's cross-project fallback
in searchTranscriptInProjectDirs can scan sibling project subdirs.

ResolveSessionFile now globs *_<id>.jsonl and returns the most recent
timestamp match; absolute paths from hook payloads still pass through
unchanged. The .entire/tmp/pi/ cache stays as a hook-internal detail
— captureTranscript writes there, and the framework records the
recorded path as SessionRef in checkpoint metadata, so subsequent
operations on hooked sessions go through the recorded path rather
than re-resolving via GetSessionDir. Rename the cache constant to
piHookCacheSubdir (lifecycle.go) to make the agent-public-API vs
hook-internal split obvious.

The trade-off vs the prior choice: AgentForTranscriptPath ownership
matching now matches live Pi paths and misses cached paths, instead
of the inverse. Cached paths are only reachable via Pi's own hook
flow, where caller-agent-type fallback already resolves ownership
correctly; live-path matching is the more important case because
that's the one cold attach surfaces.

Verified end-to-end: a Pi session created with no Entire hooks
installed is now resolvable via `entire session attach <id>` against
PR #1170's branch (transcript_path resolves to the live JSONL in
~/.pi/agent/sessions/, token usage parses correctly).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: ee3bb4d600af
session_shutdown previously emitted a SessionEnd lifecycle event,
which races with agent_end during normal Pi session teardown:

* Pi fires both events at session shutdown
* The TypeScript extension's pi.on(...) handlers each dispatch an
  `entire hooks pi <event>` child process via execFile
* Both child processes run concurrently in Node's event loop; their
  startup ordering decides which event reaches Entire first
* When session_shutdown wins, the SessionEnd transition moves the
  session to "ended" and condenses it before agent_end can save its
  linkable checkpoint
* prepare-commit-msg then sees a "fully-condensed ended session" and
  doesn't add a checkpoint trailer to the user's commit
* entire/checkpoints/v1 never advances on the commit

Reproduced reliably on a TestSingleSessionManualCommit/pi run before
the fix (37s timeout failure); same test passes in 8.5s after.

session_shutdown is now cleanup-only — clears the cached session ID
and emits no lifecycle event. agent_end is the source of truth for
"turn complete" and, for any single-turn `pi -p` invocation,
effectively "session over" too. SessionEnd is left for the framework
to derive from idle timeout or the next SessionStart's stale-state
cleanup. Update GetSupportedHooks (drop HookSessionEnd) and the
header comment to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 09a0086f82a2
Register the Pi adapter so the e2e harness picks it up via
E2E_AGENT=pi. Same shape as the existing in-tree adapters: shells out
to `pi -p <prompt> --no-skills --no-prompt-templates --no-themes`
in non-interactive mode and uses the standard tmux helper for
interactive sessions.

Drives the regression test for both the cold-attach native-store
fix (committed earlier) and the agent_end / session_shutdown race
fix — TestSingleSessionManualCommit/pi exercises the full hooked
flow against a real `pi` binary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 9329fbd003b5
List Pi alongside the other native agents in the agent-package
description, the per-agent test:e2e flag table, the e2e agent
enumeration, and the E2E_AGENT env var values.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 67bcf0cbbd57
Soph and others added 3 commits May 10, 2026 10:05
Pi's encoded session-dir name (--Users-foo-repo--) was produced by
replacing only os.PathSeparator. On Windows that meant '\\' was
substituted but '/' leaked through — git rev-parse --show-toplevel
returns forward slashes regardless of platform, so the encoded name
still contained directory separators and pi.GetSessionDir produced a
nested path instead of a single subdirectory under <piHome>/sessions/.
Result: cold-attach lookups missed, even though the live JSONL was on
disk.

Replace both '/' and '\\' uniformly so the encoding is identical
across hosts. filepath.ToSlash isn't enough on its own — it only
maps the host's separator. Add Windows-style fixtures to the
encoding test (forward and back slashes, with and without trailing
separator).

Reported by Copilot review on #1173.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: c2bfe61c1a96
The Pi e2e adapter previously inherited the user's real ~/.pi/agent
state, so parallel E2E runs could interleave session writes and a
test could read transcripts from another test's session if encoded
repo paths happened to overlap. Mirrors what codex.go does with
CODEX_HOME.

Per-test home directory under $XDG_CACHE_HOME/entire-e2e/pi-home-*,
exported as PI_CODING_AGENT_DIR for both RunPrompt and StartSession
(the tmux helper now wraps the binary in `env PI_CODING_AGENT_DIR=…`
so the var propagates into the agent process). Cleanup runs via
defer (RunPrompt) or s.OnClose (StartSession).

Pi stores OAuth tokens at <piHome>/auth.json — a fresh isolated home
breaks auth. seedPiHome symlinks auth.json + settings.json from the
real home (best-effort) so the user's existing OAuth tokens flow
through; tests with explicit ANTHROPIC_API_KEY / OPENAI_API_KEY env
inherit those regardless. Sessions still land in the isolated
home's sessions/ subdir, which is the actual hermeticity goal.

PiSession exposes the home so future fixtures can introspect or
clean it.

TestSingleSessionManualCommit/pi passes against the isolated home in
8.9s (vs the previous 8.5s on the shared home). Reported by Copilot
review on #1173.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: f2edd6ee34e2
Pi: cold-attach native-store resolution, race fix, E2E coverage
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants