Контекст — это весь текст, который модель видит перед собой, когда генерирует ответ: ваш запрос, приложенные файлы, история диалога, инструкции. У модели нет памяти между обращениями — она знает ровно то, что сейчас в контексте, и ничего больше. Понимать это критично: почти все проблемы «модель не поняла / забыла / перепутала» — это проблемы контекста.
Контекстное окно
У каждой модели есть контекстное окно — предел того, сколько токенов она может держать за раз (вход и выход вместе). Это как размер рабочего стола: что на нём лежит — с тем модель и работает; что не поместилось — для неё не существует.
Окна бывают разные — от тысяч до сотен тысяч и миллионов токенов. Большое окно позволяет отдать модели больше (целые файлы, длинную историю), но «большое» не значит «нужно заполнять целиком».
Чтобы эти числа что-то значили, переведём их в файлы вашего проекта. Ориентир простой: файл кода на 300 строк — это примерно 4–5 тысяч токенов (в коде токенов на символ больше, чем в английском тексте, а в русских комментариях — ещё больше). Отсюда порядки:
| Окно | Сколько это примерно | На что хватает |
|---|---|---|
| 8 тысяч | 2 файла по 300 строк | Один вопрос по одному файлу; для агента мало |
| 32 тысячи | 7–8 таких файлов | Небольшая задача в паре файлов |
| 200 тысяч | 40–50 файлов, или 150 страниц текста | Обычная рабочая сессия агента целиком |
| 1 миллион | 200+ файлов, небольшой репозиторий | Разбор незнакомого проекта, длинные документы |
Что стоит забрать из таблицы: окно в 200 тысяч токенов — это не весь проект, а несколько десятков файлов, и типичный репозиторий в него не влезает. Поэтому агент не «загружает проект», а читает файлы по мере надобности, и в окне лежит только то, что он успел прочитать, плюс история разговора.
И вторая мысль, важнее первой: окно расходуется быстрее, чем кажется. Одна команда с длинным выводом (сборка, список файлов, журнал) — это тысячи токенов, которые останутся в истории до конца сессии. Именно поэтому окно кончается не на сороковом файле, а на пятнадцатой минуте разговора, где половина занята выводом команд.
Отдельно от окна у ответа есть свой потолок — сколько токенов модель имеет право сгенерировать за один раз. Он обычно в разы меньше окна: окно на двести тысяч токенов не означает, что можно получить ответ на сто пятьдесят тысяч.
Что лежит в окне по порядку и что в него не влезло: подсвечена середина, которую модель учитывает хуже начала и конца.
Почему модель «забывает»
Два разных механизма, которые новички путают:
- Между разговорами модель не помнит вообще ничего. Новый чат — чистый лист. То, что вы обсуждали вчера, надо дать снова (это и решает настройка агента: правила, скиллы и память кладут нужное в контекст автоматически).
- Внутри длинного диалога всё, что помещается в окно, модели доступно. А когда история перестаёт помещаться, сама модель ничего не выбрасывает: на запрос, который не влез в окно, приходит ошибка. Разбирается с этим программа вокруг модели — чат или агент: обрезает старые сообщения, заменяет их кратким пересказом или честно останавливается.
Поэтому «модель забыла, что я просил в начале» — это почти всегда не сбой модели, а то, как ваш инструмент подрезал историю; в разных инструментах это сделано по-разному, и полезно знать, как именно в вашем.
Посмотрим, как эта подрезка выглядит изнутри: её пишут руками все, кто делает свой чат или помощника поверх модели. На входе четыре вещи: постоянная часть (правила помощника, они же системная часть), история прошлых ходов, свежий вопрос и бюджет — сколько токенов мы готовы отдать на вход.
Правило простое. Системная часть и последний вопрос остаются всегда: без правил помощник перестанет быть собой, без вопроса отвечать не на что. История берётся с конца, потому что свежие ходы нужнее старых, и добавляется, пока укладывается в бюджет; что не влезло — выбрасывается молча. Цену хода считают один раз, когда кладут его в историю, и хранят рядом с самим сообщением.
record Turn(ChatCompletionMessageParam message, int tokens) {}
String ask(String system, List<Turn> history, String question, int budget) {
int used = (system.length() + question.length()) / 3;
Deque<Turn> kept = new ArrayDeque<>();
for (Turn turn : history.reversed()) {
if (used + turn.tokens() > budget) {
break;
}
kept.addFirst(turn);
used += turn.tokens();
}
ChatCompletionCreateParams.Builder params = ChatCompletionCreateParams.builder()
.model("gpt-4o-mini")
.addSystemMessage(system);
kept.forEach(turn -> params.addMessage(turn.message()));
params.addUserMessage(question);
return client.chat().completions().create(params.build())
.choices().get(0).message().content().orElse("");
}
type Turn struct {
Message openai.ChatCompletionMessageParamUnion
Tokens int
}
func ask(ctx context.Context, system string, history []Turn, question string, budget int) (string, error) {
used := (len(system) + len(question)) / 3
kept := []openai.ChatCompletionMessageParamUnion{}
for i := len(history) - 1; i >= 0; i-- {
if used+history[i].Tokens > budget {
break
}
kept = slices.Insert(kept, 0, history[i].Message)
used += history[i].Tokens
}
messages := append([]openai.ChatCompletionMessageParamUnion{openai.SystemMessage(system)}, kept...)
messages = append(messages, openai.UserMessage(question))
resp, err := client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{
Model: "gpt-4o-mini",
Messages: messages,
})
if err != nil {
return "", err
}
return resp.Choices[0].Message.Content, nil
}
async function ask(system, history, question, budget) {
let used = Math.floor((system.length + question.length) / 3);
const kept = [];
for (const turn of [...history].reverse()) {
if (used + turn.tokens > budget) break;
kept.unshift(turn.message);
used += turn.tokens;
}
const answer = await client.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "system", content: system }, ...kept, { role: "user", content: question }],
});
return answer.choices[0].message.content;
}
def ask(system, history, question, budget):
used = (len(system) + len(question)) // 3
kept = []
for turn in reversed(history):
if used + turn["tokens"] > budget:
break
kept.insert(0, turn["message"])
used += turn["tokens"]
answer = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "system", "content": system}, *kept, {"role": "user", "content": question}],
)
return answer.choices[0].message.content
Где тут спотыкаются. Бюджет это не всё окно: из окна вычитают потолок ответа, иначе вход уйдёт впритык и модели негде будет писать. Ходы выбрасывают парами «вопрос плюс ответ»: одинокий ответ без своего вопроса модель читает как обрывок и отвечает мимо. Деление длины на три это грубая оценка для примера, настоящий счётчик токенов даст другое число, поэтому бюджет берут с запасом, а не вплотную к пределу. И главное: системная часть осталась на месте, а вот договорённость из пятого хода уже выброшена, и помощник про неё не знает — ровно то, что пользователь назовёт «он забыл».
«Память» продуктов — это тот же контекст
Утверждение «между разговорами модель не помнит ничего» верно про модель и противоречит опыту: чат помнит, что вы просите отвечать по-русски и короче, помнит имя вашего проекта, а агент помнит, что в прошлый раз вы отказались от определённого подхода. Кажется, что статья врёт.
Никакого противоречия нет: память имитирует программа вокруг модели, а не модель. Устроено это тремя способами, и полезно различать их, потому что вести себя они ведут по-разному.
Файлы правил. Самое простое и самое предсказуемое: в проекте лежит файл с правилами и договорённостями, и агент читает его в начале каждой сессии, добавляя во вход. Это не память, а автоматическая подстановка одного и того же текста — зато вы видите этот текст и правите его руками.
Сохранённые факты. Продукт по ходу разговора выписывает в отдельное хранилище то, что похоже на устойчивый факт про вас («работает с Java, предпочитает короткие ответы»), и подмешивает подходящие факты во вход следующих разговоров. Отсюда ощущение памяти. Отсюда же два подвоха: во-первых, туда попадает не то, что вы считаете важным, а то, что счёл важным продукт; во-вторых, устаревшее оттуда не исчезает само — записанное однажды предпочтение будет подмешиваться и через полгода, когда оно уже не ваше. Практика простая: раз в какое-то время смотреть список сохранённого и чистить.
Поиск по прошлым разговорам. Некоторые продукты умеют найти релевантный кусок старой переписки и подставить его в текущий вход. Это уже поиск, а не память, и работает он как любой поиск: иногда находит не то.
Что из этого следует для работы. Во-первых, всё, что «помнится», на самом деле занимает контекст — то есть стоит денег и разбавляет важное; длинный список сохранённых фактов работает против вас. Во-вторых, на память нельзя опираться в важном: если ограничение обязано соблюдаться, оно должно лежать в файле правил проекта или в самом запросе, а не «мы это обсуждали». В-третьих, память привязана к продукту и учётной записи: в другом инструменте или у коллеги её нет, и код, который «работает у меня», у соседа не воспроизводится, потому что у него агент не знает того, что знает ваш.
Правила, скиллы и память на живом примере
Три слова выше — «файл правил», «скиллы», «память» — звучат абстрактно, пока не посмотреть, как они лежат в настоящем репозитории. Возьмём открытый набор скиллов курса, remodov/usecase-pattern-skills: он устроен ровно вокруг бюджета контекста.
Что в контексте всегда. В корне проекта лежит CLAUDE.md — правила проекта на одну-две страницы, которые агент читает в начале каждой сессии. Рядом .claude/rules/ucp-java-core.md или ucp-python-core.md, по языку сервиса: выжимка методологии на сто строк, то, что надо знать до первого символа кода. Это постоянная плата, четыре-пять тысяч токенов в каждом запросе, поэтому туда попадает только то, без чего агент напишет не так.
Что подгружается по требованию: скилл. Скилл — это папка с файлом SKILL.md: короткая шапка, по которой агент решает, нужен ли скилл сейчас, и инструкция, что читать и что делать. Вот шапка и первый шаг настоящего скилла проектирования REST-эндпоинта:
---
name: ucp-api-design
description: Спроектировать новый REST API-эндпоинт или ресурс по требованиям rest-api/* для Java/Spring — OpenAPI-first, нейминг URL, статусы, ошибки ProblemDetails.
when_to_use: Создание новых эндпоинтов, проектирование структуры API, написание OpenAPI-спеки с нуля.
allowed-tools: Read Glob Grep Write Edit
---
1. **Прочитай индекс правил** `.claude/docs/backend/rest-api/spec.md` — компактный список
всех кодов с формулировками; следуй каждому строго. Полную версию
`references/java/implementation.md` (примеры, обоснование) читай **точечно по нужному
разделу**, когда индекса не хватает — не целиком.
2. **Уточни требования** ...
Скилл в примере написан под Java и Spring, но устроен он одинаково для любого языка: шапка, индекс правил, справочник по требованию. Смотреть стоит на две вещи. В контексте каждой сессии лежит только список шапок всех скиллов, по строке на скилл; тело попадает туда в момент, когда скилл выбран. И сам скилл управляет контекстом дальше: велит прочитать короткий индекс правил и запрещает тянуть полный справочник целиком. Корпус требований в .claude/docs/ — сотни страниц, в окно он не влезет, а индекс на две тысячи токенов влезает всегда.
Память. Агент для кода ведёт её как картотеку: файл-индекс MEMORY.md, где по строке на факт, и по отдельному файлу на каждый факт с шапкой — имя, однострочное описание, тип (о пользователе, обратная связь, проект, ссылка). В сессию попадает только индекс; карточка читается, когда факт понадобился. Так выглядит одна из них:
---
name: project_cones_currency
description: "Шишки — внутренняя валюта ИИ-ментора в кабинете: правила, где живёт код, что решено с владельцем"
type: project
---
База 20 шишек в день, потолок 200; задача +2, тест +10, фаза +20 ...
Отсюда и практика работы с такой памятью: факт на карточку, устаревшие карточки удалять, а то, что обязано соблюдаться всегда, переносить из памяти в CLAUDE.md — память агент может не открыть, правила он читает каждый раз.
Больше контекста ≠ лучше
Соблазн — вывалить в модель всё: весь репозиторий, всю документацию, всю переписку. На практике это вредит:
- Разбавляется важное. Среди 50 файлов нужные три теряются; модель хуже находит релевантное в шуме. Это иногда называют «потерянным в середине» — то, что лежит в середине огромного контекста, модель учитывает хуже, чем начало и конец.
Из последнего свойства следует практика, которая стоит ноль и заметно помогает.
Важное — в начало и в конец. Инструкции и ограничения кладут в начало запроса, а самое главное требование повторяют в конце, после материалов. Это не суеверие: конец запроса — ближайший к ответу текст, и модель опирается на него сильнее всего. Отсюда рабочая форма длинного запроса: задача → материалы → «напоминаю: сделай то-то, не трогай то-то, ответ в таком-то виде».
Материалы — с подписями. Когда в запросе несколько файлов или документов, каждый начинают строкой «файл такой-то» (или тегом), а не склеивают в одну простыню. Модель тогда может сослаться на источник, а вы — проверить, откуда она взяла утверждение.
Требование — рядом с материалом, к которому относится. Если правило касается конкретного файла, его пишут рядом с этим файлом, а не в общем списке из десяти пунктов на две страницы выше.
Длинный разговор — повторять ограничения. В сессии на час ограничение, названное в первой реплике, к тридцатой уже далеко в середине и, возможно, потеряно при сжатии истории. Ключевые запреты («миграции не трогай», «не меняй публичный контракт») повторяют явно, когда переходят к новому шагу. Это же лечится тем, что такие правила кладут в файл настроек агента, который читается каждый раз, — тогда они всегда в начале контекста.
- Дороже и медленнее. Каждый лишний токен — это деньги и время.
- Растёт риск ошибки. Больше противоречивого материала — больше шанс, что модель зацепится не за то.
Правило продукт-инженера: давать релевантный контекст, а не максимальный. Три нужных файла лучше пятидесяти «на всякий случай».
Как управляют контекстом
Управление контекстом — ключевой навык работы с ИИ:
- Отбирайте релевантное — только те куски кода, документации, требований, что относятся к задаче.
- Давайте факты, а не «вспомни сам» — заземление на данные прямо в контексте резко снижает галлюцинации.
- Подтягивайте контекст по требованию через вызов инструментов: вместо того чтобы залить всё сразу, дайте модели возможность достать нужное (поиск по коду, запрос к базе) в момент, когда оно понадобилось.
- Не тащите бесконечную историю — на длинных диалогах начинайте новый с кратким резюме нужного.
- Автоматизируйте повторяющееся — то, что модель должна знать всегда (стиль, правила проекта), выносят в правила, скиллы и память проекта (как это настраивают), чтобы не вставлять руками каждый раз.
Что уходит наружу вместе с контекстом
Первый вопрос, который задаёт служба безопасности, когда узнаёт, что команда работает с агентом: а что именно уезжает к поставщику модели? Ответ прямой и его стоит знать до разговора, а не во время.
Уезжает всё, что попало в контекст: файлы, которые агент прочитал, вывод команд, который он видел, ваши реплики, содержимое ошибок и трасс, имена переменных и путей. Плюс то, что подставляется автоматически, — правила проекта, описания инструментов. То есть при работе с агентом по закрытому коду за границу процесса уходит именно закрытый код, а не абстрактное «мы спросили ИИ».
Что из этого следует практически:
- Секреты не должны попадать в контекст. Не потому, что поставщик злоумышленник, а потому, что это второй экземпляр секрета в чужой системе. Файлы с ключами — в исключения агента, вывод команд, печатающих переменные окружения, — не в диалог. И правило: секрет, засветившийся в диалоге, считают утёкшим и меняют.
- Персональные данные в контексте — это передача персональных данных обработчику со всеми требованиями закона; настоящие данные пользователей в запросы не кладут, для отладки берут обезличенные.
- У поставщиков разные условия по обучению на ваших данных. У корпоративных тарифов обычно записано, что данные не используются для обучения и удаляются через срок; у бесплатных — часто наоборот. Это первое, что читают перед внедрением, и это же главный аргумент в разговоре с безопасностью.
- Есть варианты, где данные не выходят из контура: модель в вашем облаке или открытая модель на своём железе. Качество ниже, цена владения выше, и для части команд это единственно допустимый путь.
Подробный разбор с тем, как это обсуждают с безопасностью и что писать в правилах команды, — в разделе «Глубже» статьи про агентов.
Что это значит на практике
Контекст — это рабочая память модели, и качество ответа во многом определяется тем, что вы в неё положили. Продукт-инженер думает не «какой вопрос задать», а «что должно быть перед глазами у модели, чтобы она ответила хорошо»: нужные файлы, факты, правила — и ничего лишнего. Управлять контекстом важнее, чем красиво формулировать запрос.
Глубже: когда контекст кончился: сжатие, перезапуск с резюме и его признакирасширенное
«Начинайте новый диалог» это совет, а не механика. Что происходит, когда история перестаёт помещаться, зависит от инструмента, и полезно знать три вещи.
Сжатие. Агенты для кода не дают окну переполниться: заранее, при приближении к пределу, они просят модель пересказать историю в короткое резюме (задача, что сделано, какие решения приняты, какие файлы тронуты) и продолжают с ним вместо полной истории. Это сжатие, и оно происходит без вашего участия. После него агент помнит выводы, но не детали: точные формулировки требований, которые вы дали в начале, аргументы отвергнутых вариантов, содержимое файлов, которые он читал.
Признаки, что сжатие уже случилось. Агент переспрашивает то, что вы говорили час назад. Меняется стиль ответов или имена в коде. Он повторно читает файлы, которые уже читал. Забывает ограничение («не трогай миграции»), которое соблюдал. Инструменты обычно сообщают о сжатии строкой в журнале, и её стоит замечать: после неё ключевые ограничения повторяют явно.
Перезапуск с резюме. Надёжнее, чем полагаться на автоматическое сжатие, завершить сессию самому в удобной точке: попросить агента записать резюме в файл (задача, сделано, осталось, решения и почему, известные проблемы), проверить его глазами, и начать новую сессию с этого файла. Резюме, написанное до переполнения, полнее того, что модель напишет в спешке у предела. То же правило для долгих задач: план и заметки живут в файле в репозитории, а не в истории диалога, и переживают любое сжатие.
Как не доходить до предела. Задача на сессию одна и небольшая; вывод команд и содержимое больших файлов не вываливают в диалог целиком, а просят агента посмотреть нужную часть; подзадачи, которые дают много шума (поиск по репозиторию, чтение документации), отдают отдельным субагентам, чей контекст не попадает в основной. Об этом статьи про настройку агента и работу с ним.
Глубже: обучить модель на нашем коде или дать контекстрасширенное
Через неделю работы с агентом каждый спрашивает: а можно обучить модель на нашем репозитории, чтобы она его знала? Ответ почти всегда «не нужно», и понимание причины экономит месяцы.
Дообучение меняет параметры модели на ваших примерах. Оно хорошо учит форме: стиль ответов, формат вывода, жаргон узкой области, поведение, которое трудно описать инструкцией. Оно плохо учит фактам: модель после дообучения на коде не «помнит» ваш репозиторий как справочник, а лишь чуть охотнее пишет в его стиле; конкретные сигнатуры и правила она по-прежнему достраивает. И дообученная модель устаревает в момент следующего коммита, а переобучение стоит денег и требует набора примеров, размеченных людьми.
Контекст решает ту же задачу дешевле и точнее: правила проекта в файле настроек агента, которые он читает каждую сессию; инструменты поиска по коду, которыми он достаёт нужные файлы по требованию; для базы знаний поиск по документам с подмешиванием найденного в промпт (о нём статья про RAG в следующей фазе). Всё это обновляется вместе с репозиторием и не требует ни данных, ни обучения.
Когда дообучение всё же оправдано: тысячи примеров одного формата (разметка обращений по вашей схеме), где нужна маленькая быстрая модель вместо большой с длинной инструкцией; или стиль, который инструкцией не передать. Для «чтобы агент знал наш код» ответ один: контекст.
Коротко
- Контекст это всё, что модель видит за один вызов; между разговорами памяти нет, у окна есть предел, у ответа свой потолок.
- Больше контекста не значит лучше: важное разбавляется, растут цена и ошибки; давать релевантное и подтягивать по требованию через инструменты.
- Когда история не влезает, агент сжимает её в резюме сам; признаки сжатия это переспросы и забытые ограничения; надёжнее перезапуск с резюме в файле и заметки в репозитории.
- «Обучить на нашем коде» почти всегда не нужно: дообучение учит форме, а не фактам, и устаревает с первым коммитом; факты дают контекстом, правилами и поиском.
- Окно в 200 тысяч токенов — это 40–50 файлов по 300 строк, а не весь проект; расходуется оно быстрее всего выводом команд, который остаётся в истории.
- Против «потерянного в середине»: инструкции в начало, главное требование повторить в конце после материалов, материалы подписать, ограничения повторять в длинной сессии.
- «Память» продуктов — это подстановка текста программой вокруг модели: сохранённые факты не исчезают сами и тоже занимают контекст, поэтому важное держат в файле правил, а не в памяти.
- В контекст попадает и уезжает к поставщику всё, что агент прочитал: секреты в диалоге считают утёкшими, персональные данные не кладут, условия про обучение на ваших данных читают заранее.
- Правила проекта и ядро методологии лежат в контексте всегда (тысячи токенов), скилл подгружается по шапке и сам велит читать индекс, а не справочник, память — картотека: индекс в сессии, карточки по требованию.
Что почитать дальше
Контекст можно наполнять не только вручную, но и давать модели доставать нужное самой — через вызов инструментов. А когда модель в цикле сама решает, что достать и что сделать, — это уже агенты.