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 <path|id>
resume a specific session file or partial UUID
yes
--session-id <id>
use an exact project session id, creating it if missing
yes
--resume, -r
interactive picker to select a session
no — TUI only
--fork <path|id>
fork a session into a new one
yes
--session-dir <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 <ISO-timestamp>_<uuid>.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.
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.
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 <id>. 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.
---
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 <path\|id>` | resume a specific session file or partial UUID | yes |
| `--session-id <id>` | use an exact project session id, creating it if missing | yes |
| `--resume`, `-r` | interactive picker to select a session | no — TUI only |
| `--fork <path\|id>` | fork a session into a new one | yes |
| `--session-dir <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 `<ISO-timestamp>_<uuid>.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:<entryId>` | entries after the cursor; unknown id fails with `"Entry not found: <id>"` | 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 <id>`. 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.