Pi 구성, 확장, 플랫폼 설정 및 API 참조.

세션 파일 형식

세션은 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)의 출처:

프로젝트의 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/undefined pi로 생성된 경우(레거시 필드 이름)
  • 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/undefined pi로 생성된 경우(레거시 필드 이름)

맞춤 항목

확장 상태 지속성. 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() 현재 리프에서 루트로 이동하여 압축을 유지하면서 활성 항목 목록을 생성합니다.

  1. 경로의 모든 항목을 수집합니다.
  2. CompactionEntry가 경로에 있는 경우:
    • 먼저 압축 항목을 포함합니다.
    • retainedTail가 있는 경우 독립형 체크포인트 역할을 하며 압축 후 항목이 포함됩니다.
    • 그렇지 않으면 firstKeptEntryId부터 압축까지의 항목이 포함됩니다.
    • 그런 다음 압축 후 항목이 포함됩니다.
  3. 대화형 모드에서 렌더링할 수 있도록 선택한 범위의 메시지가 아닌 항목을 보존합니다.

buildSessionContext() 해당 항목 목록을 기반으로 LLM에 대한 메시지 목록을 생성합니다.

  1. 전체 경로에서 현재 모델 및 사고 수준 설정을 추출합니다.
  2. 선택한 항목을 메시지로 변환합니다.
    • message -> 저장됨 AgentMessage
    • compaction -> compactionSummary + retainedTail 존재하는 경우
    • branch_summary -> branchSummary
    • custom_message -> CustomMessage
    • custom -> 컨텍스트 메시지 없음

이로 인해 최신 압축이 독립적인 체크포인트처럼 작동합니다. 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() - 세션 UUID
  • getSessionFile() - 세션 파일 경로(인메모리의 경우 정의되지 않음)
  • isPersisted() - 세션이 디스크에 저장되는지 여부