feat(agent): add pi coding agent integration in-tree#1170
Open
feat(agent): add pi coding agent integration in-tree#1170
Conversation
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)
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ 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.
Contributor
There was a problem hiding this comment.
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
piagent 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. |
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
3 tasks
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
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.

Adds a built-in Pi integration. Ports the external
entire-agent-piplugin in-tree so Pi ships in every Entire CLI binary.What it does
After
entire enable --agent pi:.pi/extensions/entire/index.tsforwardssession_start/before_agent_start/agent_end/session_shutdowntoentire hooks pi <event>before_agent_start→agent_endpair<repo>/.entire/tmp/pi/<id>.jsonatagent_endTranscriptAnalyzerextracts paths fromwrite/edittoolCallargumentsTokenCalculatorsums per-messageusage(input / output / cacheRead / cacheWrite / api_call_count)compactPidispatched viaisPiFormatsniffer; output is byte-compatible with the external pluginpi --session <id>(specific) orpi --continue(empty)Why it's not just a copy of the external plugin
DetectPresencewhich pi.pi/only (matches Claude/Gemini/OpenCode convention)FormatResumeCommandpi --continue.entire/tmp.entire/tmp/pi/(avoidsAgentForTranscriptPathcollision with other agents)session_shutdownrecoverySessionEndcarries the right ID@mariozechner/pi-coding-agent(stale)@earendil-works/pi-coding-agentcmd/entire/cli/agent/pi/pijsonl/so agent + compactor stay in syncTests
40+ unit tests + integration suite + Vogon E2E canary.
mise run checkgreen.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
feat(agent): add pi coding agent integration in-tree— initial portfix(agent/pi): address review feedback— critical compactor bug (active-branch resolution on truncated data → abandoned-branch leak),pijsonlextraction, scanner buffer 1 MB → 10 MB, foreign-file rejection inInstallHooks,GetSessionDirrepo-root resolution, doc fixes