Example

Read a session file and its tree

See a session as JSONL: a header, entries linked by id and parentId, a computed directory name, and a context_edit that hides without deleting.

A persistent Pi session is a plain JSONL file you can open in any editor. The first line is a header, every later line is a tree entry with an id and a parentId, and the model only ever sees the active branch. This is an illustrative trace plus a directory-name calculation, grounded in the Chapter 5 tests; it is a drawing of the shape, not a transcript from your machine.

The first line is a header

Entries form a tree. For a linear conversation each entry’s parent is the entry before it, and the header is not part of the tree:

{"type":"session","version":3,"id":"<session-id>"}
{"type":"message","id":"a1b2c3d4","parentId":null,"timestamp":"2026-10-01T09:00:00.000Z","message":{"role":"user","content":"question A"}}
{"type":"message","id":"b2c3d4e5","parentId":"a1b2c3d4","timestamp":"2026-10-01T09:00:01.000Z","message":{"role":"assistant","content":"answer A"}}
{"type":"message","id":"c3d4e5f6","parentId":"b2c3d4e5","timestamp":"2026-10-01T09:00:02.000Z","message":{"role":"user","content":"question B"}}

The test asserts this on the real file: header first, then one entry per message in order, ids unique, and the first entry’s parentId is null. It also shows stored is not sent: model_change, thinking_level_change and appendCustomEntry entries sit in the file and never reach the provider.

The directory name is a calculation

The default location is ~/.pi/agent/sessions/--<path>--/<timestamp>_<session-id>.jsonl. Pi drops the leading separator of the working directory, then replaces /, \ and : with -:

Working directory Directory component
C:\Projects\pi --C--Projects-pi--
/home/u/app --home-u-app--

The drive letter stays; no separator and no colon survives. The session id is a UUID by default, which the filename pattern in the test confirms.

A context edit hides without deleting

Appending a context_edit with replacement: null omits the target from future requests and leaves the entry in the file:

{"type":"context_edit","id":"g6h7i8j9","parentId":"f6g7h8i9","timestamp":"2026-10-01T09:00:03.000Z","targetId":"a1b2c3d4","replacement":null}

The edit is branch-relative: navigating to a point before it (the assistant’s answer to A) reveals the original contribution again. A replacement object such as { "content": "REDACTED" } rewrites the message for the model and leaves the stored entry untouched; the replacement must be an object with content, not a bare string.

Mechanism and limitations

Documented: the session-file format and location formula in the pinned session-format.md, and the base entry fields (type, id, parentId, timestamp) declared in examples/ch27-session-entries/declared.ts. That declaration is Chapter 27’s subject and is referenced here, not reproduced. Observed: disk.test.ts reads the real file written by the shared history and asserts the header order, the tree links, the stored-not-sent entries, the branch-relative edit, and the directory name on the shipped binary.

The trace is illustrative and its ids are shortened; it shows the entry shapes, not a complete file. Stored is not sent in either direction: an entry in the file is not proof the model saw it, and the active branch — after compaction and edits — is what a request contains. Chapter 24 covers branching and Chapter 27 the full format.

Full source and test .

Understand this example

Copy this prompt into your AI tool. No code runs here.

Apply this example

Copy this prompt into your AI tool. No code runs here.