У меня небольшой бюджет на AI-ассистент — $20 в месяц. Хватает, но только если понимаешь как работает тарификация. Я потратил время чтобы разобраться что именно ест токены, и написал framework который пытается решить эти проблемы. Расскажу про оба.

Как формируется контекст

Каждый запрос к модели стоит денег пропорционально размеру контекста — всего что модель читает перед ответом. В Cursor контекст состоит из нескольких слоёв:

  1. Файлы которые вы явно указали через @

  2. Файлы которые Cursor подтянул автоматически как связанные

  3. Содержимое .cursor/context/ — если есть

  4. Правила из .cursor/rules/ — если есть

  5. Проиндексированное содержимое проекта

Последний пункт — главная проблема. По умолчанию 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 проект и желание проверить — буду рад обратной связи в комментариях. Особенно интересно где не работает как ожидается и насколько цифры отличаются от моих.

GitHub