Протокол парного replay: стоят ли rag-first подсказки токенов исследования
Принимает критерий AC 8 задачи v14b-rag-first-nudges (отложен 2026-05-03): «прогнать сессию исследования до и после подсказок и сравнить расход». Задача v14b-rag-nudge-replay-benchmark фиксирует здесь прибор, корпус, условия, формулу и место улик — до прогонов, чтобы число потом было о чём-то.
1. Прибор и его границы (проверено на реальной записи, смена #249)
Источник посессионных чисел — session_usage_metrics, которую заполняет scripts/hooks/session_metrics.parse_transcript через окно смены make_session_resolver() (починено в session-rollup-window-attribution). Проверка на транскрипте 89b9d743 этой машины: один файл разложился на семь окон — #243 180 730 / #244 135 941 / #245 176 220 / #246 119 638 / #247 50 592 / #248 120 869 / #249 169 024 tokens_total; сумма 953 014 из 954 210, вне окон — 1 196. Раньше все семь строк были бы одним числом.
Две границы, обе решающие для этого замера:
tokens_total= некэшированный вход + выход. В тех же окнах поляusageтранскрипта дают для #248cache_read_input_tokens129 334 844 иcache_creation_input_tokens183 391 противtokens_total120 869. Рост контекста — то, что подсказки должны менять, — этот счётчик не видит. Поэтому основная метрика берётся из полейusageтранскрипта по тому же окну, а не изsession_usage_metrics.- Смесь инструментов слепа к Bash. В окнах #248/#249 ноль вызовов
Read/Grep/Glob/search_code— агент исследовал черезgrep/sed/catвBash. Метрика смеси обязана классифицировать команды Bash по первому слову, иначе «ноль Read» будет выдан за успех подсказок.
Отвергнуто и остаётся отвергнутым: token_metrics.jsonl и usage_events источника posttool — они штампуют посообщенческий usage на каждый вызов и пересчитывают кэш (решение #201).
2. Корпус запросов (фиксирован, только чтение)
Десять вопросов о ЭТОМ репозитории, каждый — отдельный ход одной сессии, ответ без правок файлов и без задач TAUSIK. Порядок фиксирован.
- Где и как
task doneчитает кэшverifyи что делает QG-2 при просроченном кэше? - Какие генераторы файлов правил вызывают
warn_output_mode_not_appliedи что общего у их сигнатур? - Как
memory_relevance.search_anyстроит FTS5-запрос и почему не черезmemory_search? - Что именно
verify_commit_ownership.pyсчитает «своим» файлом — перечислите три яруса с именами функций. - Где хранится ключ подписи квитанции verify и кто его читает при
task done? - Как
config_trustрешает, что глобальная настройка относится к чужому проекту? - Какие хуки установлены в
.claude/settings.jsonи какой скрипт стоит за каждым? - Как
knowledge export --redactedвыбирает, что маскировать, и что никогда не маскируется? - Где задан потолок
CAVEMAN_DIRECTIVE_MAX_CHARSи какие тесты его стерегут? - Как гейт
test_dedupeсчитает «структурно неразличимые» тесты и где его базовая линия?
Ответы не оцениваются на правильность внутри протокола (это замер расхода, не качества), но сохраняются рядом с уликами: сравнение без ответов даёт возможность «сэкономить», ответив хуже.
3. Условия
| условие | харнесс | как получить |
|---|---|---|
| A — подсказки есть | развёрнутый профиль как есть | python bootstrap/bootstrap.py --ide claude в рабочем дереве |
| B — подсказок нет | тот же коммит без мест впрыска | git worktree add ../tausik-B HEAD; в нём: (1) TOOL_ROUTING не включается в build_full_body (bootstrap/bootstrap_templates.py); (2) секции «Code search hierarchy» убраны из harness/skills/{start,task,debug,explore}/SKILL.md; (3) в scripts/hooks/session_start.py убраны строка RAG-summary с search_code и bullet reminders про RAG; (4) scripts/hooks/keyword_detector.py — на HEAD 396de834 уже не рекомендует search_code, снимать нечего; (5) в scripts/hooks/user_prompt_submit.py отключён SEARCH_RECOMMENDATION (сюда nudge переехал из keyword_detector); (6) в scripts/hooks/tool_output_truncation_nudge.py из строки-лекарства убрано упоминание search_code; затем bootstrap там же. Проверка чистоты — не по списку, а по транскрипту: ноль вхождений текстов подсказок (§7) |
Всё остальное одинаково: модель (model_id записывается смену в session start), окно контекста, индекс RAG (в B сервер codebase-rag остаётся подключён — снимаются подсказки, не инструмент), одна свежая сессия IDE на условие, одна смена TAUSIK на условие, открытая session start и закрытая session end до первого и после последнего вопроса.
Порядок: сначала B, потом A, в разные дни не обязательно, но между ними — ни одного коммита в основное дерево, иначе корпус отвечает на разный код.
4. Метрика
Для окна смены [started_at, ended_at) через make_session_resolver():
- основная:
Σ cache_creation_input_tokens + Σ tokens_outputпо сообщениямassistantокна — «сколько нового контекста и вывода породило исследование»; - вторичная:
Σ cache_read_input_tokens— стоимость перечитывания, функция числа ходов × размера контекста; - смесь: число вызовов и байты результатов по классам
search_code,Read,Grep/Glob,Bash:grep|rg|sed|cat|head|tail|find|ls(класс по первому слову команды послеcd … &&), остальное —other; - сверка:
session_usage_metrics.tokens_totalза то же окно должен совпасть сΣ input_tokens + Σ output_tokensтранскрипта — иначе окно выбрано неверно и сравнение недействительно.
Дельта: A − B по каждой строке, абсолютно и в процентах от B. Утверждение «подсказки экономят» допустимо только если основная метрика и байты результатов исследования у A меньше, чем у B, на одном корпусе, при совпавшей сверке; в любом другом исходе записывается «сравнение недействительно: <причина>» — и это тоже результат закрытия (AC-3).
5. Улики
docs/ru/research/_internal/rag-replay/<дата>/{A,B}/ — usage.json (суммы по формуле §4), mix.json, answers.md, session_id.txt, transcript.sha256 (сам транскрипт в репозиторий не кладётся: он содержит пути и вывод инструментов, граница публикации). Цифры дублируются в журнал задачи строкой AC-3 … с обоими абсолютными значениями и дельтой.
6. Что этот протокол не обещает
Никакой экономии до парного прогона (AC-4). Одна сессия — не улика: разброс tokens_total между сменами #243–#249 одной и той же работы — от 50 592 до 180 730 — больше любого правдоподобного эффекта подсказок, поэтому две сессии дают лишь один парный отсчёт на фиксированном корпусе, и вывод формулируется как «на этом корпусе, в этой паре», без обобщения. Стоимость: две сессии по десять вопросов, ориентировочно по 20–40 минут каждая, — решение владельца, когда их прогонять и прогонять ли.
7. Результат парного прогона 14 сентября 2026 (смены #261–#263)
Пара получена, сравнение действительно (сверка §4 сошлась в обоих условиях, один коммит 396de834, одна модель claude-opus-5[1m], один корпус), и утверждение «подсказки экономят» на этой паре не допускается: основная метрика и байты результатов исследования у A больше, чем у B.
| метрика | B (без подсказок) | A (с подсказками) | A − B | % от B |
|---|---|---|---|---|
| основная: Σ cache_creation + Σ output | 195 055 | 198 848 | +3 793 | +1,9 % |
| — Σ cache_creation_input_tokens | 163 546 | 172 114 | +8 568 | +5,2 % |
| — Σ output_tokens | 31 509 | 26 734 | −4 775 | −15,2 % |
| вторичная: Σ cache_read_input_tokens | 6 264 364 | 5 652 601 | −611 763 | −9,8 % |
| сообщений assistant (вызовов API) | 52 | 44 | −8 | −15,4 % |
| вызовов инструментов | 76 | 62 | −14 | −18,4 % |
— search_code | 0 | 0 | 0 | — |
— Read | 37 | 39 | +2 | +5,4 % |
— Grep/Glob | 37 | 21 | −16 | −43,2 % |
— Bash:search (grep, sed, cat…) | 0 | 0 | 0 | — |
| байты результатов исследования | 292 715 | 326 323 | +33 608 | +11,5 % |
— из них Read | 252 001 | 285 738 | +33 737 | +13,4 % |
| время, с | 516 | 462 | −54 | −10,5 % |
сверка: session_usage_metrics.tokens_total = Σ input + Σ output | 57 135 = 57 135 | 47 221 = 47 221 |
Главная строка — search_code: ноль вызовов в обоих условиях. В A агент получил все три текста подсказки (строку RAG-summary и bullet при SessionStart, один nudge UserPromptSubmit) и ни разу не выбрал инструмент, который они рекомендуют; в B инструмент был подключён и тоже не выбран. Подсказки не изменили выбор инструмента — они изменили только форму исследования (меньше Grep, больше и крупнее Read), и это обошлось дороже.
Что ещё измерено и что это значит:
- Разброс между прогонами одного условия больше эффекта. Первая попытка B (отброшена: один nudge прошёл) дала основную метрику 220 343 и 366 544 байт — на 13 % и 25 % больше чистого B при том же дереве и корпусе. Дельта A − B в 1,9 % внутри этого разброса; вывод формулируется только как «на этом корпусе, в этой паре подсказки не сэкономили», без обобщения (§6).
- Счётчик
session_metrics.parse_transcriptзавышаетtokens_totalпримерно в 1,8 раза: он суммируетusageкаждой записи JSONL, а Claude Code пишет по записи на каждый блок содержимого одного сообщения с одним и тем жеusage(52 сообщения B лежат в 321 записи). Сведённая поmessage.idсумма input + output для B — 31 613 против 57 135 у счётчика. Сверка §4 намеренно считается «как счётчик», чтобы равенство означало верно выбранное окно; числа таблицы сведены. Дефект заведён отдельной задачей. - Подсказки физически не доходили. SessionStart-хук на этой машине идёт 5,9 с при таймауте 6 с и в пробном прогоне был отменён — весь автовставляемый контекст, включая подсказку, не попал в сессию. Причина —
tausik status5,3 с, из них 5,04 с однаEXISTSпоtasks.defect_ofбез индекса (status-takes-five-seconds-on-a-missing-defect-of-index). На время прогонов таймауты SessionStart/Stop подняты в обоих профилях, после — восстановлены побайтово. - Мест впрыска было шесть, а не четыре (§3 исправлен). Полноту B доказывает не список, а транскрипт: ноль вхождений текстов подсказок.
Улики: docs/ru/research/_internal/rag-replay/2026-09-14/{A,B,B1-contaminated}/ (usage.json, mix.json, answers.md, session_id.txt, transcript.sha256, turns.json), там же драйвер, метрика и сравнение как скрипты и README.md с отклонениями. Обе сессии — безголовый Claude Code 2.1.270 с одной сессией на условие и десятью отдельными ходами (stream-json), а не две ручные сессии IDE.