y83 code / инструмент

Cache watch

Cache watch — сторож prompt-кэша шлюза. Кэш кэширует префикс запроса, и при длинных сессиях процент попадания (cachedInputTokens / inputTokens) иногда падает без видимой причины: контекст у шлюза «залип», и каждый запрос оплачивается полной стоимостью.

раздел реестра
Codex
код
общий модуль чата Codex
документ
17-codex_cache_watch.md
Раздел: Codex
Идентификатор: codex_cache_watch · Экран: ui/codex/CodexCacheWatchScreen.kt (619)
Снимок: 2026-10-12

1. Зачем и что это

Сторож ловит именно эту аномалию: если кэш не попал, хотя запрос был крупным и контекст почти не менялся, это ненормально. Реакция зависит от режима:

  • Manual — в чате появляется баннер «кэш залип» с кнопкой;
  • Auto — сессия автоматически ротируется (новый session id), и расход падает.

Судит сторож только те модели, у которых на бэкенде есть лейн кэш-сессии (promptCache у провайдера — шлюз opencode.ai напрямую и встроенный zen-прокси): у прямых API и удалённых серверов ротация ничего не сбрасывает, и промах кэша там ничего не значит.

Экран объединяет два разных источника данных в одном скролле: настройки сторожа (локальные prefs агента) и историю ротаций (с backend).

2. Как попасть

Хаб инструментов → секция Codex → карточка «Cache watch» (hub_codex_cache_watch_title, тайл на главной — «Watch»).

Откуда ещёПуть
Меню треда Codexярлык на тот же экран (codex@cache-watch)
Мостapp_navigate с screen: "codex_cache_watch"

Маршрут: Screen.kt:138 ("codex_cache_watch", "codex_settings", "codex_resets" -> Screen.CodexCacheWatch) → MainRouter.kt:314.

Шапка экрана — терминальная codex@cache-watch :~$. В meta видно текущее состояние сторожа: режим, порог в процентах, число промахов, «subagents included», а суффикс — «off» либо статистика сбросов.

3. Экран: элементы и действия

СекцияЭлементЧто делает
// watchWatchToggleCardВкл/выкл сторожа целиком
// thresholdsLow cache thresholdПорог «промаха»: PERCENT_OPTIONS = 30/40/50 %
Consecutive missesСколько промахов подряд считать залипанием: ANOMALY_OPTIONS = 2/3/5
Minimum inputМинимальный размер запроса: MIN_INPUT_OPTIONS = 10k/20k/30k/50k токенов
// reset modeReset modeManual или Auto
Пояснение«Паузы свыше 30 мин, компакция и смена треда/модели не считаются»
// summaryResetsSummaryCardВсего / вручную / автоматически, время и причина последнего
// by modelModelRowsРазбивка сбросов по моделям
// last 14 daysDayRowsСбросы по дням за RESET_DAYS_SHOWN = 14
// eventsEventRowСписок событий с пагинацией «Load older»
Меню шапкиClear reset historyСброс счётчиков и истории на backend (с подтверждением)

Пустое состояние (state.isEmpty) показывается, пока не было ни одного сброса.

4. Возможности подробно

Алгоритм оценки

Ядро — CacheWatch.note(threadId, model, usage, nowMs, …). Логика:

  1. Нет lastBreakdown → вердикт None (не судим).
  2. Смена модели → сбрасываем базу, вердикт None.
  3. Повтор сэмпла (те же input/cached) → None, обновляем время.
  4. Пауза свыше CACHE_WATCH_PAUSE_MS = 30 мин → база сбрасывается (кэш мог честно остыть).
  5. Мелкий запрос (input < minInputTokens) → база сбрасывается (доля кэша шумная).
  6. Контекст переписан (новый input < 60% прошлого) → база сбрасывается (компакция/обрезка).
  7. Иначе считаем percent = cached * 100 / input. Если percent < lowPercent — это промах, инкремент счётчика аномалий; при достижении anomalyLimit вердикт Stuck. Иначе счётчик сбрасывается и вердикт Healthy.

Состояние ведётся на каждый тред, потому что родитель и сабагенты шлют сэмплы вперемешку: у каждого свой лейн на бэкенде (conversationKey), и общая база сбрасывала бы вердикт при каждом переключении. Карта состояний — LRU на CACHE_WATCH_THREADS_MAX = 32 треда.

Где работает сторож

Сэмпл судится только тогда, когда модель входит в cacheWatchModels — множество id моделей провайдеров с флагом promptCache из GET /api/v1/providers. Флаг ставит бэкенд (CacheLaneProvider): шлюз opencode.ai напрямую и встроенный zen-прокси (127.0.0.1:8788) — у обоих роутер держит лейн разговора x-opencode-session и умеет его ротировать. Прямые API и удалённые серверы лейна не имеют: там сторож молчит, пункт «Reset cache session» в меню треда скрыт, а на экране сторожа (секция // watch) стоит строка о границах (codex_cache_watch_scope).

Пока список провайдеров не прочитан (cacheWatchModels пуст), сторож тоже молчит — ложный баннер хуже пропуска сэмпла.

Zen-тред идёт через роутер (Codex → y83_router → 127.0.0.1:8788), поэтому лейн zen:<threadId> живёт в общем сторе (~/.y83-ws/router-sessions.json) и обычная ротация снимает его вместе с лейном opencode-go того же разговора. Прокси пробрасывает входящий x-opencode-session на апстрим opencode.ai (с 17.10.2026 — opencode.ai/inference/v1), а без заголовка (прямой вызов) заводит свой локальный id на 30 минут.

Форма id — часть контракта. Free-tier гейт Zen пропускает запрос только с x-opencode-session вида ses_ + 9 строчных hex + маркер ffe + 14 [0-9A-Za-z] (ровно 30 символов; у msg_ маркер 001) и на чужую форму отвечает 403 FreeTierError на весь запрос. Поэтому и лейны роутера, и локальные сессии прокси выдаёт один генератор backend/internal/sessionid, а прокси пробрасывает входящий id только если он этой формы (иначе подставляет локальный). Старые zen-лейны другой формы пересоздаются при загрузке стора; лейны opencode-go не трогаются.

Режимы и кулдаун

КонстантаЗначениеСмысл
CACHE_WATCH_SNOOZE_MS1 чассколько «Продолжать» прячет баннер
CACHE_WATCH_PAUSE_MS30 минпосле какой паузы первый запрос не судим
CACHE_WATCH_ANOMALY_LIMIT3 (дефолт)промахов подряд до «залипа»
CACHE_WATCH_AUTO_COOLDOWN_MS10 минне ротировать один тред чаще
CACHE_WATCH_THREADS_MAX32тредов в памяти сторожа

В авто-режиме перед ротацией проверяется canAutoRotate(threadId, nowMs) — с прошлой ротации должно пройти не меньше кулдауна; после ротации состояние треда сбрасывается (resetThread), а база всего сторожа — только при выключении (reset()).

Ручная ротация

Репозиторий SessionRotationRepository.rotate(conv, reason, model, pct) вызывает backend и пишет причину в историю. Ручные и авто-сбросы считаются раздельно в manual / auto, поэтому видно, работал ли автомат.

Хранение истории

История — на backend (/api/v1/router/session/rotations), она переживает перезапуск приложения. Экран грузит по RESET_EVENTS_PAGE = 50 событий за раз и догружает старые по курсору before = nextCursor, склеивая по at/conv (чтобы не было дублей). «Clear reset history» обнуляет только счётчики и историю — сами кэш-сессии не трогаются.

5. Данные: БД, файлы, настройки

ЧтоГде лежитКак меняетсяКто читает
Настройки сторожаprefs приложения, ключи codex_cache_*CodexSettingsStore.saveCodexWatchdog, экран
История ротацийbackend (REST)Ротации и «Clear history»CodexSessionResetsViewModel

Ключи: codex_cache_enabled (дефолт true), codex_cache_mode (manual/auto, дефолт manual), codex_cache_anomaly_limit (3), codex_cache_low_percent (50), codex_cache_min_input (30 000).

Значения из «кривого» хранилища чинятся функцией sanitized(): неизвестные числа заменяются дефолтом, поэтому битые prefs не ломают сторож.

6. Backend: REST/роуты

Метод и путьНазначениеОбработчик
POST /api/v1/router/session/rotateротировать кэш-сессию тредаcodexRouter.RotateSession
GET /api/v1/router/session/rotationsстатистика и события ротацийcodexRouter.Rotations
DELETE /api/v1/router/session/rotationsочистить историюcodexRouter.ClearRotations

Роуты: backend/internal/api/v1/routes.go:199–201.

7. Мост и MCP

ЧтоКакПримечание
Открыть экранapp_navigate с screen: "codex_cache_watch"BridgeScreenCatalog
Диагностика кэшаcache_dump, dump_item, dump_timelineСкилл y83-bridge

Отдельного MCP-тула для управления сторожем нет — настройки живут в prefs приложения, а состояние кэша агент смотрит через codex_state.

8. Настройки, конфиг, квоты и лимиты

ПараметрЗначениеГде задаётся
Порог «промаха»30 / 40 / 50 %PERCENT_OPTIONS
Промахов подряд2 / 3 / 5ANOMALY_OPTIONS
Минимум запроса10k / 20k / 30k / 50kMIN_INPUT_OPTIONS
Тредов в памяти32 (LRU)CACHE_WATCH_THREADS_MAX
Событий на странице50RESET_EVENTS_PAGE
Дней в сводке14RESET_DAYS_SHOWN
Снос баннера1 часCACHE_WATCH_SNOOZE_MS
Кулдаун авто-ротации10 минCACHE_WATCH_AUTO_COOLDOWN_MS

9. Ловушки и ограничения

  • Настройки и история живут в разных местах. Пороги — в prefs телефона, счётчики и события — на backend. При отладке «почему не сработало» смотреть нужно оба источника: настройка может быть выключена, а история — пуста.
  • Кулдаун 10 минут на тред. В авто-режиме тред, который только что был ротирован, следующий раз не проверяется — даже если кэш снова залип.
  • Первый сэмпл, смена модели, пауза > 30 мин, мелкий запрос и перезапись контекста всегда дают «не судим». Это осознанные исключения: без них сторож ловил бы ложные срабатывания.
  • Порог считается от input, а не от общего контекста. При очень большом свежем контексте процент естественно ниже — отсюда минимальный размер запроса.
  • Сабагенты включены (subagents included), но у каждого свой conversationKey — без этого вердикт сбрасывался бы при каждом переключении между родителем и сабагентом.
  • «Clear reset history» не трогает сами сессии — только статистику. Сброс кэша — отдельное действие (ротация).
  • Кэш 0% на zen больше не аномалия данных. До 12.10.2026 zen-прокси терял prompt_tokens_details при конвертации usage, и приложение видело кэш 0% на всех zen-моделях — отсюда ложные баннеры и 52 пустых авто-ротации zen/space-bunny-free. Теперь детали переносятся, а провайдеры без лейна вообще не судятся; одноразовая миграция убрала старые события lanes == 0 из router-rotations.json (новые такие события остаются: после починки лейнов lanes == 0 снова означает дефект).
  • Модель может не сообщать кэш и со свежим лейном (тогда сторож ротирует её каждые 10 минут). Авто-«слепоты» у сторожа нет осознанно: лечится тумблером сторожа на экране или режимом Manual, а тренд виден в разбивке // by model.
  • LRU на 32 треда — давно неактивные треды вытесняются из памяти сторожа и при следующем появлении считаются «первым сэмплом».

10. Источники правды

  • app/src/main/java/com/y83/ws/ui/codex/CodexCacheWatchScreen.kt — экран.
  • app/src/main/java/com/y83/ws/ui/codex/CodexCacheWatch.kt — алгоритм сторожа и константы.
  • app/src/main/java/com/y83/ws/ui/codex/CodexSessionResetsViewModel.kt — состояние, пагинация, очистка.
  • app/src/main/java/com/y83/ws/data/codex/CodexSettings.kt — CodexCacheSettings, CodexSettingsStore.
  • app/src/main/java/com/y83/ws/ui/codex/CodexWatchdog.kt — где сторож живёт в рантайме чата.
  • app/src/main/java/com/y83/ws/ui/codex/CodexChatHelpers.kt — CACHE_LOW_PERCENT = 50, CACHE_JUDGE_MIN_TOKENS = 30 000.
  • app/src/main/java/com/y83/ws/data/repo/SessionRotationRepository.kt — клиент ротаций.
  • backend/internal/api/v1/routes.go:199–201 — роуты роутера.
  • backend/internal/providers/sessions.go — лейны разговоров, CacheLaneProvider, rotateLane.
  • backend/internal/providers/rotations.go — учёт ротаций и миграция lanes == 0.
  • backend/internal/handlers/zen/zen_upstream.go (+ zen_responses.go) — проброс x-opencode-session и конверсия usage.

Источник: docs/project/hub/17-codex_cache_watch.md · Codex · общий модуль чата Codex. Страница собирается из документации проекта при сборке сайта.