У меня небольшой бюджет на AI-ассистент — $20 в месяц. Хватает, но только если понимаешь как работает тарификация. Я потратил время чтобы разобраться что именно ест токены, и написал framework который пытается решить эти проблемы. Расскажу про оба.
Как формируется контекст
Каждый запрос к модели стоит денег пропорционально размеру контекста — всего что модель читает перед ответом. В Cursor контекст состоит из нескольких слоёв:
Файлы которые вы явно указали через
@Файлы которые Cursor подтянул автоматически как связанные
Содержимое
.cursor/context/— если естьПравила из
.cursor/rules/— если естьПроиндексированное содержимое проекта
Последний пункт — главная проблема. По умолчанию Cursor индексирует всё в директории проекта. В Go проекте это vendor/ с зависимостями, .git/ с историей, dist/, логи. Реальный код занимает процент от этого.
.cursorignore работает как .gitignore — говорит Cursor что не трогать. После его настройки индексируется только то что нужно.
Почему Agent Mode дорогой
В Agent Mode Cursor может создавать и редактировать файлы. Но есть особенность: при каждом File Edit контекст перечитывается заново. Не один раз за задачу — а для каждого изменения отдельно.
Когда AI исправляет баг, он обычно создаёт два файла: исправленный код и тест. Это два перечитывания контекста. Нормально.
Проблема в том что AI по своей инициативе часто создаёт дополнительные файлы: описание что было сделано, чеклист, саммари изменений. Я видел как одна задача порождала 11 File Edits вместо двух. При контексте в 100K токенов это 1.1M токенов входа вместо 200K.
Есть механизм Prompt Caching — повторное чтение одного и того же контекста стоит дешевле. Но кэш работает только если контекст не меняется между запросами. Каждый новый файл меняет контекст — кэш инвалидируется, платишь полную цену снова.
Запрет на создание лишних файлов через .cursor/rules/ решает это напрямую: AI создаёт только код и тест, всё остальное запрещено правилами которые Cursor применяет автоматически.
Кеш на стороне платформы и почему он не всегда помогает
Anthropic поддерживает Prompt Caching на уровне API. Если вы отправляете одинаковый контекст в нескольких запросах подряд — повторное чтение кешированной части стоит значительно дешевле.
Но у этого кеша есть TTL — он живёт около 30 минут без обращения. Если вы ушли на кофе, отвлеклись на встречу, вернулись через час — кеш протух. Следующий запрос снова читает весь контекст по полной цене.
Это не баг, это особенность архитектуры. Практический вывод: при маленьком бюджете лучше не рассчитывать на кеш как на стабильную экономию. Лучше изначально держать контекст маленьким — тогда и цена каждого запроса меньше, и потеря кеша не так болезненна.
Потеря контекста при смене модели
Cursor Composer — stateless. У каждого разговора нет памяти о предыдущих. Когда меняешь модель — начинаешь с чистого листа.
Это важно если используешь разные модели для разных задач. Дорогая модель для анализа архитектуры, дешёвая для рутинных задач — логичная стратегия. Но если дешёвая модель не видит что делала дорогая, приходится объяснять контекст заново.
Решение через файловую структуру: дорогая модель пишет результаты анализа в файл, дешёвая читает этот файл через @. Контекст не теряется при смене модели — он хранится на диске, а не в памяти сессии.
.cursor/ ├── analysis/ │ └── project-map.md # Opus написал один раз └── plans/ └── tasks/ └── task-003.md # Haiku читает и выполняет
Проблема большого плана
Когда задач много, появляется соблазн держать весь план в одном файле и тянуть его через @ в каждый промпт.
При выполнении задачи №7 модель читает задачи 1-6 которые уже сделаны, и задачи 8-20 которые ещё не актуальны. Это лишние токены при каждом запросе.
Вторая проблема — план устаревает. Анализ написан в начале: "функция ProcessOrder, строка 47". После первых задач строки сдвинулись, что-то переименовано. Модель работает по устаревшим координатам и ошибается.
Разделение на маленькие файлы решает первое: при выполнении задачи в контексте только её файл — 15-20 строк, не весь план на 300.
Для второго — снапшоты. Но сначала стоит понять саму проблему подробнее, потому что это ключевой момент.
Opus анализирует проект и создаёт карту:
## service.go — 150 строк ### Функции: - `ProcessOrder` (~строка 47) — обрабатывает заказ - `withdrawAndDeposit` (~строка 124) — списание и пополнение
На основе этого Sonnet создаёт план. Задача task-003 выглядит так:
## Функция `withdrawAndDeposit` (~строка 124)
Выполняем задачи 001 и 002. В ходе task-001 рефакторим service.go — разбиваем большую функцию, добавляем обработку ошибок. Файл вырастает со 150 до 220 строк.
Теперь ситуация такая:
В плане написано: В реальном коде: withdrawAndDeposit withdrawAndDeposit ~строка 124 ~строка 187 ← сдвинулась на 63 строки
Когда приходит время task-003, модель идёт к строке 124, видит там что-то другое и либо ошибается, либо тратит токены на повторный поиск. Чем больше задач выполнено — тем сильнее план расходится с реальностью.
Снапшоты решают это так. После каждого git commit запускается скрипт:
# snapshot-state.sh DATE=$(date '+%Y-%m-%d %H:%M') CHANGED=$(git diff --name-only HEAD) echo "## $DATE" >> .cursor/snapshots/changes.md echo "$CHANGED" | while read f; do lines=$(wc -l < "$f") echo "- $f ($lines строк)" >> .cursor/snapshots/changes.md done
После task-001 в changes.md появляется запись:
## 2026-02-23 14:30 - src/mascot/service.go (220 строк) - src/mascot/service_test.go (45 строк)
Это просто лог. Сам по себе он ничего не делает.
Важное происходит в run-next-task.txt — перед выполнением следующей задачи промпт читает этот лог и обновляет файл только следующей задачи:
Прочитай .cursor/snapshots/changes.md. service.go изменился. Задача task-003 ссылается на этот файл. Найди актуальное расположение функции withdrawAndDeposit и обнови строку в task-003.md. Остальные задачи не трогай.
Модель открывает service.go, находит withdrawAndDeposit на строке 187, обновляет task-003.md. Задача выполняется по актуальным координатам.
Ключевое здесь — обновляется только следующая задача, не весь план. Переписывать весь план при каждом изменении было бы дорого и медленно.
Как это запускается: анализ и план
Прежде чем выполнять задачи, нужно понять что вообще делать. Framework начинается с двух шагов.
Анализ проекта. Промпт 01-analyze-project.txt запускается один раз с Opus. Он сначала выполняет bash-скрипты локально — собирает структуру проекта, количество строк, список TODO — и использует этот вывод как контекст. Потом читает сам код и создаёт файл .cursor/analysis/project-map.md:
## service.go — 150 строк | оценка 4/10 ### Функции: - `withdrawAndDeposit` (~строка 124) — списание и пополнение - `ProcessOrder` (~строка 47) — обрабатывает заказ ### Проблемы: - [КРИТИЧНО] нет DB-транзакции между GetBalance и UpdateBalance - [ВАЖНО] функция ProcessOrder больше 50 строк, сложно тестировать
Это карта проекта. Она не меняется — создаётся один раз и служит исходником для следующего шага.
Создание плана. Промпт 02-create-plan.txt запускается с Sonnet. Он читает карту и создаёт два вида файлов.
Первый — индекс optimization-plan.md, п��осто список ссылок:
## Очередь - [ ] task-001 — утечка горутин в retry (03-fix-simple-bug, Haiku, ~$0.03) - [ ] task-002 — валидация параметров (05-refactor, Haiku, ~$0.12) - [ ] task-003 — race condition в withdrawAndDeposit (03-fix-simple-bug, Sonnet, ~$0.15)
Второй — отдельный файл для каждой задачи в .cursor/plans/tasks/. Эти файлы и есть суть хранения контекста: каждый содержит всё что нужно для выполнения одной задачи — конкретный файл, конкретную функцию, конкретное решение. Индекс — это только ссылки на них, сами задачи живут отдельно.
Такое разделение нужно именно для контроля контекста: при выполнении задачи модель читает только её маленький файл, не весь план целиком.
Как работает run-next-task под капотом
Основной промпт framework'а run-next-task.txt — это последовательность bash команд которые Cursor выполняет сам в Agent Mode:
Шаг 1: найти задачу
grep -m 1 '^\- \[ \]' .cursor/plans/optimization-plan.md
Возвращает первую строку без ✅, из неё берётся номер задачи.
Шаг 2: прочитать файл задачи
cat .cursor/plans/tasks/task-003.md
Файл содержит проблему, путь к файлу, имя функции, конкретное решение, какой промпт использовать.
Шаг 3: прочитать сам промпт
cat .cursor/scripts/prompts/05-refactor.txt
Промпт содержит конкретные инструкции для типа задачи: что создать, что не создавать, как запустить тесты.
Шаг 4: прочитать целевой код
cat src/mascot/types.go
Шаг 5: выполнить Модель следует инструкциям из промпта, используя детали из файла задачи.
Шаг 6: зафиксировать
bash .cursor/scripts/bash/snapshot-state.sh git commit -m "feat: add input validation"
Шаг 7: закрыть задачу
mv .cursor/plans/tasks/task-003.md .cursor/plans/done/task-003.md
В индексе ставится ✅, проверяется следующая задача по changes.md.
Вся эта цепочка запускается одним промптом. Контекст при этом минимальный: файл задачи (15-20 строк) + целевой файл кода + base.md который Cursor читает автоматически.
Установка в проект
Framework распространяется как репозитарий на гитхабе. После клониования:
cd your-go-project bash /path/to/framework/setup.sh bash test-framework.sh
setup.sh копирует всю структуру .cursor/ в ваш проект, создаёт .cursorignore, копирует промпты, bash-скрипты и test-framework.sh.
test-framework.sh проверяет что всё на месте — структура папок, все промпты, скрипты, содержимое .cursorignore. Выдаёт список ✅ и ❌ по каждому пункту. Если что-то не так — сразу видно что именно.
После установки в корне проекта появляется:
your-project/ ├── .cursorignore ├── test-framework.sh ├── GUIDE.md └── .cursor/ ├── rules/optimization.mdc # правила — запрет лишних файлов ├── context/base.md # описание проекта — заполнить под себя ├── analysis/ # сюда Opus запишет карту проекта ├── plans/ │ ├── optimization-plan.md # индекс задач │ ├── tasks/ # файлы задач │ └── done/ # выполненные ├── snapshots/changes.md # лог изменений кода └── scripts/ ├── bash/ # скрипты анализа └── prompts/ # все промпты
Единственное что нужно сделать руками — заполнить .cursor/context/base.md. В архиве есть готовый пример для Go + Clean Architecture, обычно достаточно поменять название проекта и цель.
Эту проблему решают и другие
Пока я разбирался с токенами, наткнулся на Get Shit Done — framework для Claude Code который решает ту же проблему деградации контекста, но с другой стороны.
Их подход: оркестратор + subagents. Главная сессия остаётся лёгкой и только координирует. Тяжёлую работу делают subagents — каждый стартует с чистым контекстом, выполняет одну задачу, записывает результат в файл (SUMMARY.md, STATE.md) и умирает. Следующий subagent читает эти файлы и продолжает. Основная сессия никогда не деградирует.
Это та же центральная идея — контекст живёт в файлах, а не в памяти сессии. Только у GSD это автоматизировано на уровне multi-agent оркестрации, а у меня реализовано вручную через промпты в Cursor.
Где расходимся: GSD не контролирует что загружается в контекст subagent при старте. Если в проекте нет аналога .cursorignore — subagent получает раздутый контекст из vendor/ и .git/ с первого же запроса. Мой framework решает именно этот пласт: что попадает в контекст, сколько это стоит, как не платить за мусор. GSD про это не говорит вообще.
Они скорее дополняют друг друга чем конкурируют. GSD — система управления проектом. Мой framework — оптимизация затрат внутри Cursor.
Результаты на тестовом проекте
Я прогнал framework на Go сервисе около 1500 строк — несколько багов, валидация параметров, тесты, godoc.
Фаза | Без framework | С framework |
|---|---|---|
Анализ | ~$5 | ~$2 |
4 бага | ~$4 | ~$0.12 |
Рефакторинг | ~$3 | ~$0.15 |
3 теста | ~$2 | ~$0.54 |
Документация | ~$2 | ~$0.40 |
Итого | ~$16 | ~$3.21 |
Цифры получились ожидаемыми — каждый компонент экономит на своём. Но это один тестовый проект в контролируемых условиях. Насколько это воспроизводится на других проектах с другим кодом — непонятно.
Вопросы которые возникнут
— У Cursor есть RAG, он сам индексирует проект и находит нужные файлы. Зачем тогда вся эта структура?
RAG в Cursor действительно работает — он умеет находить релевантные файлы по семантическому сходству. Но это не то же самое что явный контроль контекста.
Во-первых, RAG решает поиск, но не решает проблему объёма. Если проект не огорожен .cursorignore, поиск идёт по всему включая vendor/ и .git/. Найденные файлы всё равно попадают в контекст и стоят токенов.
Во-вторых, RAG — это вероятностный поиск. Он находит то что семантически похоже на запрос. Файловая структура .cursor/ — это детерминированный контекст: конкретный файл задачи, конкретный промпт, конкретный целевой файл. Никакой случайности в том что попадёт в контекст.
В-третьих, RAG не решает проблему потери контекста между моделями и между сессиями. Результат анализа который Opus записал в файл — он там и лежит. RAG-индекс при смене модели начинается заново.
— В Cursor есть встроенный режим планирования. Почему не использовать его?
Встроенный режим плана в Cursor удобен для небольших задач — попросил, получил список шагов, подтвердил. Но у него есть ограничения.
Он работает в рамках одного разговора. Как только сессия закончилась или модель сменилась — план исчез. Нет персистентности между сессиями.
Кроме того, встроенный план не решает проблему устаревания. Если план создан в начале работы и задачи выполняются несколько дней — он всё равно будет указывать на старые строки и функции.
Файлы задач в .cursor/plans/tasks/ живут на диске независимо от сессий, моделей и перезапусков Cursor.
— Кеш же должен спасать, зачем тогда минимизировать контекст?
Кеш на стороне платформы живёт около 30 минут. Если не успел продолжить работу до истечения TTL — кеш протух, следующий запрос снова платный в полную цену. Маленький контекст стоит меньше и при попадании в кеш, и при промахе.
— Это работает только для Go?
Структура .cursor/ и принципы — универсальны. Bash-скрипты для анализа и промпты — написаны под Go. Для другого языка нужно адаптировать промпты и скрипты, структура остаётся той же.
— В промптах и файлах задач указаны конкретные модели — Haiku, Sonnet, Opus. Это обязательно?
Нет, это рекомендации фреймворка основанные на балансе цена/качество для типичных задач. Haiku для простых багов, Sonnet ��ля рефакторинга, Opus для первоначального анализа — это не правила, а отправная точка.
Перед каждой задачей вы сами выбираете модель в Cursor. Если хотите перестраховаться и взять Sonnet там где стоит Haiku — никто не запрещает. Если у вас другой бюджет или другие предпочтения — слушайте себя. Модели в файлах задач просто подсказывают что авторы framework'а использовали бы в этой ситуации.
— А если у меня не Clean Architecture?
base.md — это просто описание вашего проекта. Можно описать любую структуру. Clean Architecture там как пример, не как требование.
Если у вас есть Go проект и желание проверить — буду рад обратной связи в комментариях. Особенно интересно где не работает как ожидается и насколько цифры отличаются от моих.
