Раздел: 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. Экран: элементы и действия
| Секция | Элемент | Что делает |
|---|---|---|
// watch | WatchToggleCard | Вкл/выкл сторожа целиком |
// thresholds | Low cache threshold | Порог «промаха»: PERCENT_OPTIONS = 30/40/50 % |
| Consecutive misses | Сколько промахов подряд считать залипанием: ANOMALY_OPTIONS = 2/3/5 | |
| Minimum input | Минимальный размер запроса: MIN_INPUT_OPTIONS = 10k/20k/30k/50k токенов | |
// reset mode | Reset mode | Manual или Auto |
| Пояснение | «Паузы свыше 30 мин, компакция и смена треда/модели не считаются» | |
// summary | ResetsSummaryCard | Всего / вручную / автоматически, время и причина последнего |
// by model | ModelRows | Разбивка сбросов по моделям |
// last 14 days | DayRows | Сбросы по дням за RESET_DAYS_SHOWN = 14 |
// events | EventRow | Список событий с пагинацией «Load older» |
| Меню шапки | Clear reset history | Сброс счётчиков и истории на backend (с подтверждением) |
Пустое состояние (state.isEmpty) показывается, пока не было ни одного сброса.
4. Возможности подробно
Алгоритм оценки
Ядро — CacheWatch.note(threadId, model, usage, nowMs, …). Логика:
- Нет
lastBreakdown→ вердиктNone(не судим). - Смена модели → сбрасываем базу, вердикт
None. - Повтор сэмпла (те же
input/cached) →None, обновляем время. - Пауза свыше
CACHE_WATCH_PAUSE_MS = 30 мин→ база сбрасывается (кэш мог честно остыть). - Мелкий запрос (
input < minInputTokens) → база сбрасывается (доля кэша шумная). - Контекст переписан (новый
input < 60%прошлого) → база сбрасывается (компакция/обрезка). - Иначе считаем
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_MS | 1 час | сколько «Продолжать» прячет баннер |
CACHE_WATCH_PAUSE_MS | 30 мин | после какой паузы первый запрос не судим |
CACHE_WATCH_ANOMALY_LIMIT | 3 (дефолт) | промахов подряд до «залипа» |
CACHE_WATCH_AUTO_COOLDOWN_MS | 10 мин | не ротировать один тред чаще |
CACHE_WATCH_THREADS_MAX | 32 | тредов в памяти сторожа |
В авто-режиме перед ротацией проверяется 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.save | CodexWatchdog, экран |
| История ротаций | 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 / 5 | ANOMALY_OPTIONS |
| Минимум запроса | 10k / 20k / 30k / 50k | MIN_INPUT_OPTIONS |
| Тредов в памяти | 32 (LRU) | CACHE_WATCH_THREADS_MAX |
| Событий на странице | 50 | RESET_EVENTS_PAGE |
| Дней в сводке | 14 | RESET_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.