@2h2d/pi-session-tools

Checkpoint inspection and context handoffs for Pi coding agents

Packages

Package details

extension

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_inspect finds or reads checkpoints across the current session tree.
  • session_handoff navigates, 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;
}
  • overview lists completed-turn checkpoints across the full current tree.
  • ancestors lists checkpoints on the path to entryId, or the active entry.
  • children lists the next checkpoints on each path from the selected checkpoint.
  • search matches literal, case-insensitive text in labels, assistant text, the latest user request, and optional tool results. It requires query.
  • read inspects an entry and nearby context. It requires entryId. 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.

  1. Validate the request, destination, and absence of pending user input.
  2. Record the request and terminate the tool batch.
  3. At agent_settled, dispatch an internal command.
  4. Navigate, fork, compact, or create the new session through Pi's public APIs.
  5. 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 pack and tests that archive.
  • Set PI_PACKAGE_ARCHIVE to 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.js by default. Set PI_TEST_CLI_PATH to another installed Pi cli.js.
  • scripts/test-live.ts reads the Codex bearer token through the repository Pi's pi 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, and PI_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:

  1. Run npm run release -- X.Y.Z from a clean, synchronized main. It refuses to continue unless CHANGELOG.md has a non-empty section for the version (Unreleased for prereleases).
  2. The release command bumps the version in package.json and package-lock.json, stages those two files, and packs the package from the staged Git index into a temporary archive.
  3. Before signing, it runs npm run test:live with PI_PACKAGE_ARCHIVE set 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.
  4. It records the archive's SHA-256 in the SSH-signed release: vX.Y.Z commit, rebuilds the package from the committed tree to prove reproducibility, and creates a lightweight vX.Y.Z tag. The rebuild does not repeat the live test.
  5. Inspect the commit and tag, then push them atomically with git push --atomic origin main vX.Y.Z.
  6. A read-only CI job validates the release notes, tests, packs, and inspects the package without publishing credentials.
  7. A separate credentialed job verifies the signed commit and exact package digest before attesting and staging that archive through npm trusted publishing.
  8. A final job creates the immutable GitHub release for the tag from the same verified archive, its checksum, and the version's CHANGELOG.md section.
  9. 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.