--- id: NVIM-RESEARCH-NOSZ7JUQ type: research title: Pi session resume and transcript backfill capabilities --- # Pi session resume and transcript backfill capabilities Verified against the installed Pi `0.87.1` (`pi --mode rpc`), not from prose. Probed live by resuming a real session with `--continue` and issuing query RPCs. ## Resume is a native Pi capability Pi already resolves "last session in cwd"; pivi need not enumerate to get the common case. | Flag | Meaning | Usable over RPC | | --- | --- | --- | | `--continue`, `-c` | resume the most recent session for the project (cwd) | yes | | `--session ` | resume a specific session file or partial UUID | yes | | `--session-id ` | use an exact project session id, creating it if missing | yes | | `--resume`, `-r` | interactive picker to select a session | no — TUI only | | `--fork ` | fork a session into a new one | yes | | `--session-dir ` | directory for session storage and lookup | yes | | `--no-session` | ephemeral, nothing saved | yes | | `--name`, `-n` | display name only, not a resume key | yes | `PI_CODING_AGENT_SESSION_DIR` sets the storage directory (`--session-dir` overrides it). On this machine it resolves to `/home/me/.local/state/pi/sessions`, not the `~/.pi/agent/sessions` shown in `--help` examples; resolve it at runtime, never hardcode. ## Session storage layout - Sessions are bucketed per project under the session dir, one directory per cwd. - The bucket directory name encodes the cwd (`/` to `-`, wrapped in `--…--`), but the encoding is lossy and ambiguous; do not decode it. - Each session is a file `_.jsonl`. - The first line is a header: `{ "type":"session", "id", "timestamp", "cwd" }`. - Robust cwd enumeration: read each file's header `cwd` and sort by header `timestamp` or the filename timestamp. This is authoritative; the bucket name is not. - There is no session-listing RPC. The Sessions RPC surface is `new_session`, `switch_session`, `fork`, `clone`, `set_session_name`. Listing for a cwd picker must read the filesystem. ## Transcript backfill RPCs All three return `success:true` on a resumed session with no prompt sent, so backfill performs no model call. | RPC | Response shape | Use | | --- | --- | --- | | `get_entries` | `{ entries: [ { type, id, parentId, timestamp, … } ] }` | flat log, authoritative for full re-render | | `get_entries` with `since:` | entries after the cursor; unknown id fails with `"Entry not found: "` | durable incremental cursor | | `get_tree` | `{ tree: [ { entry, children: [ … ] } ] }`, recursive | branch and leaf detection | | `get_messages` | `{ messages: [ { role, content, sections:{ preamble, … } } ] }` | rendered messages, includes the system preamble | ### Entry vocabulary `get_entries` entry `type` values observed: `model_change`, `thinking_level_change`, `message`, `session_info`, `custom`. A `message` entry carries a full message object under `.message`. Message roles: `system`, `user`, `assistant`, `toolResult`. - Assistant `message.content` is a list of parts, each tagged by `type`: - `{ type:"thinking", thinking, thinkingSignature }` - `{ type:"tool_call", id, name, arguments }` where `arguments` is a decoded object, not a string. - text parts. - Assistant messages also carry `usage`, `stopReason`, `model`, `provider`, `responseId`. - A tool result is its own message: `{ role:"toolResult", toolCallId, toolName, content:[{type,text}], isError, timestamp }`, matched to its call by `toolCallId`. ## Conclusions - `:Pi resume` default ("last in cwd") maps directly to `--continue`; a specific target maps to `--session `. No encoding of the bucket name is required for either. - A cwd session picker needs a filesystem scan of the session dir, filtering by each header's `cwd`, because no listing RPC exists. - Full transcript re-render is possible from `get_entries`: it carries tool calls (as assistant content parts) and tool results (as `toolResult` messages) correlated by id, plus model and usage. - The stored shape differs from the live streaming shape. The live stream delivers `message_update` deltas and rich tool-result `details` (display diff, unified patch, first changed line, truncation, output path); a stored `toolResult` carries only `content` text and `isError`. A backfill adapter is therefore distinct from the live assembly path, and backfilled tool entries degrade to subject and text rather than a reconstructed diff, consistent with "never invent an outcome". ## Unresolved - Exact, reversible bucket-name encoding. Treated as opaque; headers are read instead. - Whether any persisted `toolResult` ever includes the live `details` (diff/patch/truncation); none seen in the sample. Decides whether backfilled edits can show a diff or only text. - `leafId` selection: `get_tree` exposes branches via `children`, but which leaf is the current head on resume is not yet pinned. - Whether `--continue` over `--mode rpc` always attaches without a model call under all providers; the probe showed query-only RPCs succeed with no prompt.