세션 파일 형식
세션은 JSONL(JSON Lines) 파일로 저장됩니다. 각 줄은 type 필드가 있는 JSON 객체입니다. 세션 항목은 id/parentId 필드를 통해 트리 구조를 형성하므로 새 파일을 만들지 않고도 내부 분기가 가능합니다.
파일 위치
~/.pi/agent/sessions/--<path>--/<timestamp>_<uuid>.jsonl<path>는 /가 -로 대체된 작업 디렉토리입니다.
세션 삭제
~/.pi/agent/sessions/ 아래의 .jsonl 파일을 삭제하면 세션을 제거할 수 있습니다.
Pi는 /resume에서 대화형으로 세션을 삭제할 수도 있습니다(세션을 선택하고 Ctrl+D를 누른 다음 확인). 가능한 경우 pi는 trash CLI를 사용하여 영구 삭제를 방지합니다.
세션 버전
세션 헤더에는 버전 필드가 있습니다.
- 버전 1: 선형 항목 순서(레거시, 로드 시 자동 마이그레이션)
- 버전 2:
id/parentId연결이 포함된 트리 구조 - 버전 3:
hookMessage역할 이름을custom로 변경(확장 프로그램 통합)
기존 세션은 로드 시 자동으로 현재 버전(v3)으로 마이그레이션됩니다.
소스 파일
GitHub(pi-mono)의 출처:
packages/coding-agent/src/core/session-manager.ts- 세션 항목 유형 및 SessionManagerpackages/coding-agent/src/core/messages.ts- 확장된 메시지 유형(BashExecutionMessage, CustomMessage 등)packages/ai/src/types.ts- 기본 메시지 유형(UserMessage, AssistantMessage, ToolResultMessage)packages/agent/src/types.ts- AgentMessage 통합 유형
프로젝트의 TypeScript 정의에 대해서는 node_modules/@earendil-works/pi-coding-agent/dist/ 및 node_modules/@earendil-works/pi-ai/dist/를 검사하세요.
메시지 유형
세션 항목에는 AgentMessage 개체가 포함되어 있습니다. 세션을 구문 분석하고 확장을 작성하려면 이러한 유형을 이해하는 것이 필수적입니다.
콘텐츠 블록
메시지에는 입력된 콘텐츠 블록의 배열이 포함됩니다.
interface TextContent {
type: "text";
text: string;
}
interface ImageContent {
type: "image";
data: string; // base64 encoded
mimeType: string; // e.g., "image/jpeg", "image/png"
}
interface ThinkingContent {
type: "thinking";
thinking: string;
}
interface ToolCall {
type: "toolCall";
id: string;
name: string;
arguments: Record<string, any>;
}기본 메시지 유형(pi-ai에서)
interface UserMessage {
role: "user";
content: string | (TextContent | ImageContent)[];
timestamp: number; // Unix ms
}
interface AssistantMessage {
role: "assistant";
content: (TextContent | ThinkingContent | ToolCall)[];
api: string;
provider: string;
model: string;
usage: Usage;
stopReason: "stop" | "length" | "toolUse" | "error" | "aborted";
errorMessage?: string;
timestamp: number;
}
interface ToolResultMessage {
role: "toolResult";
toolCallId: string;
toolName: string;
content: (TextContent | ImageContent)[];
details?: any; // Tool-specific metadata
usage?: Usage; // Nested LLM work performed by the tool
isError: boolean;
timestamp: number;
}
interface Usage {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
totalTokens: number;
cost: {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
total: number;
};
}내보낸 pi-ai StopReason 유형에는 "pending"도 포함되지만 해당 값은 스트리밍 이벤트의 부분 메시지용으로 예약되어 있습니다. 터미널 done/error 메시지는 pi가 보조 메시지를 지속하기 전에 이를 완료 이유로 대체하므로 "pending"는 세션 JSONL에 절대 나타나서는 안 됩니다.
확장 메시지 유형(pi-coding-agent에서)
interface BashExecutionMessage {
role: "bashExecution";
command: string;
output: string;
exitCode: number | undefined;
cancelled: boolean;
truncated: boolean;
fullOutputPath?: string;
excludeFromContext?: boolean; // true for !! prefix commands
timestamp: number;
}
interface CustomMessage {
role: "custom";
customType: string; // Extension identifier
content: string | (TextContent | ImageContent)[];
display: boolean; // Show in TUI
details?: any; // Extension-specific metadata
timestamp: number;
}
interface BranchSummaryMessage {
role: "branchSummary";
summary: string;
fromId: string; // Entry we branched from
timestamp: number;
}
interface CompactionSummaryMessage {
role: "compactionSummary";
summary: string;
tokensBefore: number;
timestamp: number;
}AgentMessage Union
type AgentMessage =
| UserMessage
| AssistantMessage
| ToolResultMessage
| BashExecutionMessage
| CustomMessage
| BranchSummaryMessage
| CompactionSummaryMessage;진입 기지
모든 항목(SessionHeader 제외)은 SessionEntryBase을 확장합니다.
interface SessionEntryBase {
type: string;
id: string; // 8-char hex ID
parentId: string | null; // Parent entry ID (null for first entry)
timestamp: string; // ISO timestamp
}항목 유형
세션헤더
파일의 첫 번째 줄. 메타데이터만 해당되며 트리의 일부는 아닙니다(id/parentId 없음).
{"type":"session","version":3,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project"}부모와 함께하는 세션의 경우(/fork, /clone 또는 newSession({ parentSession })를 통해 생성됨):
{"type":"session","version":3,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project","parentSession":"/path/to/original/session.jsonl"}세션메시지항목
대화의 메시지입니다. message 필드에는 AgentMessage이 포함되어 있습니다.
{"type":"message","id":"a1b2c3d4","parentId":"prev1234","timestamp":"2024-12-03T14:00:01.000Z","message":{"role":"user","content":"Hello"}}
{"type":"message","id":"b2c3d4e5","parentId":"a1b2c3d4","timestamp":"2024-12-03T14:00:02.000Z","message":{"role":"assistant","content":[{"type":"text","text":"Hi!"}],"provider":"anthropic","model":"claude-sonnet-4-5","usage":{...},"stopReason":"stop"}}
{"type":"message","id":"c3d4e5f6","parentId":"b2c3d4e5","timestamp":"2024-12-03T14:00:03.000Z","message":{"role":"toolResult","toolCallId":"call_123","toolName":"bash","content":[{"type":"text","text":"output"}],"isError":false}}모델변경항목
사용자가 세션 중에 모델을 전환할 때 발생합니다.
{"type":"model_change","id":"d4e5f6g7","parentId":"c3d4e5f6","timestamp":"2024-12-03T14:05:00.000Z","provider":"openai","modelId":"gpt-4o"}사고수준변경항목
사용자가 사고/추리 수준을 변경할 때 발생합니다.
{"type":"thinking_level_change","id":"e5f6g7h8","parentId":"d4e5f6g7","timestamp":"2024-12-03T14:06:00.000Z","thinkingLevel":"high"}압축항목
컨텍스트가 압축될 때 생성됩니다. 이전 메시지의 요약을 저장합니다.
{"type":"compaction","id":"f6g7h8i9","parentId":"e5f6g7h8","timestamp":"2024-12-03T14:10:00.000Z","summary":"User discussed X, Y, Z...","firstKeptEntryId":"c3d4e5f6","tokensBefore":50000}최신 하네스 생성 압축은 firstKeptEntryId 대신 유지된 압축 후 컨텍스트를 항목에 직접 포함합니다.
{"type":"compaction","id":"f6g7h8i9","parentId":"e5f6g7h8","timestamp":"2024-12-03T14:10:00.000Z","summary":"User discussed X, Y, Z...","tokensBefore":50000,"retainedTail":[{"role":"user","content":"latest request"},{"role":"assistant","content":[{"type":"text","text":"latest reply"}],"provider":"anthropic","model":"claude-sonnet-4-5","usage":{...},"stopReason":"stop"}]}선택 필드:
usage: 요약 생성에서 LLM 사용; 세션 토큰 및 총 비용에 포함됨retainedTail: 구체화됨AgentMessage[]압축 후 유지됩니다. 이는 이전 세션과의 호환성을 위해서만 선택 사항입니다. 최신 하네스 생성 압축에는 이를 포함하므로 압축 항목 이전에 이전 항목을 탐색하지 않고도 이 체크포인트에서 컨텍스트를 다시 빌드할 수 있습니다.details: 구현별 데이터(예: 기본값의 경우{ readFiles: string[], modifiedFiles: string[] }, 확장의 경우 맞춤 데이터)fromHook:true확장 프로그램에 의해 생성된 경우,false/undefinedpi로 생성된 경우(레거시 필드 이름)firstKeptEntryId: 이전 항목 형식과의 호환성을 위해.
분기요약 항목
공통 조상까지 왼쪽 분기의 LLM 생성 요약을 사용하여 /tree를 통해 분기를 전환할 때 생성됩니다. 버려진 경로에서 컨텍스트를 캡처합니다.
{"type":"branch_summary","id":"g7h8i9j0","parentId":"a1b2c3d4","timestamp":"2024-12-03T14:15:00.000Z","fromId":"f6g7h8i9","summary":"Branch explored approach A..."}선택 필드:
usage: 요약 생성에서 LLM 사용; 세션 토큰 및 총 비용에 포함됨details: 기본 파일 추적 데이터({ readFiles: string[], modifiedFiles: string[] }) 또는 확장 프로그램의 맞춤 데이터fromHook:true확장 프로그램에 의해 생성된 경우,false/undefinedpi로 생성된 경우(레거시 필드 이름)
맞춤 항목
확장 상태 지속성. LLM 컨텍스트에 참여하지 않습니다.
{"type":"custom","id":"h8i9j0k1","parentId":"g7h8i9j0","timestamp":"2024-12-03T14:20:00.000Z","customType":"my-extension","data":{"count":42}}다시 로드할 때 확장 프로그램 항목을 식별하려면 customType를 사용하세요. 대화형 모드는 pi.registerEntryRenderer(customType, renderer)를 통해 사용자 정의 항목을 렌더링할 수 있지만 여전히 LLM 컨텍스트에 참여하지 않습니다.
사용자 정의 메시지 항목
LLM 컨텍스트에 참여하는 확장 프로그램 삽입 메시지입니다.
{"type":"custom_message","id":"i9j0k1l2","parentId":"h8i9j0k1","timestamp":"2024-12-03T14:25:00.000Z","customType":"my-extension","content":"Injected context...","display":true}전지:
content: 문자열 또는(TextContent | ImageContent)[](UserMessage와 동일)display:true= 독특한 스타일로 TUI에 표시,false= 숨김details: 선택적 확장 관련 메타데이터(LLM으로 전송되지 않음)
라벨 항목
항목에 대한 사용자 정의 북마크/마커입니다.
{"type":"label","id":"j0k1l2m3","parentId":"i9j0k1l2","timestamp":"2024-12-03T14:30:00.000Z","targetId":"a1b2c3d4","label":"checkpoint-1"}라벨을 지우려면 label에서 undefined로 설정하세요.
세션정보항목
세션 메타데이터(예: 사용자 정의 표시 이름) 확장 프로그램에서 /name, --name / -n 또는 pi.setSessionName()를 통해 설정하세요.
{"type":"session_info","id":"k1l2m3n4","parentId":"j0k1l2m3","timestamp":"2024-12-03T14:35:00.000Z","name":"Refactor auth module"}세션 이름은 설정 시 첫 번째 메시지 대신 세션 선택기(/resume)에 표시됩니다.
트리 구조
항목은 트리를 형성합니다.
- 첫 번째 항목은
parentId: null입니다. - 각 후속 항목은
parentId를 통해 상위 항목을 가리킵니다. - 분기는 이전 항목에서 새 하위 항목을 만듭니다.
- "리프"는 트리의 현재 위치입니다.
[user msg] ─── [assistant] ─── [user msg] ─── [assistant] ─┬─ [user msg] ← current leaf
│
└─ [branch_summary] ─── [user msg] ← alternate branch컨텍스트 구축
buildContextEntries() 현재 리프에서 루트로 이동하여 압축을 유지하면서 활성 항목 목록을 생성합니다.
- 경로의 모든 항목을 수집합니다.
CompactionEntry가 경로에 있는 경우:- 먼저 압축 항목을 포함합니다.
retainedTail가 있는 경우 독립형 체크포인트 역할을 하며 압축 후 항목이 포함됩니다.- 그렇지 않으면
firstKeptEntryId부터 압축까지의 항목이 포함됩니다. - 그런 다음 압축 후 항목이 포함됩니다.
- 대화형 모드에서 렌더링할 수 있도록 선택한 범위의 메시지가 아닌 항목을 보존합니다.
buildSessionContext() 해당 항목 목록을 기반으로 LLM에 대한 메시지 목록을 생성합니다.
- 전체 경로에서 현재 모델 및 사고 수준 설정을 추출합니다.
- 선택한 항목을 메시지로 변환합니다.
message-> 저장됨AgentMessagecompaction->compactionSummary+retainedTail존재하는 경우branch_summary->branchSummarycustom_message->CustomMessagecustom-> 컨텍스트 메시지 없음
이로 인해 최신 압축이 독립적인 체크포인트처럼 작동합니다. retainedTail는 선택 사항이므로 firstKeptEntryId만 저장하는 이전 세션이 계속해서 올바르게 로드됩니다.
파싱 예
import { readFileSync } from "fs";
const lines = readFileSync("session.jsonl", "utf8").trim().split("\n");
for (const line of lines) {
const entry = JSON.parse(line);
switch (entry.type) {
case "session":
console.log(`Session v${entry.version ?? 1}: ${entry.id}`);
break;
case "message":
console.log(`[${entry.id}] ${entry.message.role}: ${JSON.stringify(entry.message.content)}`);
break;
case "compaction":
console.log(`[${entry.id}] Compaction: ${entry.tokensBefore} tokens summarized`);
break;
case "branch_summary":
console.log(`[${entry.id}] Branch from ${entry.fromId}`);
break;
case "custom":
console.log(`[${entry.id}] Custom (${entry.customType}): ${JSON.stringify(entry.data)}`);
break;
case "custom_message":
console.log(`[${entry.id}] Extension message (${entry.customType}): ${entry.content}`);
break;
case "label":
console.log(`[${entry.id}] Label "${entry.label}" on ${entry.targetId}`);
break;
case "model_change":
console.log(`[${entry.id}] Model: ${entry.provider}/${entry.modelId}`);
break;
case "thinking_level_change":
console.log(`[${entry.id}] Thinking: ${entry.thinkingLevel}`);
break;
}
}세션 관리자 API
프로그래밍 방식으로 세션을 작업하기 위한 주요 방법입니다.
정적 생성 방법
SessionManager.create(cwd, sessionDir?)- 새 세션SessionManager.open(path, sessionDir?)- 기존 세션 파일 열기SessionManager.continueRecent(cwd, sessionDir?)- 최신 항목을 계속 진행하거나 새로 만들기SessionManager.inMemory(cwd?)- 파일 지속성 없음SessionManager.forkFrom(sourcePath, targetCwd, sessionDir?)- 다른 프로젝트의 포크 세션
정적 상장 방법
SessionManager.list(cwd, sessionDir?, onProgress?)- 디렉토리의 세션 나열SessionManager.listAll(onProgress?)- 모든 프로젝트의 모든 세션 나열
인스턴스 메소드 - 세션 관리
newSession(options?)- 새 세션 시작(옵션:{ parentSession?: string })setSessionFile(path)- 다른 세션 파일로 전환createBranchedSession(leafId)- 새 세션 파일로 분기 추출
인스턴스 메서드 - 추가(모든 반품 항목 ID)
appendMessage(message)- 메시지 추가appendThinkingLevelChange(level)- 생각의 변화를 기록하세요appendModelChange(provider, modelId)- 모델 변경 기록appendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?)- 압축 추가appendCustomEntry(customType, data?)- 확장 상태(컨텍스트가 아님)appendSessionInfo(name)- 세션 표시 이름 설정appendCustomMessageEntry(customType, content, display, details?)- 확장 메시지(컨텍스트)appendLabelChange(targetId, label)- 라벨 설정/삭제
인스턴스 메서드 - 트리 탐색
getLeafId()- 현재 위치getLeafEntry()- 현재 리프 항목 가져오기getEntry(id)- ID로 항목 받기getBranch(fromId?)- 입구에서 뿌리까지 걷기getTree()- 전체 트리 구조 얻기getChildren(parentId)- 자녀를 직접 만나보세요getLabel(id)- 입장 라벨 받기branch(entryId)- 리프를 이전 항목으로 이동resetLeaf()- 리프를 null로 재설정(항목 전)branchWithSummary(entryId, summary, details?, fromHook?)- 컨텍스트 요약이 포함된 분기
인스턴스 메소드 - 컨텍스트 및 정보
buildContextEntries()- 압축이 적용된 활성 분기 항목 가져오기buildSessionContext()- LLM을 위한 메시지, ThinkingLevel 및 모델 가져오기getEntries()- 모든 항목(헤더 제외)getHeader()- 세션 헤더 메타데이터getSessionName()- 최신 session_info 항목에서 표시 이름 가져오기getCwd()- 작업 디렉토리getSessionDir()- 세션 저장 디렉터리getSessionId()- 세션 UUIDgetSessionFile()- 세션 파일 경로(인메모리의 경우 정의되지 않음)isPersisted()- 세션이 디스크에 저장되는지 여부