[Upstream sync] K-Dense-AI/scientific-agent-skills (github) — 9 added, 27 modified #43

Open
promptadmin wants to merge 36 commits from upstream-sync/scientific-agent-skills-20260814-b2a92b-psgv into main
Showing only changes of commit a3b4ac8442 - Show all commits
@@ -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/`.