[Upstream sync] K-Dense-AI/scientific-agent-skills (github) — 9 added, 27 modified #43
+65
-25
@@ -2,9 +2,9 @@
|
||||
title: "Session File Format"
|
||||
task: ""
|
||||
lineage_type: import
|
||||
upstream_source: https://github.com/K-Dense-AI/scientific-agent-skills/blob/9c9bd2e9/skills/pi-agent/references/session-format.md
|
||||
upstream_sha: 9c9bd2e9
|
||||
imported_at: 2026-06-27
|
||||
upstream_source: https://github.com/K-Dense-AI/scientific-agent-skills/blob/b2a92ba0/skills/pi-agent/references/session-format.md
|
||||
upstream_sha: b2a92ba0
|
||||
imported_at: 2026-08-14
|
||||
prompt_class: unknown
|
||||
upstream_changes: accepted
|
||||
author: upstream
|
||||
@@ -15,7 +15,7 @@ validated: false
|
||||
|
||||
Source: https://pi.dev/docs/latest/session-format
|
||||
|
||||
Sessions are JSONL files. Each line is a JSON object with `type`. Entries form a tree through `id` and `parentId`.
|
||||
Sessions are JSONL files. Each line is a JSON object with a `type`. Entries form a tree through `id` / `parentId`, enabling in-place branching without new files.
|
||||
|
||||
## Location
|
||||
|
||||
@@ -23,41 +23,81 @@ Sessions are JSONL files. Each line is a JSON object with `type`. Entries form a
|
||||
~/.pi/agent/sessions/--<path>--/<timestamp>_<uuid>.jsonl
|
||||
```
|
||||
|
||||
Existing sessions auto-migrate to current version. Version 3 renamed `hookMessage` role to `custom`.
|
||||
`<path>` is the working directory with `/` replaced by `-`. Delete sessions by removing the `.jsonl` file, or from `/resume` with Ctrl+D (Pi uses the `trash` CLI when available).
|
||||
|
||||
## Message Content
|
||||
## Versions
|
||||
|
||||
Messages use content blocks: `text`, `image`, `thinking`, and `toolCall`. Base roles include `user`, `assistant`, and `toolResult`. Extended roles include `bashExecution`, `custom`, `branchSummary`, and `compactionSummary`.
|
||||
- v1: linear entry sequence (legacy, auto-migrated on load)
|
||||
- v2: tree structure with `id`/`parentId`
|
||||
- v3: renamed the `hookMessage` role to `custom` (extensions unification)
|
||||
|
||||
Assistant messages include `api`, `provider`, `model`, `usage`, `stopReason`, optional `errorMessage`, timestamp, and content blocks.
|
||||
Existing sessions auto-migrate to v3 when loaded.
|
||||
|
||||
## Content Blocks
|
||||
|
||||
`TextContent { type: "text", text }`, `ImageContent { type: "image", data (base64), mimeType }`, `ThinkingContent { type: "thinking", thinking }`, `ToolCall { type: "toolCall", id, name, arguments }`.
|
||||
|
||||
## Message Types
|
||||
|
||||
Base (from `pi-ai`):
|
||||
|
||||
- `UserMessage` — `role: "user"`, `content` string or `(Text|Image)[]`, `timestamp` (Unix ms)
|
||||
- `AssistantMessage` — `content: (Text|Thinking|ToolCall)[]`, `api`, `provider`, `model`, `usage`, `stopReason` ∈ `stop`/`length`/`toolUse`/`error`/`aborted`, optional `errorMessage`, `timestamp`
|
||||
- `ToolResultMessage` — `toolCallId`, `toolName`, `content: (Text|Image)[]`, optional `details`, optional `usage` (nested LLM work performed by the tool), `isError`, `timestamp`
|
||||
- `Usage` — `input`, `output`, `cacheRead`, `cacheWrite`, `totalTokens`, and `cost` with the same four fields plus `total`
|
||||
|
||||
The exported pi-ai `StopReason` type also includes `"pending"`, but that value is reserved for partial messages in streaming events. Terminal `done`/`error` messages replace it with a completion reason before Pi persists the assistant message, so `"pending"` should never appear in session JSONL.
|
||||
|
||||
Extended (from `pi-coding-agent`):
|
||||
|
||||
- `BashExecutionMessage` — `command`, `output`, `exitCode`, `cancelled`, `truncated`, optional `fullOutputPath`, optional `excludeFromContext` (true for `!!`)
|
||||
- `CustomMessage` — `customType`, `content`, `display`, optional `details`
|
||||
- `BranchSummaryMessage` — `summary`, `fromId`
|
||||
- `CompactionSummaryMessage` — `summary`, `tokensBefore`
|
||||
|
||||
`AgentMessage` is the union of all seven.
|
||||
|
||||
## Entry Types
|
||||
|
||||
- `session`: header, first line, metadata only.
|
||||
- `message`: wraps an `AgentMessage`.
|
||||
- `model_change`: model switches.
|
||||
- `thinking_level_change`: thinking level changes.
|
||||
- `compaction`: summary of earlier messages with `firstKeptEntryId` and `tokensBefore`.
|
||||
- `branch_summary`: summary of an abandoned branch.
|
||||
- `custom`: extension state, not sent to LLM.
|
||||
- `custom_message`: extension-injected message, sent to LLM.
|
||||
- `label`: user-defined bookmark on an entry.
|
||||
- `session_info`: display name metadata.
|
||||
All entries except the header extend `SessionEntryBase { type, id (8-char hex), parentId (null for the first entry), timestamp (ISO string) }`.
|
||||
|
||||
- `session` — header, first line, metadata only (no `id`/`parentId`): `version`, `id`, `timestamp`, `cwd`, plus `parentSession` for sessions created via `/fork`, `/clone`, or `newSession({ parentSession })`
|
||||
- `message` — wraps an `AgentMessage` in `message`
|
||||
- `model_change` — `provider`, `modelId`
|
||||
- `thinking_level_change` — `thinkingLevel`
|
||||
- `compaction` — `summary`, `tokensBefore`, plus optional `usage`, `details`, `fromHook`, `firstKeptEntryId` (old format), and `retainedTail`
|
||||
- `branch_summary` — `summary`, `fromId`, plus optional `usage`, `details`, `fromHook`
|
||||
- `custom` — `customType`, `data`; extension state, **not** in LLM context. Renderable in the transcript via `pi.registerEntryRenderer(customType, renderer)`
|
||||
- `custom_message` — `customType`, `content`, `display`, optional `details`; extension-injected and **in** LLM context
|
||||
- `label` — `targetId`, `label` (set `label` to `undefined` to clear)
|
||||
- `session_info` — `name`; set via `/name`, `--name`/`-n`, or `pi.setSessionName()`. Shown in `/resume` instead of the first message
|
||||
|
||||
`retainedTail` is a materialized `AgentMessage[]` kept after compaction. Newer harness-generated compactions include it so context rebuilds from that checkpoint without walking entries before the compaction. It is optional only for backward compatibility with sessions that store only `firstKeptEntryId`.
|
||||
|
||||
## Context Building
|
||||
|
||||
`buildSessionContext()` walks from current leaf to root. If a `CompactionEntry` is on the path, Pi emits the summary first, then messages from `firstKeptEntryId`, then later messages.
|
||||
`buildContextEntries()` walks from the current leaf to the root and produces the active entry list honoring compaction:
|
||||
|
||||
1. Collect all entries on the path.
|
||||
2. If a `CompactionEntry` is on the path: include the compaction entry first; if `retainedTail` is present it acts as a self-contained checkpoint and entries after the compaction are included; otherwise include entries from `firstKeptEntryId` to the compaction, then entries after it.
|
||||
3. Preserve non-message entries in the selected range so interactive mode can render them.
|
||||
|
||||
`buildSessionContext()` builds the LLM message list on top of that: it extracts the current model and thinking level from the full path, then converts entries — `message` → stored `AgentMessage`, `compaction` → `compactionSummary` plus `retainedTail` when present, `branch_summary` → `branchSummary`, `custom_message` → `CustomMessage`, `custom` → no context message.
|
||||
|
||||
## SessionManager API
|
||||
|
||||
Static factories: `create`, `open`, `continueRecent`, `inMemory`, `forkFrom`.
|
||||
Static creation: `create(cwd, sessionDir?)`, `open(path, sessionDir?)`, `continueRecent(cwd, sessionDir?)`, `inMemory(cwd?)`, `forkFrom(sourcePath, targetCwd, sessionDir?)`.
|
||||
|
||||
Listing: `list`, `listAll`.
|
||||
Static listing: `list(cwd, sessionDir?, onProgress?)`, `listAll(onProgress?)`.
|
||||
|
||||
Session management: `newSession`, `setSessionFile`, `createBranchedSession`.
|
||||
Session management: `newSession({ parentSession? })`, `setSessionFile(path)`, `createBranchedSession(leafId)`.
|
||||
|
||||
Append: `appendMessage`, `appendThinkingLevelChange`, `appendModelChange`, `appendCompaction`, `appendCustomEntry`, `appendSessionInfo`, `appendCustomMessageEntry`, `appendLabelChange`.
|
||||
Appending (each returns an entry ID): `appendMessage`, `appendThinkingLevelChange`, `appendModelChange`, `appendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?)`, `appendCustomEntry(customType, data?)`, `appendSessionInfo(name)`, `appendCustomMessageEntry(customType, content, display, details?)`, `appendLabelChange(targetId, label)`.
|
||||
|
||||
Tree: `getLeafId`, `getLeafEntry`, `getEntry`, `getBranch`, `getTree`, `getChildren`, `getLabel`, `branch`, `resetLeaf`, `branchWithSummary`.
|
||||
Tree navigation: `getLeafId`, `getLeafEntry`, `getEntry`, `getBranch(fromId?)`, `getTree`, `getChildren`, `getLabel`, `branch(entryId)`, `resetLeaf()`, `branchWithSummary(entryId, summary, details?, fromHook?)`.
|
||||
|
||||
Info/context: `buildSessionContext`, `getEntries`, `getHeader`, `getSessionName`, `getCwd`, `getSessionDir`, `getSessionId`, `getSessionFile`, `isPersisted`.
|
||||
Context and info: `buildContextEntries`, `buildSessionContext`, `getEntries`, `getHeader`, `getSessionName`, `getCwd`, `getSessionDir`, `getSessionId`, `getSessionFile` (undefined in memory), `isPersisted`.
|
||||
|
||||
## Parsing
|
||||
|
||||
Read the file line by line and switch on `entry.type`; treat `entry.version ?? 1` on the header, and ignore unknown types for forward compatibility. For TypeScript definitions inspect `node_modules/@earendil-works/pi-coding-agent/dist/` and `node_modules/@earendil-works/pi-ai/dist/`.
|
||||
|
||||
Reference in New Issue
Block a user