pi-handoff-rebase

Interactive git-rebase-style handoff compression for pi sessions

Packages

Package details

extensionskill

Install pi-handoff-rebase from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:pi-handoff-rebase
Package
pi-handoff-rebase
Version
1.0.3
Published
Jun 19, 2026
Downloads
not available
Author
victorchen04
License
MIT
Types
extension, skill
Size
29.1 KB
Dependencies
0 dependencies · 0 peers
Pi manifest JSON
{
  "extensions": [
    "./.pi/extensions/handoff-rebase.ts"
  ],
  "skills": [
    "./skills"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-handoff-rebase

Interactive, block-level context selection for pi session handoffs — git rebase -i, but for what your next agent sees.

The problem

/handoff (and most handoff extensions) generate one big markdown blob and either hand it to the next session automatically, or let you free-edit the prose in $EDITOR. Both leave you with an all-or-nothing choice: you either eat the whole context, or you re-write it by hand.

pi-handoff-rebase instead breaks the handoff into named blocks — goal, decisions, errors, open questions, file changes — and gives you a todo-list editor, exactly like git rebase -i, to decide what survives into the next session.

# Handoff for: "add caching layer to the search endpoint"
# Generated greedily from 14 turns — when in doubt, a block was kept.
# Your job here is to cut, not to discover what's missing.
#
# tool_results blocks default to skip — raw tool output is almost never
# needed again once an agent has acted on it.

pick  goal                    Add caching layer to the search endpoint
pick  decisions               Chose LRU with capacity 1000, evicting on TTL
skip  errors_redis            Debugging the redis connection timeout
pick  open_questions          Still undecided: persist cache across restarts?
skip  tangent_logging         Side discussion about log levels (not relevant)
skip  tool_results:grep_x3   3 grep calls while locating the endpoint code
skip  tool_results:test_run  Full pytest output (47 lines, all passing)

  > decisions                [expand: 3 sub-blocks]
  > errors_redis             [expand: 2 sub-blocks]

# Commands:
# p, pick <block>   = keep this block in the handoff
# s, skip <block>   = drop this block
# >  on a block     = expand to review/select its sub-blocks individually
# reorder lines to change the order blocks appear in the handoff
#
# Lines starting with # are comments and ignored.
# An empty file aborts the handoff.
#
# ~62% of original context picked (≈1,400 / 2,260 tokens). Skipped blocks
# stay queryable from the new session — nothing here is deleted.

Save and quit — only the picked blocks get assembled into the handoff document that starts your new session. Everything you skip stays attached to the parent session, not deleted, so the new agent (or you) can still pull it back out later if it turns out you needed it.

Why this exists

pi already ships a reference /handoff extension, and there are a few community packages that build on it (@ssweens/pi-handoff, @tifan/pi-handoff, default-anton/pi-handoff). They're good, and you may want them too — but they solve a different problem:

Editing model Selection granularity
Official handoff.ts example Free-text edit of one generated draft None — it's prose
@ssweens/pi-handoff Free-text edit + session_query for the new session to ask follow-up questions about the old one Query-based, after the fact
default-anton/pi-handoff None — fully automatic, no review step None
pi-handoff-rebase Structured todo-list, pick/skip per block, reorderable Explicit, before the new session ever starts

If you already use one of the above for generating the handoff content, this package is meant to slot in as the review step — it doesn't reimplement summarization, conversation parsing, or session linking.

Design decisions, and why

The pick/skip UI is the part people notice, but it's just a control surface. What actually makes the output good or bad is what happens before and after the editor opens. These choices aren't arbitrary — they follow recommendations that show up consistently in how production agent harnesses handle context compaction.

Generation is greedy; selection is not. Anthropic's own context-engineering guidance is to tune compaction by maximizing recall first and only then trimming for precision — capture everything that might matter, then cut. A single-pass "summarize once and that's the handoff" design can't recover from an omission at generation time; no amount of careful picking in the editor fixes a block that was never created. So block extraction here is intentionally generous, and the editor's only job is subtraction, never discovery.

tool_results blocks default to skip. This is the lowest-risk, highest-value cut in the entire compaction literature: once a tool has been called and the agent has acted on the result, the raw output deep in history is rarely needed again. Defaulting these to skip means the common case requires zero editing — you open vim, the boring stuff is already gone, and you're really only deciding on the handful of blocks that need judgment.

Skipped blocks are hidden, not deleted. Several production harnesses treat compaction as reversible: relabel and park old content rather than discard it, and keep a reference back to the source so a later agent can retrieve specifics it turns out it needed. This package does the same — skip detaches a block from the assembled handoff, it doesn't erase it from the parent session. If the new agent hits a gap, it can query back into the original session rather than the information simply being gone.

Blocks can expand into sub-blocks. Different harnesses summarize at different granularities — anywhere from a single flat summary to multi-tier structured breakdowns — because flat lists stop being reviewable once a session runs long. A 40-line conversation might fit in five top-level blocks; a 400-line one won't. Letting a block like decisions or errors_redis expand into its own sub-list (rather than forcing every individual decision into one flat top-level line) keeps the todo file reviewable regardless of session length, the same way git rebase -i stays usable whether you're squashing 3 commits or 30.

The footer shows what fraction of context survived. There's no feedback in a plain pick/skip list about how aggressive your edits actually were — it's easy to skip eight blocks and not realize that's 70% of the original context. A one-line token estimate before you save is enough to catch an accidental over-cut without turning this into a full diff/analytics tool.

None of this is meant to compete with how the content gets generated — that's still whatever summarization pipeline @ssweens/pi-handoff, the official extension, or your own prompt produces. This package only changes what happens to that content between generation and the new session.

The interaction, step by step

What actually happens at the terminal, end to end:

1. You type a command. Nothing renders yet — there's a brief wait.

> /handoff-rebase add caching layer to the search endpoint
Extracting context blocks…

This is an LLM call (same cost/latency as the official /handoff), so there's a short pause here. Nothing interactive yet.

2. Your terminal is replaced by vim, full-screen.

The command handler writes the todo file to $TMPDIR/pi-handoffs/<timestamp>.todo and spawns $EDITOR as a child process attached to the current TTY — the same thing a shell script does when it runs vim "$file" </dev/tty >/dev/tty. Pi's own REPL is blocked on that spawnSync underneath; vim gets the full terminal. You're editing a real file on disk. :w, :wq, search, macros — anything vim can normally do, works here.

3. You edit the todo file and exit vim.

Three ways out, same conventions as git rebase -i:

You do Result
Edit lines, :wq Proceeds to assembly (step 4)
Don't touch anything, :wq Proceeds with all defaults (pick for normal blocks, skip for tool_results)
Empty the file, or :cq Handoff is aborted — no new session is created

4. Control returns to pi. The todo file is parsed, not the editor's exit code alone.

Picked 4 of 6 blocks (~1,400 / 2,260 tokens). Starting new session…

If the file is malformed (unknown block id, duplicate id, pick/skip typo), you get the error inline and re-enter vim on the same file rather than silently guessing — same as a bad rebase todo line.

5. A new pi session starts. Its first message is the assembled handoff — not a fresh empty prompt.

The new session's input is a single user-role message, structurally identical to what /handoff already produces, just built from only the picked blocks:

[Handoff from session a1b2c3d, 2026-06-19]

## Goal
Add caching layer to the search endpoint

## Decisions
Chose LRU with capacity 1000, evicting on TTL

## Open Questions
Still undecided: persist cache across restarts?

---
Skipped at handoff: errors_redis, tangent_logging, tool_results (2 blocks).
Query the parent session if any of these turn out to be relevant:
/skill:pi-session-query /tmp/pi-sessions/a1b2c3d.jsonl "<question>"

The agent's first reply in the new session is its response to that message — from your perspective at the terminal, you go from "vim closes" straight to "new agent is already talking," the same beat as finishing a rebase and landing back at a clean git log.

Output, summarized: a new pi session, seeded with one message built from only the blocks you picked, plus a pointer back to the parent session for anything you skipped. Nothing is printed to stdout beyond the new session's own conversation — there's no separate report file unless you ask for one.

How it works

  1. /handoff-rebase <goal> triggers context extraction (reusing the same conversation→summary logic as the official extension), generated greedily — recall first, so nothing relevant gets dropped before you even see it.
  2. Extraction produces labeled blocks, some with sub-blocks (e.g. a decisions block may expand into individually reviewable entries). tool_results blocks are tagged separately and default to skip.
  3. A todo file is written to $TMPDIR/pi-handoffs/, one line per top-level block, with a token-count footer.
  4. The command handler spawns $EDITOR directly as a child process on the current TTY (spawnSync(editor, [todoPath], { stdio: "inherit" })). No PTY library is involved — the editor gets the terminal the same way any shell-invoked program would.
  5. On editor exit, the todo file is reparsed. Blocks marked skip are detached from the assembled handoff but remain part of the parent session record — they are not deleted, so they stay reachable via a session-query-style lookup if the new agent needs them later. Remaining blocks are assembled in the order they appear in the file.
  6. The new session starts with the assembled handoff plus a reference to the parent session, same as the official extension's final step.

Note on pi-interactive-shell

An earlier draft of this README said the editor step used pi-interactive-shell as a peer extension. That was wrong, and worth explaining because the mistake reveals something real about how Pi extensions compose (or don't).

Pi extensions are isolated: each is a separate function(pi: ExtensionAPI), loaded independently, with no shared registry and no way for one extension to call code registered by another. pi-interactive-shell registers a tool — something the LLM can invoke during an agent turn. A slash command handler runs outside the LLM loop, so there is no ctx.callTool("interactive_shell", …) available to it; tool calls are model-driven, not extension-driven.

The two honest options when a slash command needs interactive terminal access are:

  • Make it a tool instead of a command. Register it as something the model can call during an agent turn, then the model can also call interactive_shell in the same turn. The user experience changes: the user says "hand off this session" in prose and the model decides when to invoke the tool, rather than the user typing a slash command. That's a different product, not strictly worse, but different.
  • Spawn the editor directly from the command handler. The handler has full Node.js access: spawnSync(editor, [file], { stdio: "inherit" }) works from a Pi command handler the same way it works from any Node.js script. No PTY library required.

This package uses the second option. It keeps /handoff-rebase as a slash command (deterministic, user-initiated, no model judgment involved in when to run it) and spawns the editor directly. The pi-interactive-shell peer dependency is removed entirely.

Install

pi install npm:pi-handoff-rebase

No peer dependencies required.

Usage

/handoff-rebase <goal for the new session>

Falls back to plain /handoff behavior if no blocks are detected (e.g. very short conversations) — you'll never get an empty editor for no reason.

Configuration

.pi/handoff-rebase.json:

{
  "editor": "vim",
  "blocks": ["goal", "decisions", "errors", "open_questions", "file_changes", "tool_results"],
  "defaultAction": "pick",
  "toolResultsDefaultAction": "skip",
  "expandSubBlocksThreshold": 3,
  "showTokenFooter": true
}
  • blocks — which block types to extract; omit ones you never use.
  • defaultActionpick or skip for ordinary blocks. Set to skip if you want an opt-in workflow instead of opt-out.
  • toolResultsDefaultAction — separate default for tool_results blocks specifically. Defaults to skip since raw tool output is rarely needed again once acted on.
  • expandSubBlocksThreshold — minimum number of sub-items a block needs before it's shown collapsed (> block_name [expand: N sub-blocks]) instead of inline.
  • showTokenFooter — show the picked/total token estimate before save.

Status

Early / personal project. The pick/skip UI is the part I wanted that didn't exist yet; the defaults (greedy generation, tool-result skipping, reversible skips, token feedback) are pulled from how production agent harnesses already handle compaction, not invented from scratch. Feedback on what's still missing is welcome.

License

MIT