omp packages

@2h2d/pi-session-tools0.0.9

Checkpoint inspection and context handoffs for Pi coding agents

GitHub Actions Published 2 days ago MIT ~1.4K downloads/mo

Untested on omp

This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.

Did it work for you?

Result

One report per person. A new report replaces your old one.

Reports 0

No reports yet.

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.
  • /handoff does the same for you: type a mode and a message, and the agent continues from the new context.

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.3.0 and Node.js 22.23.3. The package requires Pi >=1.3.0 <1.4.0 and Node.js >=22.19.0. The extension refuses to load on a Pi older than 1.3.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: entry=<entry-id>]

Use these IDs directly. There is no need to query history before every handoff. A fork copies the path up to its fork point with the same entry IDs, so the checkpoints on that path stay valid in the fork. Checkpoints after the fork point or on other branches stay in the source session only. The extension never puts the current session ID into the conversation, because Pi gives a fork a new session ID while copying its history. 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 one handoff:

{
  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

{
  "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

{
  "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, run /handoff apply <operation-id>.
  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. In every mode, the handoff message itself starts the next run. The model receives it as a user-role message, but the session records it as an extension message, not as user input, so input handlers do not see it. Pi prepares the run like a prompt, so it carries current system instructions, project instructions, and extension prompt and tool changes. The handoff 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. 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. When a resumed session has unfinished records, the next run receives a hidden notice that lists them. Inspect the current context and source history before requesting a replacement operation. A handoff applies only in the session that accepted it. 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 apply form applies only the agent request that is still waiting in memory. Typing it with any other ID changes nothing, so it cannot replay a saved request.

/handoff command

/handoff new <message>
/handoff fork <checkpoint-id> <message>
/handoff navigate <checkpoint-id> <message>
/handoff compact <message>

The command performs the same four modes for you. Your message becomes the handoff and starts the next agent run. It is marked as user-authored and carries the same source checkpoint and workspace reminders as an agent handoff. The message has the same 16,000-character limit. compact uses Pi's default summary instructions. Use the tool for findings files or custom summary instructions.

In the interactive UI and RPC, you can leave parts out. With no arguments, the command asks for the mode. For fork and navigate without a checkpoint ID, it lists the 50 newest checkpoints across the session tree with their entry ID and a short preview. Without a message, it opens an editor. Cancelling any step changes nothing. Print and JSON modes have no dialogs, so there the command needs every part.

The command runs only when the agent is idle. It applies the same checks as an agent handoff: no queued input, no unsubmitted editor draft, a valid checkpoint, and a persisted session for fork and new. It records the same operation journal.

If another extension also registers /handoff, Pi lists the two commands as /handoff:1 and /handoff:2 in load order. Type the one whose description starts with "Start a new session, fork". Agent handoffs find this extension's command by that description and keep working.

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. Handoffs you send with /handoff are your own instructions.
  • 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 second test forks with a typed /handoff command. A third 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 tests use synthetic conversations and isolated sessions. They make 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.