Кэш промпта
Префикс-кэш провайдера живёт, пока префикс байт-в-байт совпадает, поэтому промпт собран по летучести:
- системный промпт целиком стабилен между ходами (персона, правила, файлы
проекта
[project], списки навыков и наборов) и весь лежит подcache_control-breakpoint’ом. Файлы проекта ради этого читаются ОДИН раз на входе в тур: блок байт-стабилен между итерациями, а не перечитывается на каждой отправке; - инструменты лежат в префиксе перед system, поэтому смена
набора рвёт кэш один раз, а
scope: "turn"— ещё раз на следующем ходу, когда набор откатится. Потому дефолт —chat. Узкий набор при этом облегчает вход каждый ход, и на длинной сессии это окупается с запасом; - летучее —
[now], recall памяти, память о чате — собрано в блок[state]и едет последним блоком последнего сообщения, то есть после всей истории: префикс «инструменты + system + история» переживает ход, и якорь кэша внутри истории попадает. Живи[state]в system, «сейчас» рвало бы весь префикс каждый ход; - атрибуция чужого хода едет отдельным якорем и переезжает на свежий
tool_resultкаждой итерации: сводка перед циклом к концу тула-тяжёлого тура оказывается под десятками сообщений выхлопа, и «речь выше» становится ложью. Носитель якоря и так новый — кэш от переезда почти не страдает; - байтовое схлопывание тул-трафика (и результатов, и длинных строк входа —
граница у них одна) — один раз на входе в тур: внутри тура
история почти append-only, и каждая итерация агентного лупа читает префикс
из кэша. Единственное исключение — поджатие длинного тура
(
context.turn_squeeze_percent): когда трафик тура перерастает долю порога компакции, одним проходом схлопываются и вытесненные копии перечитанного (тот же файл, та же выдача памяти), и старый выхлоп с длинными входами. Префикс рвётся ровно раз на срабатывание, повтор — на удвоенном весе, а проход, которому нечего схлопывать, кэша не касается вовсе. Пока тур легче порога, копии едут целиком: замена по капле на каждой итерации стоила промаха всего префикса у провайдера — дороже всей экономии; - якорь кэша — последнее сообщение, чьи байты уже не изменятся (перед
elide-окном), плюс маркер на хвосте истории. Граница elide-окна считается
ОДНОЙ функцией и для элизии, и для якоря: разойдись они, якорь встал бы на
байтах, которые ещё поедут. Двигает её теперь не только счёт сообщений, но
и байтовый бюджет (
tool_result_keep_chars) — тяжёлый ход сдвигает её чаще, и это осознанная плата: занятость окна дороже промаха кэша; - телеграм-окно живой истории меряется репликами людей и схлопывается
скачками, кратными
history_limit(окно живёт в [limit, 2×limit) ходов) — префикс стабилен между скачками, а не рвётся каждый ход.
llm.prompt_cache: "5m" | "1h" включает (5m — запись в кэш 1.25×, 1h —
вдвое дороже на запись, но переживает паузы между репликами);
llm.prompt_cache_telegram переопределяет для телеграм-туров ("off" —
заглушить только там). Без кэша меняется лишь расстановка маркеров —
содержимое промпта то же. Экономику смотри в /v1/stats: cached_tokens
(прочитано из кэша) и cache_write_tokens (записано, по наценке) — в
итогах и в разрезах by_model/by_source. Записи без чтений — признак
инвалидатора или кэша, включённого там, где он не окупается.