При длительной работе с кодинг-агентом вроде Pi, Claude Code или Codex рано или поздно происходит компакция — сжатие накопленной истории диалога. Разберём, как устроен этот механизм и когда именно Pi запускает его.

Диалог с LLM

Большие языковые модели (LLM) работают с ограниченным контекстным окном — это то, что модель "видит" при формировании ответа. Архитектура трансформера, на которой построены LLM, ограничивает объём входных данных, который она может обработать. Ввод для сессии кодинг-агента включает все предыдущие сообщения и вызовы инструментов, и этот объём постоянно растёт по мере работы. Как только он превышает контекстное окно, LLM отклоняет запрос.

При интерактивной работе с кодинг-агентом вроде Pi агент отправляет запросы LLM и получает ответы. Каждый запрос включает системный промпт, загруженные файлы вроде AGENTS.md, определения инструментов и историю диалога.

Первый запрос кодинг-агента к LLM содержит этот начальный контекст вместе с первым сообщением пользователя.

request 1:
[system][tools][user]

Так начинается ход (turn). LLM может сначала вернуть сообщение ассистента с вызовами инструментов. Агент выполняет их и отправляет новый запрос к LLM с полной историей диалога, теперь включающей результаты вызовов. В ответ приходит ещё одно сообщение ассистента. Ход завершается, когда ассистент закончил генерировать вывод.

after request 1:
[system][tools][user][assistant: tool call][tool result][assistant]
                     <------------------->     ^        <--------->
                     returned by LLM           |        returned by LLM
                                               |
                                     produced by the agent

Работа продолжается, отправляется ещё одно сообщение.

request 2:
[system][tools][user][assistant: tool call][tool result][assistant][user]
                                                                     ^
                                                               new user message

Каждый ход расширяет диалог. В итоге история превышает лимит контекста. Следующий запрос возвращает ошибку вроде Request exceeds the maximum size.

[system][tools][user][assistant][....][tool result][user]
                                                      ^
                                             exceeds context window

Обработка переполнения контекста

Когда продолжить работу с существующим диалогом как есть невозможно, остаётся два пути.

  1. Начать новый, пустой диалог без накопленного контекста. Это отбрасывает всю историю, включая предыдущие решения и незавершённую работу. Иногда это оправдано, поскольку качество вывода LLM снижается по мере роста размера контекста.
  2. Создать более компактное представление контекста диалога, если продолжение этого же диалога важно сохранить. Именно этим занимается компакция.

Компакция

Теоретически компакцию можно реализовать разными способами. Например, написать детерминированную функцию, которая сохраняет часть содержимого диалога, а остальное отбрасывает. На практике же реализации компакции используют запрос к LLM для суммаризации истории диалога.

Компакция заменяет часть истории сжатым представлением, освобождая место для новых сообщений и вызовов инструментов.

[system][tools][compaction result][user]
                                    ^
                               new message

Реализация в Pi

Стоит подробнее рассмотреть, как именно Pi реализует компакцию.

Когда диалог становится слишком длинным, Pi использует компакцию для суммаризации более старого содержимого, сохраняя при этом недавнюю работу. Компакция запускается, когда лимит контекста приближается к общему размеру контекстного окна. Её также можно вызвать вручную командой /compact.

Pi проверяет необходимость авто-компакции после завершения хода. До этого момента каждый запрос расширяет существующий промпт и может переиспользовать его закэшированный префикс. Pi также может выполнить компакцию посреди хода, если возникает ошибка переполнения контекста.

При компакции Pi сохраняет некоторое количество последних сообщений без изменений.

before compaction:
[system + tools][older turns][recent retained messages]

Количество сохраняемых сообщений варьируется, поскольку Pi использует настраиваемый токенный бюджет. Текущее значение по умолчанию в 20 тысяч токенов соответствует примерно 5–20 ходам. Все сообщения до этой точки отсечения извлекаются, сериализуются и подлежат суммаризации.

Промпт компакции в Pi

Идеальный результат качественной суммаризации для кодинг-агента похож на передачу смены — краткий брифинг о состоянии дел. Промпт компакции в Pi делает акцент на том, что значительная часть существующего контекста уже неактуальна. Стоит сохранять только то, что остаётся важным контекстом для следующего запроса к LLM.

Поэтому Pi отправляет для компакции отдельный запрос, отличающийся от обычного диалогового.

  1. Системный промпт в автономном запросе компакции отличается от обычного. Вместо "you are an expert coding assistant" модели сообщается "you are a context summarization assistant".
  2. Пользовательское сообщение в запросе компакции тоже отличается. Оно запрашивает "structured summary of this conversation branch for context when returning later". Промпт указывает разделы для цели, прогресса и ключевых решений.
  3. Это автономный запрос, не использующий существующую историю диалога, а значит, для него можно применить другую модель LLM без лишних затрат.

Результат компакции добавляется в сессию Pi как запись компакции, после чего сессия может продолжаться. После запроса компакции контекст оказывается сжатым.

after compaction:
[system][tools][summary][recent turns][new user message]

Теперь в контексте диалога появляется место для гораздо большего числа сообщений.

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

Компакция и кэширование промптов

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

cached before compaction:
[system][tools][older history][recent retained turns]
<-------------------- cached prefix -------------------->

first request after compaction:
[system][tools][summary][recent retained turns][new user message]
<-- reusable -->^
                |
        first changed token
                |
                +-- everything after this point must be recomputed

Сохранённые ходы содержат те же токены, но теперь следуют за другим префиксом. Поэтому их прежнее закэшированное состояние переиспользовать нельзя.

Новые запросы после компакции снова начинают получать выгоду от кэширования промптов.

Эксперимент

Поскольку Pi расширяем и гибок в настройке, можно заменить встроенную компакцию на собственную. Чтобы протестировать другой механизм сжатия контекста, достаточно попросить Pi создать расширение с кастомным промптом компакции.