pi-handoff-rebase
Interactive git-rebase-style handoff compression for pi sessions
Package details
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
/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.- Extraction produces labeled blocks, some with sub-blocks (e.g. a
decisionsblock may expand into individually reviewable entries).tool_resultsblocks are tagged separately and default toskip. - A todo file is written to
$TMPDIR/pi-handoffs/, one line per top-level block, with a token-count footer. - The command handler spawns
$EDITORdirectly 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. - On editor exit, the todo file is reparsed. Blocks marked
skipare 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. - 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_shellin 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.defaultAction—pickorskipfor ordinary blocks. Set toskipif you want an opt-in workflow instead of opt-out.toolResultsDefaultAction— separate default fortool_resultsblocks specifically. Defaults toskipsince 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