# Экономичность CM в агентской сессии **Итог: в трёх сериях по 36 сессий (2–4) новый CM решил 36 из 36 задач против 35 из 36 без CM и расходовал на 45–66% меньше токенов, на 30–47% дешевле и на 18–37% быстрее агента без CM. К CM 0.8.1 суммарно по трём сериям −20% токенов; в серии 4 из-за одной неоднозначной задачи (wallet-delta) новый CM оказался на 14% дороже 0.8.1.** Один проект, шесть задач — см. ограничения. Документ описывает, как CM (Code Memory) снижает расход агента, какие изменения внесены после 0.8.1 и как они проверены живыми сессиями DeepSeek Harness с GPT-6 Luna на Geo Dash. Все прогоны, включая неудачные, сохранены в [evaluation/efficiency](../evaluation/efficiency). ## Откуда берётся расход агента Агент работает циклом: вызов модели → инструмент → вызов модели. При каждом вызове модели заново отправляется вся переписка: системный промпт, схемы инструментов, задача и всё, что уже прочитано. Поэтому **токены сессии ≈ число вызовов модели × размер переписки.** Каждый лишний `read`, `grep` или `glob` стоит целого вызова. Прочитанный файл к тому же остаётся в переписке и повторяется во всех следующих вызовах. CM экономит, когда модель с первого шага получает нужный код и правит без поиска и перечитывания. Минимум для правки с проверкой — три вызова: правка, `project_checks`, итог. ## Как CM формирует контекст Перед первым шагом агента plugin берёт текст задачи и строит один блок контекста, не больше 6 000 байт исходника: 1. **Один названный небольшой самостоятельный файл** (до 6 000 байт, без import) — отдаётся целиком прямо с диска, индекс не нужен. 2. **Названные функции** (`` `creditAdReward` ``, путь к файлу) — точные якоря в графе компилятора и ограниченное замыкание зависимостей. 3. **Поиск неизвестного места** — лексический IDF, локальные MiniLM-векторы и поддержка соседей в графе. Код проверяется по версии файла и регистрируется в Harness как прочитанный, поэтому правка возможна без отдельного `read`. Подробно маршруты описаны в [графе и бюджете контекста](HOW-IT-WORKS.md#2-выбор-маршрута) и [скорости 0.7.0](HISTORY.md#6-быстрые-маршруты-070). ## Что изменено ### Этап 1 — поиск и выдача (коммит `1a6b157`) | Было в 0.8.1 | Стало | Зачем | |---|---|---| | Запросом служил весь текст сообщения, включая общие инструкции агента («работай только в этом workspace», «закончи итогом») | `focusQuery` ([focus.ts](../src/code-memory/focus.ts)) убирает предложения, в которых ≥80% слов — общая лексика процесса; предложения с путями, `` `именами` `` и camelCase остаются всегда | В пилоте инструкции увели поиск motion-envelope в 9 посторонних файлов `geoOriginal*`; без них первым находится `geoCollisionCandidates.js` | | Короткие файлы отдавались фрагментами | Файл до 3 000 байт целиком, с пометкой `(whole file)`, если позволяет бюджет | Фрагмент короткого файла провоцирует «проверочный» `read` | | Блок начинался «partial» и всегда заканчивался «read if necessary», даже при 0 пропусках | Заголовок говорит, что код актуален и уже засчитан как прочитанный; предложение дочитать — только если что-то действительно пропущено | В quota-receipts модель видела обе строки с ошибкой, но перечитала оба файла | ### Этап 2 — меньше повторов (коммит `7d1d605`) | Изменение | Как устроено | |---|---| | **Повторный `read` не дублирует файл** | Хук Harness `tools/post-execute` (официально позволяет заменить результат инструмента). Если модель читает файл, который CM уже показал целиком, и его байты не изменились (сверка SHA-256), вместо копии возвращается короткая ссылка на контекст выше. Только первый повтор; второй `read` и любой изменённый файл отдаются как обычно. Подмены пишутся в диагностику событием `read_deduplicated`. Переписывать сообщения в `llm/stream` нельзя: Harness замораживает запрос, собранный циклом, чтобы сессия восстанавливалась из лога | | **Места вызова в контексте** | Строка `Call sites of creditAdReward: game/src/platform/rewarded-flow.js:34.` строится из связей `calls` индекса, включая вызовы на уровне модуля (их граф функций раньше терял). Если вызовов нет, так и написано. Модель чаще всего шла в `grep` именно за этим | | **Конкретнее инструкция** | В системном промпте: файлы `(whole file)` полные, не перечитывать; места вызова уже даны; правку в 1–2 местах делать сразу без отдельного плана | `todo_write` не трогался: это настройка Harness, она одинакова для всех вариантов и улучшила бы и вариант без CM. ## Методика - **Проект:** Geo Dash, тот же commit `1700732`, что в серии 0.7.0; corpus — 224 отслеживаемых JS-файла, 7 503 строк векторов. - **Задачи:** шесть спецификаций [0.7.0](../evaluation/speed-tokens/cases.json): именованные исправления, цепочка зависимостей, два поиска неизвестного места, контракт двух модулей и CPU-оптимизация. В каждую рабочую копию внесена ошибка; исходное поведение проходит проверку, испорченное — нет. - **Варианты:** `none` — без CM; `cm_base` — замороженная сборка 0.8.1; `cm_next` — текущая сборка. Одинаковые модель (`gpt-6-luna`, reasoning low), инструменты, лимиты (12 вызовов, 240 с) и подготовленные индексы. - **Порядок:** вариантов внутри задачи чередуется по кругу и в обратную сторону; идентификаторы запусков непрозрачные. - **Качество:** независимый grader вне Harness с другим seed, проверка экспортов, отсутствие лишних изменений и новых файлов. Для оптимизации — реальное ускорение функции не меньше 1,25×. - **Учёт:** токены и стоимость берутся из usage провайдера по каждому вызову; цена рассчитана по тарифам Luna с кешем (это оценка, не счёт). Время — от запуска Harness до закрытия, без копирования и итоговой проверки. - **Заморозка:** перед платным прогоном протокол фиксирует хеши кода CM, runner, grader, входов и Harness. Изменение кода во время серии прерывает её. Runner: [efficiency_ab.ts](../scripts/efficiency_ab.ts), запуск — [run_efficiency_ab.ts](../scripts/run_efficiency_ab.ts), сводка — [summarize_efficiency.ts](../scripts/summarize_efficiency.ts). ## Результаты ### Пилот: none против 0.8.1, по одному повтору 12 сессий, обе группы 12/12. CM 0.8.1: 133 279 токенов против 205 044 (−35,0%). Выявлены три проблемы, исправленные на этапе 1. [Данные](../evaluation/efficiency/pilot-results.json), [сводка](../evaluation/efficiency/pilot-summary.json). ### Прерванный запуск Первая попытка серии 1 остановилась на третьей сессии до платного вызова: счётчик токенов искал старый заголовок CM и решил, что CM не включился. Защита сработала как задумано, счётчик исправлен, серия перезапущена в новой папке. [Сохранено](../evaluation/efficiency/aborted-launch-results.json). ### Серия 1: этап 1, 36 сессий | | Без CM | CM 0.8.1 | CM этап 1 | |---|---:|---:|---:| | Правильных правок | **12/12** | 11/12 | 11/12 | | Токены | 404 785 | 280 300 | **242 689** | | к без CM | — | −30,8% | **−40,0%** | | Расчётная цена | $0.01631 | $0.01292 | **$0.01194** | | к без CM | — | −20,8% | **−26,8%** | | Вызовы модели | 80 | 66 | 62 | | read / grep / glob | 36 | 18 | **7** | | Время Harness, сумма | 232,2 с | 249,7 с | 237,2 с | К 0.8.1 этап 1 дал −13,4% токенов, −7,6% цены и −5,0% времени при том же качестве. Меньше токенов, чем без CM, в 10 из 12 парных задач, дешевле — в 11 из 12. Чего не достигнуто: время не лучше, чем без CM (+2,2%), и одна неудача против нуля без CM. Обе неудачи CM (по одной у 0.8.1 и у этапа 1) — wallet-delta: модель неверно поняла смысл счётчика `videoRewards`, хотя CM дал правильный файл целиком. Это ошибка рассуждения, а не поиска. По задачам (токены за два повтора): этап 1 сильно выиграл wallet-delta (43К против 96К у 0.8.1) и quota-receipts (62К против 83К). Проиграл motion-envelope (50К против 37К) и statistics-linear (53К против 27К): по логам модель там завела список задач и перечитала файлы, уже отданные целиком. Это привело к этапу 2. [Данные](../evaluation/efficiency/series1-results.json), [сводка](../evaluation/efficiency/series1-summary.json). ### Серия 2: этап 2, 36 сессий | | Без CM | CM 0.8.1 | CM этап 2 | |---|---:|---:|---:| | Правильных правок | 12/12 | 12/12 | **12/12** | | Токены | 412 705 | 222 441 | **142 349** | | к без CM | — | −46,1% | **−65,5%** | | Расчётная цена | $0.01638 | $0.01169 | **$0.00875** | | к без CM | — | −28,7% | **−46,6%** | | Вызовы модели | 86 | 57 | **45** | | read / grep / glob | 34 | 12 | **0** | | Время Harness, сумма | 326,1 с | 265,3 с | **204,7 с** | | к без CM | — | −18,7% | **−37,2%** | К 0.8.1 в той же серии: −36,0% токенов, −25,2% цены, −22,8% времени. Против варианта без CM новый CM расходует меньше токенов во всех 12 парах, дешевле во всех 12, быстрее в 11 из 12. Качество не потеряно: 12/12 во всех трёх вариантах. Что сработало. Ни одного `read`, `grep` или `glob` в 12 сессиях с новым CM: модель правила сразу по выданному контексту. Подмена повторного `read` не понадобилась ни разу (0 событий `read_deduplicated`) — перечитывания прекратились раньше, за счёт мест вызова и инструкции. Подмена остаётся страховкой. Оговорки, без которых вывод неполон: - Разброс модели между сериями большой: время модели без CM — 209,8 с в серии 1 и 303,4 с в серии 2. Часть выигрыша по времени к варианту без CM — этот разброс. Сравнение с 0.8.1 внутри одной серии (−22,8%) от него не зависит. - statistics-linear: по токенам новый CM близок к 0.8.1 (34,6К против 32,4К), по времени медленнее (59,4 с против 34,9 с) — модель дольше писала оптимизацию; задача решена в обоих повторах. - Накладной расход CM в сессии (не время модели) — 42,7 с на 12 сессий против 22,7 с без CM; основная часть — загрузка индекса на 92 МБ. Её ускорение (коммит `005ac31`, ~1,6 с → ~0,85 с на загрузку) сделано после серии 2 и в ней не измерено. [Данные](../evaluation/efficiency/series2-results.json), [сводка](../evaluation/efficiency/series2-summary.json). ### Ускорение загрузки индекса (коммит `005ac31`) Разбор времени серии 1 показал: время модели с CM меньше, чем без CM (192,4 с против 209,8 с), а отставание создаёт старт CM — загрузка индекса Geo Dash (JSON 92 МБ) в каждой сессии: разбор 0,8 с, строгая проверка UTF-8 0,48 с, проверка схемы и связей 0,35 с, сам поиск ~0,2 с. Изменено: - строгая проверка UTF-8 через `TextDecoder` с `fatal: true` и сохранением BOM вместо перекодирования 92 МБ туда и обратно; отклоняются те же последовательности; - после полной проверки рядом с индексом пишется отметка `index.json.verified.json`: SHA-256 проверенных байтов и хеш проверяющего кода и схемы. Следующие загрузки тех же байтов пропускают повторную проверку; любое изменение индекса или проверяющего кода — полная проверка заново. Если каталог только для чтения, проверка просто идёт каждый раз. Загрузка индекса: ~1,6 с → ~0,85 с. ### Серия 3: итоговая сборка, 36 сессий Та же серия после ускорения загрузки индекса (коммит `005ac31`). Отметка о проверке индекса создана на этапе подготовки, как при постройке индекса у пользователя. | | Без CM | CM 0.8.1 | Итоговый CM | |---|---:|---:|---:| | Правильных правок | 12/12 | 11/12 | **12/12** | | Токены | 393 954 | 231 228 | **153 937** | | к без CM | — | −41,3% | **−60,9%** | | Расчётная цена | $0.01631 | $0.01062 | **$0.00905** | | к без CM | — | −34,9% | **−44,5%** | | Вызовы модели | 78 | 57 | **47** | | read / grep / glob | 37 | 14 | **0** | | Время Harness, сумма | 224,9 с | 177,9 с | **157,8 с** | | к без CM | — | −20,9% | **−29,8%** | | Медиана сессии | 15,3 с | 15,6 с | **11,9 с** | К 0.8.1: −33,4% токенов, −14,8% цены, −11,3% времени. Против варианта без CM: меньше токенов в 10 парах из 12, дешевле в 11, быстрее в 9. Неудача 0.8.1 — camera-time, второй повтор. Серия 2 повторилась: при тех же задачах и качестве выигрыш к варианту без CM снова больше половины токенов и около трети времени. Слабое место то же — statistics-linear: итоговый CM по токенам близок к варианту без CM (49,0К против 46,5К) и дороже 0.8.1 (21,2К); модель дольше пишет оптимизацию, задача решена во всех повторах. Время модели с CM — 117,3 с против 201,7 с без CM. Накладной расход CM в сессии — 40,5 с на 12 сессий против 23,2 с без CM; из них выбор контекста 11,8 с (было 15,0 с у 0.8.1 в той же серии). Остаток — запуск плагина, ONNX Runtime и LanceDB; это следующий резерв скорости. [Данные](../evaluation/efficiency/series3-results.json), [сводка](../evaluation/efficiency/series3-summary.json). ### Задача statistics-linear и инструкция «сначала продумай» (коммит `1074f09`) Во всех трёх сериях новый CM на statistics-linear чаще ошибался с первой попытки, чем 0.8.1, при одинаковом контексте (весь файл). Три прицельные проверки по 5 повторов: | Проверка | Вариант A | Вариант B | |---|---|---| | 1. Без фразы «правь сразу» против текущей | 134,2K токенов, повторные попытки 4/5 | 97,5K, 5/5 — фраза не виновата | | 2. 0.8.1 против текущей | 87,5K, 3/5; первый вызов ~535 выходных токенов | 105,8K, 5/5; первый вызов ~270 | | 3. «Сначала продумай поведение» против текущей | **71,6K, 1/5** | 104,9K, 4/5 | Формулировка нового CM («код уже прочитан, файл целиком, ничего не пропущено») делала модель увереннее: она вдвое меньше рассуждала перед правкой и чаще ошибалась в порядке, ничьих и энтропии. В инструкцию добавлено: перед прямой правкой продумать поведение, которое нужно сохранить (граничные случаи, порядок, ничьи, форма результата). [Проверка 1](../evaluation/efficiency/stats-probe1-no-edit-directly.json), [2](../evaluation/efficiency/stats-probe2-old-vs-new.json), [3](../evaluation/efficiency/stats-probe3-reason-first.json). В том же коммите TypeScript загружается лениво ([ts.ts](../src/code-memory/ts.ts)): поиск по готовому индексу его не использует. Импорт плагина — 0,66 с → 0,18 с. ### Серия 4: с инструкцией и ленивым TypeScript, 36 сессий | | Без CM | CM 0.8.1 | Новый CM | |---|---:|---:|---:| | Правильных правок | 11/12 | 12/12 | **12/12** | | Токены | 384 535 | **186 277** | 212 812 | | к без CM | — | −51,6% | −44,7% | | Расчётная цена | $0.01543 | **$0.00968** | $0.01080 | | Время Harness, сумма | 173,3 с | 139,5 с | 141,5 с | | к без CM | — | −19,5% | −18,3% | | Медиана сессии | 12,6 с | 11,2 с | **9,5 с** | | Быстрее варианта без CM, пар | — | 7/12 | **11/12** | К варианту без CM выигрыш сохранился. К 0.8.1 новый CM в этой серии **дороже на 14,2%** по токенам при равном времени. Разница почти целиком — wallet-delta: 94,7К против 48,3К. В одном повторе модель с новым CM завела список задач, искала и читала соседние файлы и сделала три попытки (79К); в другом решила задачу за 15,6К. У 0.8.1 в этой же серии та же задача заняла 40,5К и 7,8К. Без wallet-delta новый CM на 14% дешевле 0.8.1 (118,1К против 138,0К). statistics-linear в этой серии: 29,2К против 27,7К у 0.8.1 (в серии 3 было 49,0К против 21,2К). Не исключено, что «сначала продумай» иногда подталкивает модель исследовать окрестности на неоднозначной задаче; двух повторов недостаточно, чтобы это отличить от разброса. ### Сводно по сериям 2–4 | | Без CM | CM 0.8.1 | Новый CM | |---|---:|---:|---:| | Правильных правок | 35/36 | 35/36 | **36/36** | | Токены | 1 191 194 | 639 946 | **509 098** | Сборка нового CM в сериях отличалась (этап 2 → загрузка индекса → инструкция), поэтому суммирование — ориентир, а не отдельный эксперимент. [Данные серии 4](../evaluation/efficiency/series4-results.json), [сводка](../evaluation/efficiency/series4-summary.json). ## Кеш провайдера Каждый вызов модели заново отправляет всю переписку; провайдер кеширует уже виденное начало запроса. У Luna чтение из кеша стоит $0.01 за 1M, запись — $0.125, в 12,5 раза дороже. ### Влияет ли CM на кеш | Серия 2 | Без CM | CM 0.8.1 | Новый CM | |---|---:|---:|---:| | Входные токены | 403 550 | 215 204 | 136 604 | | Доля из кеша | 83,2% | 76,1% | 71,3% | | Записано в кеш | 67 365 | 51 325 | 39 110 | | Цена: чтение / запись / выход | $0.00336 / $0.00842 / $0.00458 | $0.00164 / $0.00642 / $0.00362 | $0.00097 / $0.00489 / $0.00287 | Доля кеша с CM ниже, потому что меньше вызовов, повторно читающих ту же переписку; в абсолютных числах CM снижает и чтение, и запись, и выход. Поэтому же цена падает слабее токенов (−46,6% против −65,5%): убраны в основном дешёвые повторные чтения, а запись — около половины цены во всех вариантах. CM не ломает кеш внутри сессии: контекст вставляется один раз в первый шаг, после системного промпта и схем инструментов, и со второго вызова читается из кеша; указатель вместо повторного `read` возвращается в момент выполнения инструмента, прошлые сообщения не переписываются. ### Почему новая сессия не использовала кеш В обеих сериях первый вызов каждой сессии читал из кеша 0 токенов во всех вариантах. Проверка ([данные](../evaluation/efficiency/cache-probe.json)) нашла две причины в Harness: 1. Адаптер OpenAI передаёт ID сессии как `prompt_cache_key`; запрос с новым ключом не попадает в кеш даже при одинаковом начале (0 из 1 200 против 1 115 из 1 200 с тем же ключом). 2. Последняя строка системного промпта — `Your working directory is …`, а системный промпт идёт раньше схем инструментов; при разной папке кеш не срабатывает. Без ключа (`cacheRetention: "none"` в провайдере Harness) провайдер направляет запросы по началу промпта: на настоящих сессиях Harness в одной папке проекта вторая сессия прочитала из кеша 2 690 из 2 693 токенов первого вызова и записала за всю сессию 148 токенов вместо 2 835. Внутри сессии кеш работает и без ключа. Изменено: установщик ([install.ts](../src/setup/install.ts)) и [start_harness.ps1](../scripts/start_harness.ps1) задают провайдеру `cacheRetention: "none"`. Это настройка Harness, а не CM: выигрывает любой агент в одной папке проекта. В A/B она не включалась — там у каждой сессии своя папка, и кеш между сессиями невозможен для всех вариантов одинаково. Без ключа много параллельных клиентов с одинаковым началом промпта делят одну маршрутизацию провайдера; для одного пользователя это не ограничение. ## Ограничения - Один проект, шесть задач, два повтора на вариант; разброс поведения модели сопоставим с эффектом на отдельных задачах. Это не статистическое доказательство и не обещание того же выигрыша на других проектах. - Индексы и векторы строились заранее и не входят во время сессии; первая постройка и полная пересборка — отдельная стоимость. - Подмена повторного `read` полагается на то, что контекст CM остаётся в переписке; при сжатии истории в длинной сессии указатель может ссылаться на вытесненный текст. Поэтому подменяется только первый повтор. - Места вызова берутся из связей компилятора по JS/TS; динамические вызовы (по строке, через события) не видны. ## Как повторить в облачной сессии Claude Code 1. Сетевой секрет окружения: тип Bearer, сайт `api.openai.com`, значение — ключ OpenAI. Ключ подставляет прокси, в контейнер он не попадает; скриптам достаточно любого непустого `CM_AB_API_KEY`. 2. Зависимости. Postinstall `onnxruntime-node` пытается скачать CUDA-библиотеки с nuget, прокси облака рвёт это соединение; для CPU они не нужны: ```bash ONNXRUNTIME_NODE_INSTALL=skip npm ci npm run verify ``` 3. Harness отдельно от проекта: ```bash mkdir -p ~/harness-rt && cd ~/harness-rt && npm init -y ONNXRUNTIME_NODE_INSTALL=skip npm i @deepseek-ai/dsh@0.2.0-rc.2 @deepseek-ai/dsh-base@0.2.0-rc.2 ``` 4. Geo Dash — checkout commit `1700732`; сборка CM для сравнения — копия `dist` нужной версии. 5. Подготовка (без вызовов модели), заморозка и прогон: ```bash C="--project ../geodash --out ../exp/run1 --harness ~/harness-rt/node_modules/@deepseek-ai/dsh/lib/bin.js \ --cases evaluation/speed-tokens/cases.json --model-cache .code-memory/models --reuse-cache empty-cache.json \ --base-plugin ../cm-base/dist/src/harness/plugin.js --arms none,cm_base,cm_next --repetitions 2" npm run harness:efficiency -- --phase prepare $C P=$(npm run -s harness:efficiency -- --phase freeze $C | tr -d '"') CM_AB_API_KEY=proxy-injected npm run harness:efficiency -- --phase run --protocol $P $C node dist/scripts/summarize_efficiency.js ../exp/run1/results-none+cm_base+cm_next.json summary.json ``` `empty-cache.json` — `{"embedding": , "values": {}}`. Подготовка шести задач занимает около четырёх минут на CPU.