Pi の設定、拡張、プラットフォーム設定、API リファレンス。

圧縮とブランチの要約

LLM のコンテキスト ウィンドウは限られています。会話が長くなりすぎると、Pi は圧縮を使用して、最近の内容を保持しながら古いコンテンツを要約します。このページでは、自動圧縮と branch summarization の両方について説明します。

ソース ファイル (pi-mono):

プロジェクト内の TypeScript 定義については、 node_modules/@earendil-works/pi-coding-agent/dist/ を調べてください。

概要

Pi には 2 つの要約メカニズムがあります。

機構 トリガー 目的
圧縮 コンテキストがしきい値を超えているか、または /compact 古いメッセージを要約してコンテキストを解放する
ブランチの要約 /tree ナビゲーション ブランチを切り替えるときにコンテキストを保持する

どちらも同じ構造化された要約形式を使用し、ファイル操作を累積的に追跡します。圧縮およびブランチサマリー要求では、新しいルーティング セッション ID が使用され、プロバイダーによってサポートされている場合は、これらの 1 回限りのプロンプトが再利用される可能性が低いため、プロンプト キャッシュの書き込みが無効になります。

圧縮

トリガーされるとき

自動圧縮は次の場合にトリガーされます。

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. エントリを追加: CompactionEntry を概要とともに保存し、firstKeptEntryId
  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 を再計算するため、トークン数は、置き換えられる実際の圧縮前のコンテキストを反映します。

スプリットターン

「ターン」はユーザー メッセージで始まり、次のユーザー メッセージまでのすべてのアシスタントの応答とツールの呼び出しが含まれます。通常、圧縮はターン境界でカットされます。

1 つのターンが 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 は 2 つのサマリーを生成し、それらをマージします。

  1. 履歴の概要: 以前のコンテキスト (存在する場合)
  2. ターン プレフィックスの概要: スプリット ターンの前半部分

カットポイントのルール

有効なカットポイントは次のとおりです。

  • ユーザーメッセージ
  • アシスタントのメッセージ
  • Bash実行メッセージ
  • カスタムメッセージ (custom_message、branch_summary)

ツールの結果では決してカットしないでください (ツールの呼び出しを維持する必要があります)。

CompactionEntry 構造体

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 は、JSON でシリアル化可能なデータを details に保存できます。デフォルトの圧縮ではファイル操作が追跡されますが、カスタム拡張機能の実装では独自の構造を使用できます。生成され、拡張機能が提供する要約は、利用可能な場合は 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 (存在する場合)

これは、複数の圧縮またはネストされたブランチ サマリーにわたってファイル追跡が蓄積され、読み取りおよび変更されたファイルの完全な履歴が保存されることを意味します。

Branch SummaryEntry 構造体

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 を参照してください。

セッション前_コンパクト

自動圧縮または /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 を参照してください。

セッション前ツリー

/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 を使用して手動で圧縮することもできます。