@2h2d/pi-session-tools
Checkpoint inspection and context handoffs for Pi coding agents
Package details
Install @2h2d/pi-session-tools from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@2h2d/pi-session-tools- Package
@2h2d/pi-session-tools- Version
0.0.7- Published
- Oct 8, 2026
- Downloads
- 1,174/mo · 539/wk
- Author
- kaanozdokmeci
- License
- MIT
- Types
- extension
- Size
- 66.8 KB
- Dependencies
- 0 dependencies · 3 peers
Pi manifest JSON
{
"extensions": [
"./extensions/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-session-tools
Pi extension for inspecting conversation checkpoints and handing work to a different conversation context.
session_inspectfinds or reads checkpoints across the current session tree.session_handoffnavigates, forks, compacts, or starts a fresh session, then appends a handoff and resumes the agent.
A checkpoint is the last message of a completed assistant response and its tool batch. A handoff carries findings, current restrictions, and next steps. This extension does not launch subagents or restore workspace files.
Requirements and loading
Tested with Pi 1.1.0 and Node.js 22.23.3. The package requires Pi
>=1.1.0 <1.2.0 and Node.js >=22.19.0. The extension refuses to load on a Pi
older than 1.1.0, because Pi does not enforce the peer range when it installs
packages.
Pi's virtual models are not supported. Pi's experimental virtual models,
registered with pi.registerVirtualModel(), are not tested with this package.
From this checkout:
mise run init
mise exec -- pi -e ./extensions/index.ts
This does not change your Pi configuration. Keep other extensions enabled, including any required provider or system-prompt extensions.
Install @2h2d/pi-session-tools from npm:
pi install npm:@2h2d/pi-session-tools
Automatic checkpoints
The extension records a checkpoint after each valid turn_end. Pi defines a
turn as one assistant response plus all its tool results, not the entire user
request. A checkpoint can therefore mark the boundary before a lengthy
investigation within a single request.
The agent receives compact metadata at those boundaries:
[Session checkpoint: session=<session-id> entry=<entry-id>]
Use these IDs directly. There is no need to query history before every handoff.
The source checkpoint is included in the destination handoff for a direct
return. Other branches and compacted-away checkpoints remain discoverable
through session_inspect.
Checkpoint IDs are persisted as extension entries. Markers are inserted into
model context through Pi's context_with_system event without modifying the
stored assistant messages or the transcript's system messages. This preserves
Pi's text-mode final response, keeps mid-conversation prompt and tool updates
in place, and does not trigger additional model requests. If another
extension rewrites a message before context projection, its marker may be
omitted. The checkpoint remains available through inspection.
Pi's portable extension API represents these as custom metadata messages, not developer-role messages. Static tool guidance explains their meaning.
session_inspect
{
view: "overview" | "ancestors" | "children" | "search" | "read";
entryId?: string;
query?: string;
limit?: number; // Default 20, maximum 50
before?: number; // Read view: default 2, maximum 10
after?: number; // Read view: default 2, maximum 10
includeToolResults?: boolean; // Default false
cursor?: string;
}
overviewlists completed-turn checkpoints across the full current tree.ancestorslists checkpoints on the path toentryId, or the active entry.childrenlists the next checkpoints on each path from the selected checkpoint.searchmatches literal, case-insensitive text in labels, assistant text, the latest user request, and optional tool results. It requiresquery.readinspects an entry and nearby context. It requiresentryId. At a branch point, it lists child IDs instead of silently choosing a path. System entries show instruction changes and tool names, not tool schemas. System and usage entries are not completed-turn checkpoints.
Responses include the current session and entry IDs, checkpoint relationships, labels, previews, approximate context token counts, and recent operation phases. Labels and previews are cut to their first 240 characters. Token counts estimate serialized message characters divided by four. They are not provider usage measurements.
Repeat the same arguments with nextCursor as cursor to continue a page.
Pagination uses a fixed snapshot even when subsequent turns add entries.
Reading returns up to 8,000 text characters per page. Model-facing text is
capped at 48,000 bytes. Reduce page or neighbor limits if the output reports truncation.
Thinking blocks, image data, and tool-call arguments are never returned.
Tool output is opt-in and can contain sensitive source material.
Inspection does not navigate or search other session files.
The tool is annotated read-only and closed-world. It declares an output schema,
so scripts run by Pi's codemode tool receive the response as an object
instead of text. That object is never truncated. Pagination still bounds its
size.
session_handoff
Every call includes the current expectedSessionId and one handoff:
{
expectedSessionId: string;
mode: "navigate" | "fork" | "compact" | "new";
targetEntryId?: string;
compactionInstructions?: string;
handoff:
| { kind: "inline"; text: string }
| { kind: "file"; path: string; instruction: string };
}
| Mode | Destination | Retained conversation |
|---|---|---|
navigate |
Current session | Path through targetEntryId |
fork |
New session | Copy of the path through targetEntryId |
compact |
Current session | Pi's summary and retained recent messages |
new |
New session | No inherited conversation |
targetEntryId is required only for navigate and fork. It must identify a
complete turn. The selected entry is retained. Mid-batch tool results, unmatched
tool calls, user messages, and arbitrary metadata entries are rejected.
compactionInstructions is allowed only for compact. It guides the summarizer.
It is not the post-compaction handoff.
The tool is model-only. Scripts run by Pi's codemode tool cannot see or
call it, because a call from a script can never be the sole tool call of an
assistant message.
Check its absence with "session_handoff" in tools. Pi 1.0.0 throws when a
script reads an unavailable member, including typeof tools.session_handoff.
It is annotated as not read-only, not destructive, not idempotent, and closed-world. Handoffs append entries or create sessions and never delete source history.
Inline handoff
{
"expectedSessionId": "<current-session-id>",
"mode": "compact",
"compactionInstructions": "Preserve the agreed design and validation results.",
"handoff": {
"kind": "inline",
"text": "Investigation complete. Retries duplicate writes. No files changed. Add request deduplication and concurrency tests. Do not change the database schema."
}
}
Inline text is limited to 16,000 characters. It is appended verbatim inside an agent-authored wrapper with source information and workspace safety reminders.
Findings-file handoff
{
"expectedSessionId": "<current-session-id>",
"mode": "new",
"handoff": {
"kind": "file",
"path": "research/findings.md",
"instruction": "Read this file first, then implement its recommended approach."
}
}
Relative paths resolve against the source working directory. A leading @ is
accepted. The file must already exist and be a regular readable file.
The extension resolves symlinks and hashes the file without embedding its
contents. It checks the path and hash again before moving and before delivery.
A detected change stops the handoff. The file remains mutable after delivery.
Execution and recovery
Call session_handoff alone, after other tools and findings-file writes
finish. The initial result says accepted, not completed.
- Validate the request, destination, and absence of pending user input.
- Record the request and terminate the tool batch.
- At
agent_settled, dispatch an internal command. - Navigate, fork, compact, or create the new session through Pi's public APIs.
- Append exactly one handoff after success and start the next agent run.
Pi runs prompts sent from settled handlers after those handlers return and before it resolves idle waits, so its print and JSON hosts do not exit before the handoff completes. Forks and new sessions use Pi's fresh replacement context. Continuation starts after the host finishes its replacement action, so a late editor reset cannot erase a new draft. For forks and new sessions, the extension saves the handoff first, then submits a short, explicitly agent-authored continuation prompt. This runs Pi's normal input handlers and prompt preparation before the first response. It preserves system instructions, project instructions, and extension prompt/tool changes. The continuation prompt grants no new authorization. Compaction waits for its completion callback.
Incoming user input, unsubmitted editor drafts, session changes, failed validation, or cancellation from another extension stop a pending handoff. Input handlers can also stop the replacement continuation after its handoff has been saved. Already-completed context changes are not silently undone. Source history remains available.
Operation records use unique IDs. Reloading or resuming a session never replays unfinished requests automatically. Inspect the current context and source history before requesting a replacement operation. Navigation, message append, and generation are not one atomic transaction. A crash or generation failure can leave the destination selected without a completed continuation.
The internal /session-tools-apply-handoff command accepts only a matching
in-memory request. It is not a recovery or manual-navigation command.
Safety boundaries
- Files, Git commits, remote side effects, and running processes are unchanged.
- A fork is not a filesystem sandbox.
- A new session loads normal system and project instructions in the same working directory. It does not inherit the old conversation.
- Fork and new-session modes require a persisted session so the original can be resumed. Navigation and compaction also work with in-memory sessions.
- Carry current user restrictions, changed paths, validation results, active process handles, remaining work, and next steps in the handoff or findings file.
- Agent-authored handoffs do not grant new permissions.
- Other extensions retain their own lifecycle and side effects. Review any extension that restores files during navigation.
Development
mise run init
npm test
mise run check
npm run fmt
Tests load the extension through Pi's real resource loader and use an offline scripted provider. They exercise tree boundaries, compaction, fresh runtime replacement, cancellation, input races, restart recovery, and actual print/JSON hosts. No real provider requests or live user sessions are needed. Offline tests also cover the release command with mocked child processes and the live test's archive selection.
npm test and npm run test:live remove an inherited PI_PACKAGE_DIR from
their test processes, so the in-process Pi SDK reads the version, docs, and
themes of the tested Pi dependency. A test fails if a different package
directory is in effect. Other Pi launches keep their own environment.
Live validation
Before releasing, run npm run test:live with an existing Pi Codex login.
It exercises inspection, all four handoff modes, automatic continuation, and
source-history preservation through the shipped Pi CLI. A separate
test loads Pi's built-in codemode extension. It checks that a script receives
a structured session_inspect response and cannot reach session_handoff. The test uses
synthetic conversations and isolated sessions. It makes billed requests.
Archive selection:
- By default the test packs the current worktree into a temporary directory
with
npm packand tests that archive. - Set
PI_PACKAGE_ARCHIVEto test a prepared archive instead. The release command does this with the archive built from the staged index. A relative path resolves against the current working directory. - A supplied value that is empty, missing, a directory, an empty file, or not a gzip tar archive fails the test. The test never falls back to packing the worktree when a value is supplied.
- Every archive must contain exactly the files in
.github/npm-package-files.
Runtime selection:
- The test runs
node_modules/@earendil-works/pi-coding-agent/dist/bundle/cli.jsby default. SetPI_TEST_CLI_PATHto another installed Picli.js. scripts/test-live.tsreads the Codex bearer token through the repository Pi'spi auth print-bearer-token. Each CLI subprocess resolves its own package directory. The test requires the selected CLI to report the version of the repository's Pi development dependency.- The subprocess uses an isolated agent directory,
PI_OFFLINE=1, andPI_TELEMETRY=0. Your Pi configuration is not read or changed.
.github/npm-package-files defines the expected npm package contents. CI checks
types, formatting, lint, repository hygiene, secrets, workflows, tests,
dependency audit, and package contents.
Release
Release flow:
- Run
npm run release -- X.Y.Zfrom a clean, synchronizedmain. It refuses to continue unlessCHANGELOG.mdhas a non-empty section for the version (Unreleasedfor prereleases). - The release command bumps the version in
package.jsonandpackage-lock.json, stages those two files, and packs the package from the staged Git index into a temporary archive. - Before signing, it runs
npm run test:livewithPI_PACKAGE_ARCHIVEset to that exact archive. The live test therefore validates the release candidate itself, not a fresh pack of the worktree. A missing prerequisite, such as an unavailable Codex login, or a failed test stops the release before any commit or tag exists. - It records the archive's SHA-256 in the SSH-signed
release: vX.Y.Zcommit, rebuilds the package from the committed tree to prove reproducibility, and creates a lightweightvX.Y.Ztag. The rebuild does not repeat the live test. - Inspect the commit and tag, then push them atomically with
git push --atomic origin main vX.Y.Z. - A read-only CI job validates the release notes, tests, packs, and inspects the package without publishing credentials.
- A separate credentialed job verifies the signed commit and exact package digest before attesting and staging that archive through npm trusted publishing.
- A final job creates the immutable GitHub release for the tag from the same
verified archive, its checksum, and the version's
CHANGELOG.mdsection. - Approve the staged package on npmjs.com or with
npm stage approve <stage-id>.
Stable versions use latest; prereleases derive a non-latest dist-tag such as
alpha from their first prerelease identifier. Installing into a user's live Pi
configuration remains a separate operation.
Recovery after a failed release
The release command never undoes its own changes. Inspect first, then recover
only what the failed attempt created. Do not run blanket git restore,
git reset, or git clean commands.
Failure before the version bump (not on main, dirty worktree, HEAD
behind origin/main, existing tag, missing changelog section): nothing changed.
Fix the reported condition and run the command again.
Failure during the version update or before the commit: version changes can
remain in package.json and package-lock.json. They are staged once the
version update and git add succeed. Package validation, live-test, and signing
failures then leave them staged. No release commit or tag was created by this
attempt. Inspect the state:
git status --short
git diff -- package.json package-lock.json
git diff --cached -- package.json package-lock.json
Undo only this attempt's version edits in the worktree and index. Preserve
concurrent changes, including edits in those same files. Do not stage whole
files containing unrelated edits. Rerun the release only after the cause is
fixed and main is clean and synchronized.
Failure after the commit (commit verification, reproducibility mismatch, or
tag checks): a local signed release: vX.Y.Z commit exists on main, and a
local vX.Y.Z tag may exist. Do not push that commit or tag, and do not rerun
the release command. Inspect the state:
git log -1 --format='%H %s%n%(trailers:key=Npm-Artifact-SHA256)'
git show --stat HEAD
git tag --points-at HEAD
git status --short
Removing the local release commit or tag changes local refs. Confirm the refs
were never pushed, record their hashes and a recovery path, and obtain explicit
approval before changing them. Never replace a published tag. Fix the cause,
for example a non-deterministic package build, and start a new release from a
clean, synchronized main.