8125b7e700
Остаточный кирпич (#490/#510): при незаданном chatContextWindow база = плоский дефолт 100k, а реальное окно модели маленькое (<50k). Фиксированный одинарный cut 0.5×100k=50k всё равно превышал реальное окно → провайдер снова 400, строка переставлялась replayOverflow, но булев priorOverflowed уже был true → второго ужатия не происходило. Чат навсегда застревал на 50k и не восстанавливался. Фикс (без парсинга тел 400): - Сигнал из булева переведён в счётчик `metadata.replayOverflowCount` = число ПОДРЯД идущих overflow-ходов: инкремент (prior+1) на каждом overflow, сброс в 0 на любом чистом финализе (чистая строка не пишет поле → читается как 0). BACK-COMPAT: старая строка с булевым `replayOverflow:true` читается как k=1. - resolveEffectiveReplayThreshold(threshold, k) = max(floor(threshold·0.5**k), min(REPLAY_MIN_FLOOR_TOKENS, threshold)). k=0 → база; k=1 → 0.5×; k=2 → 0.25×; большой k → упирается в пол 8k (сходимость). null-база (trimming OFF) не трогается; пол никогда не поднимает легитимно малый настроенный бюджет выше него самого. - Пол REPLAY_MIN_FLOOR_TOKENS=8k: ниже него чат не несёт осмысленный недавний контекст, и даже малое реальное окно его вмещает; keep-recent-turns сверху. Тесты: таблица эскалации (k=0/1/2/большой/null), регрессия остаточного кирпича (база 100k, окно ~40k → сходится ниже 40k, чего фиксированный 0.5× никогда не мог), жизненный цикл счётчика на реальном pg (инкремент/сброс/back-compat через jsonb), мутация **k→**1 краснит convergence-тест. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>