Настройка, расширение, параметры платформы и справочник API для Pi.

Сжатие и суммирование ветвей

LLM имеют ограниченные контекстные окна. Когда разговоры становятся слишком длинными, Pi использует сжатие, чтобы суммировать старый контент, сохраняя при этом недавнюю работу. На этой странице описаны как автоматическое сжатие, так и branch summarization.

Исходные файлы (pi-mono):

Для определений TypeScript в вашем проекте проверьте node_modules/@earendil-works/pi-coding-agent/dist/.

Обзор

Pi имеет два механизма суммирования:

Механизм Курок Цель
Уплотнение Контекст превышает пороговое значение или /compact Обобщите старые сообщения, чтобы освободить контекст.
Обобщение ветвей /tree навигация Сохранять контекст при переключении ветвей

Оба используют один и тот же формат структурированной сводки и кумулятивно отслеживают операции с файлами. Запросы сжатия и сводки ветвей используют новые идентификаторы сеансов маршрутизации и, если это поддерживается поставщиком, отключают запись в кэш подсказок, поскольку эти одноразовые подсказки вряд ли будут использоваться повторно.

Уплотнение

Когда это срабатывает

Автоматическое сжатие срабатывает, когда:

contextTokens > contextWindow - reserveTokens

По умолчанию reserveTokens — это 16384 токена (настраивается в ~/.pi/agent/settings.json или <project-dir>/.pi/settings.json). Это оставляет место для ответа LLM.

Вы также можете активировать вручную с помощью /compact [instructions], где дополнительные инструкции фокусируют сводку.

Как это работает

  1. Найти точку отсечения: идти назад от самого нового сообщения, накапливая оценки токенов до тех пор, пока не будет достигнуто keepRecentTokens (по умолчанию 20 тыс., настраивается в ~/.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 также пересчитывает tokensBefore из перестроенного контекста сеанса перед записью нового CompactionEntry, поэтому количество токенов отражает фактический заменяемый контекст предварительного уплотнения.

Разделенные повороты

«Поворот» начинается с сообщения пользователя и включает в себя все ответы помощника и вызовы инструментов до следующего сообщения пользователя. Обычно уплотнение срезается на границах поворотов.

Когда один оборот превышает 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 может хранить любые 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 (если есть)

Это означает, что отслеживание файлов накапливается в нескольких уплотнениях или сводках вложенных ветвей, сохраняя полную историю чтения и изменения файлов.

Структура ввода сводки филиала

Определено в 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 символов. Содержимое, превышающее этот предел, заменяется маркером, указывающим, сколько символов было усечено. Это удерживает запросы на обобщение в пределах разумного бюджета токенов, поскольку результаты инструментов (особенно из read и bash), как правило, вносят наибольший вклад в размер контекста.

Пользовательское суммирование с помощью Extensions

Extensions может перехватывать и настраивать как уплотнение, так и branch summarization. См. extensions/types.ts для определений типов событий.

session_before_compact

Запускается перед автосжатием или /compact. Можно отменить или предоставить собственное резюме. См. SessionBeforeCompactEvent и CompactionPreparation в файле типов.

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 */ },
      }
    };
  }
});

См. SessionBeforeTreeEvent и TreePreparation в файле типов.

Настройки

Настройте уплотнение в ~/.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.