Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

neovim/.system/research/NVIM-RESEARCH-NOSZ7JUQ-pi-session-resume-and-transcript-backfill-capabi/index.md

Raw
Rendered preview

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.
---
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.