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

압축 및 분기 요약

LLM에는 제한된 컨텍스트 창이 있습니다. 대화가 너무 길어지면 Pi는 압축을 사용하여 최근 작업을 유지하면서 오래된 콘텐츠를 요약합니다. 이 페이지에서는 자동 압축과 branch summarization을 모두 다룹니다.

소스 파일(pi-mono):

프로젝트의 TypeScript 정의에 대해서는 node_modules/@earendil-works/pi-coding-agent/dist/를 검사하세요.

개요

Pi에는 두 가지 요약 메커니즘이 있습니다.

기구 방아쇠 목적
압축 컨텍스트가 임계값을 초과하거나 /compact 오래된 메시지를 요약하여 맥락을 확보하세요
지점 요약 /tree 탐색 지점 전환 시 컨텍스트 유지

둘 다 동일한 구조화된 요약 형식을 사용하고 파일 작업을 누적적으로 추적합니다. 압축 및 분기 요약 요청은 새로운 라우팅 세션 ID를 사용하며, 공급자가 지원하는 경우 이러한 일회성 프롬프트는 재사용될 가능성이 없으므로 프롬프트 캐시 쓰기를 비활성화합니다.

압축

트리거될 때

자동 압축은 다음과 같은 경우에 트리거됩니다.

contextTokens > contextWindow - reserveTokens

기본적으로 reserveTokens는 16384개의 토큰입니다(~/.pi/agent/settings.json 또는 <project-dir>/.pi/settings.json에서 구성 가능). 이는 LLM의 대응을 위한 여지를 남겨둡니다.

/compact [instructions]를 사용하여 수동으로 트리거할 수도 있습니다. 여기서 선택적 지침은 요약에 초점을 맞춥니다.

작동 방식

  1. 절단점 찾기: 최신 메시지에서 뒤로 이동하여 keepRecentTokens(기본값 20k, ~/.pi/agent/settings.json 또는 <project-dir>/.pi/settings.json에서 구성 가능)에 도달할 때까지 토큰 추정치를 누적합니다.
  2. 메시지 추출: 이전 보관된 경계(또는 세션 시작)부터 절단 지점까지의 메시지를 수집합니다.
  3. 요약 생성: LLM을 호출하여 구조화된 형식으로 요약하고 이전 요약이 있는 경우 반복 컨텍스트로 전달합니다.
  4. 항목 추가: 요약과 함께 CompactionEntryfirstKeptEntryId를 저장합니다.
  5. 새로고침: firstKeptEntryId 이후의 요약 + 메시지를 사용하여 세션을 다시 로드합니다.
Before compaction:

  entry:  0     1     2     3      4     5     6      7      8     9
        ┌─────┬─────┬─────┬─────┬──────┬─────┬──── ─┬──────┬─────┬─────┐
        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool│
        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴─────┘
                └────────┬───────┘ └──────────────┬──────────────┘
               messagesToSummarize            kept messages
                                   ↑
                          firstKeptEntryId (entry 4)

After compaction (new entry appended):

  entry:  0     1     2     3      4     5     6      7      8     9     10
        ┌─────┬─────┬─────┬─────┬──────┬─────┬──── ─┬──────┬─────┬─────┬─────┐
        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool│ cmp │
        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴─────┴─────┘
               └──────────┬──────┘ └──────────────────────┬───────────────────┘
                 not sent to LLM                    sent to LLM
                                                         ↑
                                              starts from firstKeptEntryId

What the LLM sees:

  ┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐
  │ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │
  └────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘
       ↑         ↑      └─────────────────┬────────────────┘
    prompt   from cmp          messages from firstKeptEntryId

반복되는 압축에서 요약된 범위는 압축 항목 자체가 아닌 이전 압축의 유지된 경계(firstKeptEntryId)에서 시작하며, 유지된 항목을 경로에서 찾을 수 없는 경우 이전 압축 이후의 항목으로 돌아갑니다. 이렇게 하면 다음 요약 단계에도 포함되어 이전 압축에서 살아남은 메시지가 보존됩니다. Pi는 또한 새 CompactionEntry를 작성하기 전에 재구축된 세션 컨텍스트에서 tokensBefore를 다시 계산하므로 토큰 수는 대체되는 실제 압축 전 컨텍스트를 반영합니다.

분할 회전

"턴"은 사용자 메시지로 시작되며 다음 사용자 메시지까지 모든 보조자 ​​응답 및 도구 호출을 포함합니다. 일반적으로 압축은 회전 경계에서 절단됩니다.

단일 회전이 keepRecentTokens를 초과하면 보조 메시지에 따라 컷 포인트가 회전 중간에 도착합니다. 이것이 "분할 회전"입니다.

Split turn (one huge turn exceeds budget):

  entry:  0     1     2      3     4      5      6     7      8
        ┌─────┬─────┬─────┬──────┬─────┬──────┬──────┬─────┬──────┐
        │ hdr │ usr │ ass │ tool │ ass │ tool │ tool │ ass │ tool │
        └─────┴─────┴─────┴──────┴─────┴──────┴──────┴─────┴──────┘
                ↑                                     ↑
         turnStartIndex = 1                  firstKeptEntryId = 7
                │                                     │
                └──── turnPrefixMessages (1-6) ───────┘
                                                      └── kept (7-8)

  isSplitTurn = true
  messagesToSummarize = []  (no complete turns before)
  turnPrefixMessages = [usr, ass, tool, ass, tool, tool]

분할 회전의 경우 Pi는 두 개의 요약을 생성하고 병합합니다.

  1. 기록 요약: 이전 컨텍스트(있는 경우)
  2. 턴 프리픽스 요약: 스플릿 턴의 초기 부분

컷 포인트 규칙

유효한 절단점은 다음과 같습니다.

  • 사용자 메시지
  • 어시스턴트 메시지
  • BashExecution 메시지
  • 사용자 정의 메시지(custom_message, Branch_summary)

도구 결과를 자르지 마십시오(도구 호출을 유지해야 함).

압축항목 구조

session-manager.ts에 정의됨:

interface CompactionEntry<T = unknown> {
  type: "compaction";
  id: string;
  parentId: string;
  timestamp: number;
  summary: string;
  firstKeptEntryId: string;
  tokensBefore: number;
  usage?: Usage;       // LLM usage that generated the summary
  fromHook?: boolean;  // true if provided by extension (legacy field name)
  details?: T;         // implementation-specific data
}

// Default compaction uses this for details (from compaction.ts):
interface CompactionDetails {
  readFiles: string[];
  modifiedFiles: string[];
}

Extensions는 details에 JSON 직렬화 가능한 데이터를 저장할 수 있습니다. 기본 압축은 파일 작업을 추적하지만 사용자 정의 확장 구현은 자체 구조를 사용할 수 있습니다. 생성 및 확장 제공 요약은 사용 가능한 경우 LLM usage을 저장하므로 세션 총계에는 요약 작업이 포함됩니다.

구현 방법은 prepareCompaction()compact()를 참조하세요. 직접 프로그래밍 방식 요약의 경우 generateSummary()는 요약 텍스트를 반환하고 generateSummaryWithUsage(){ text, usage }를 반환합니다.

지점 요약

트리거될 때

/tree를 사용하여 다른 분기로 이동하면 Pi를 사용하면 종료할 작업을 요약할 수 있습니다. 이렇게 하면 왼쪽 분기의 컨텍스트가 새 분기에 주입됩니다.

작동 방식

  1. 공통 조상 찾기: 이전 위치와 새 위치가 공유하는 가장 깊은 노드
  2. 항목 수집: 오래된 잎에서 다시 공통 조상까지 걸어갑니다.
  3. 예산으로 준비: 토큰 예산까지 메시지 포함(최신순)
  4. 요약 생성: 구조화된 형식으로 LLM 호출
  5. 항목 추가: 탐색 지점에 BranchSummaryEntry 저장
Tree before navigation:

         ┌─ B ─ C ─ D (old leaf, being abandoned)
    A ───┤
         └─ E ─ F (target)

Common ancestor: A
Entries to summarize: B, C, D

After navigation with summary:

         ┌─ B ─ C ─ D
    A ───┤
         └─ E ─ F ─ [summary of B,C,D] (new leaf)

누적 파일 추적

압축과 branch summarization 모두 파일을 누적적으로 추적합니다. 요약을 생성할 때 pi는 다음에서 파일 작업을 추출합니다.

  • 요약되는 메시지의 도구 호출
  • 이전 압축 또는 분기 요약 details (있는 경우)

이는 파일 추적이 여러 압축 또는 중첩된 분기 요약에 걸쳐 누적되어 읽기 및 수정된 파일의 전체 기록을 보존한다는 것을 의미합니다.

BranchSummary항목 구조

session-manager.ts에 정의됨:

interface BranchSummaryEntry<T = unknown> {
  type: "branch_summary";
  id: string;
  parentId: string;
  timestamp: number;
  summary: string;
  fromId: string;      // Entry we navigated from
  usage?: Usage;       // LLM usage that generated the summary
  fromHook?: boolean;  // true if provided by extension (legacy field name)
  details?: T;         // implementation-specific data
}

// Default branch summarization uses this for details (from branch-summarization.ts):
interface BranchSummaryDetails {
  readFiles: string[];
  modifiedFiles: string[];
}

압축과 마찬가지로 확장 프로그램은 details에 사용자 정의 데이터를 저장할 수 있습니다.

구현 방법은 collectEntriesForBranchSummary(), prepareBranchEntries()generateBranchSummary()를 참조하세요.

요약 형식

압축과 branch summarization 모두 동일한 구조 형식을 사용합니다.

## Goal
[What the user is trying to accomplish]

## Constraints & Preferences
- [Requirements mentioned by user]

## Progress
### Done
- [x] [Completed tasks]

### In Progress
- [ ] [Current work]

### Blocked
- [Issues, if any]

## Key Decisions
- **[Decision]**: [Rationale]

## Next Steps
1. [What should happen next]

## Critical Context
- [Data needed to continue]

<read-files>
path/to/file1.ts
path/to/file2.ts
</read-files>

<modified-files>
path/to/changed.ts
</modified-files>

메시지 직렬화

요약하기 전에 메시지는 serializeConversation()를 통해 텍스트로 직렬화됩니다.

[User]: What they said
[Assistant thinking]: Internal reasoning
[Assistant]: Response text
[Assistant tool calls]: read(path="foo.ts"); edit(path="bar.ts", ...)
[Tool result]: Output from tool

이렇게 하면 모델이 이를 대화로 처리하여 계속할 수 없습니다.

도구 결과는 직렬화 중에 2000자로 잘립니다. 해당 제한을 초과하는 콘텐츠는 잘린 문자 수를 나타내는 표시로 대체됩니다. 도구 결과(특히 readbash)가 일반적으로 컨텍스트 크기에 가장 큰 영향을 미치기 때문에 이렇게 하면 합당한 토큰 예산 내에서 요약 요청이 유지됩니다.

Extensions를 통한 맞춤 요약

Extensions는 압축과 branch summarization를 모두 가로채고 사용자 정의할 수 있습니다. 이벤트 유형 정의는 extensions/types.ts를 참조하세요.

session_before_compact

자동 압축 또는 /compact 전에 실행됩니다. 취소하거나 사용자 정의 요약을 제공할 수 있습니다. 유형 파일의 SessionBeforeCompactEventCompactionPreparation를 참조하세요.

pi.on("session_before_compact", async (event, ctx) => {
  const { preparation, branchEntries, customInstructions, reason, willRetry, signal } = event;

  // preparation.messagesToSummarize - messages to summarize
  // preparation.turnPrefixMessages - split turn prefix (if isSplitTurn)
  // preparation.previousSummary - previous compaction summary
  // preparation.fileOps - extracted file operations
  // preparation.tokensBefore - context tokens before compaction
  // preparation.firstKeptEntryId - where kept messages start
  // preparation.settings - compaction settings

  // branchEntries - all entries on current branch (for custom state)
  // reason - "manual" (/compact), "threshold", or "overflow"
  // willRetry - whether the aborted turn is retried after compaction (overflow recovery)
  // signal - AbortSignal (pass to LLM calls)

  // Cancel:
  return { cancel: true };

  // Custom summary:
  return {
    compaction: {
      summary: "Your summary...",
      firstKeptEntryId: preparation.firstKeptEntryId,
      tokensBefore: preparation.tokensBefore,
      // usage: summaryResponse.usage, // Optional; included in session totals
      details: { /* custom data */ },
    }
  };
});

메시지를 텍스트로 변환

자신만의 모델로 요약을 생성하려면 serializeConversation를 사용하여 메시지를 텍스트로 변환하세요.

import { convertToLlm, serializeConversation } from "@earendil-works/pi-coding-agent";

pi.on("session_before_compact", async (event, ctx) => {
  const { preparation } = event;
  
  // Convert AgentMessage[] to Message[], then serialize to text
  const conversationText = serializeConversation(
    convertToLlm(preparation.messagesToSummarize)
  );
  // Returns:
  // [User]: message text
  // [Assistant thinking]: thinking content
  // [Assistant]: response text
  // [Assistant tool calls]: read(path="..."); bash(command="...")
  // [Tool result]: output text

  // Now send to your model for summarization
  const { summary, usage } = await myModel.summarize(conversationText);
  
  return {
    compaction: {
      summary,
      firstKeptEntryId: preparation.firstKeptEntryId,
      tokensBefore: preparation.tokensBefore,
      usage,
    }
  };
});

다른 모델을 사용한 전체 예는 custom-compaction.ts를 참조하세요.

session_before_tree

/tree 탐색 전에 실행됩니다. 사용자가 요약을 선택했는지 여부에 관계없이 항상 실행됩니다. 탐색을 취소하거나 사용자 정의 요약을 제공할 수 있습니다.

pi.on("session_before_tree", async (event, ctx) => {
  const { preparation, signal } = event;

  // preparation.targetId - where we're navigating to
  // preparation.oldLeafId - current position (being abandoned)
  // preparation.commonAncestorId - shared ancestor
  // preparation.entriesToSummarize - entries that would be summarized
  // preparation.userWantsSummary - whether user chose to summarize

  // Cancel navigation entirely:
  return { cancel: true };

  // Provide custom summary (only used if userWantsSummary is true):
  if (preparation.userWantsSummary) {
    return {
      summary: {
        summary: "Your summary...",
        // usage: summaryResponse.usage, // Optional; included in session totals
        details: { /* custom data */ },
      }
    };
  }
});

유형 파일의 SessionBeforeTreeEventTreePreparation를 참조하세요.

설정

~/.pi/agent/settings.json 또는 <project-dir>/.pi/settings.json에서 압축을 구성합니다.

{
  "compaction": {
    "enabled": true,
    "reserveTokens": 16384,
    "keepRecentTokens": 20000
  }
}
환경 기본 설명
enabled true 자동 압축 활성화
reserveTokens 16384 LLM 응답을 위해 예약할 토큰
keepRecentTokens 20000 보관할 최근 토큰(요약되지 않음)

"enabled": false로 자동 압축을 비활성화합니다. /compact를 사용하여 수동으로 압축할 수도 있습니다.