Как добавить память ИИ-агенту: паттерны сохранения состояния для продакшена
Агенты без состояния — те, что забывают всё при завершении Worker — подходят для разовых задач. Как только агент должен помнить, что произошло вчера, узнавать возвращающегося клиента или опираться на предыдущие результаты, нужна память. Есть три паттерна: рабочая память (контекст «в полёте», живёт в KV на время запуска), эпизодическая память (что произошло и когда, журнал для запросов) и семантическая память (что вы знаете, извлекается через векторный поиск или структурированные данные). Подключите нужный паттерн к нужной задаче.
Каждую среду. 28 400+ читателей. Никакой воды.
✓ Проверьте почту — нажмите ссылку подтверждения, чтобы завершить подписку.
✓ Вы подписаны!
✓ Вы уже в списке.
Содержание
Обновлено июнь 2026.
TL;DR: Агенты без состояния — те, что забывают всё при завершении Worker — подходят для разовых задач. Как только агент должен помнить, что произошло вчера, узнавать возвращающегося клиента или опираться на предыдущие результаты, нужна память. Есть три паттерна: рабочая память (контекст «в полёте», живёт в KV на время запуска), эпизодическая память (что произошло и когда, журнал для запросов) и семантическая память (что вы знаете, извлекается через векторный поиск или структурированные данные). Подключите нужный паттерн к нужной задаче.
[Взгляд оператора] Я не раз упирался в стену безгосударственности. Агент ответов в соцсетях, который снова и снова представлялся клиентам, с которыми уже разговаривал 20 раз. Агент ежедневного брифинга, который четыре дня подряд сигнализировал об одной и той же проблеме, потому что не помнил, что уже делал это вчера. Добавление правильного типа памяти исправило оба случая. Вот что я использую.
Почему агенты без состояния продолжают ломаться
Агент без состояния начинает каждый запуск только с тем, что вы явно ему передаёте: системный промпт, сообщение пользователя и данные, которые вы получаете в момент вызова. У него нет осведомлённости о предыдущих запусках, предыдущих пользователях или предыдущих решениях.
Для разовой задачи классификации — прочитать комментарий, вернуть категорию — без состояния правильно. Это быстро, дёшево и предсказуемо.
Поверхность сбоя появляется в момент, когда вам нужна непрерывность:
- Агент для клиентов, который не распознаёт историю клиента
- Агент контента, который рекомендует статью, уже рекомендованную на прошлой неделе
- Агент модерации, который продолжает эскалировать уже решённый кейс
- Ежедневный брифинг, который бесконечно показывает один и тот же устаревший сигнал
Всё это симптомы одной проблемы: у агента нет способа переносить контекст между запусками.
Три типа памяти
Фреймворк, который я нахожу полезным в продакшене:
- Рабочая память — что агент знает прямо сейчас, в течение одного запуска. Хранится в KV или в оперативной памяти в течение жизни вызова.
- Эпизодическая память — что произошло и когда. Структурированный журнал, который агент читает в начале каждого запуска для ориентации.
- Семантическая память — что он знает о мире, клиентах или базе знаний. Извлекается через структурированные запросы или векторный поиск по необходимости.
Вам не всегда нужны все три. Большинству агентов, которые я запускаю, нужны рабочая + эпизодическая. Семантическая память — самая сложная в построении и занимает своё место только тогда, когда база знаний слишком велика для контекстного окна.
Рабочая память: контекст в полёте
Рабочая память — это состояние, которое живёт в течение одного запуска агента. Простейшая форма — переменные в области видимости функции. Более интересная форма — общий ключ KV, который подзадачи в одном запуске читают и пишут.
Мой агент ответов в соцсетях использует рабочую память для накопления контекста при обработке пакета комментариев в одном сообщении очереди. В начале он читает историю последних разговоров каждого клиента из KV, добавляет новый контекст по ходу обработки и записывает обратно в конце.
// workers/social-reply.ts
async function processComment(
comment: SocialCommentEvent,
env: Env
): Promise<void> {
// Загрузить последнюю историю этого клиента из KV (рабочая память)
const historyKey = `customer:${comment.userId}:history`;
const rawHistory = await env.AGENT_KV.get(historyKey);
const history: ConversationTurn[] = rawHistory
? JSON.parse(rawHistory)
: [];
// Построить контекстно-зависимый системный промпт из истории
const systemPrompt = buildSystemPrompt(history);
const response = await anthropic.messages.create({
model: "claude-opus-4-8",
max_tokens: 512,
system: systemPrompt,
messages: [{ role: "user", content: comment.text }],
});
const reply =
response.content[0].type === "text" ? response.content[0].text : "";
// Обновить историю — хранить последние 10 ходов, TTL 30 дней
const updatedHistory: ConversationTurn[] = [
...history.slice(-9),
{ role: "assistant", content: reply, timestamp: comment.timestamp },
];
await env.AGENT_KV.put(historyKey, JSON.stringify(updatedHistory), {
expirationTtl: 60 * 60 * 24 * 30,
});
await postReply(comment, reply, env);
}Два момента. История ограничена 10 ходами — используйте скользящее окно, не давайте ей расти безгранично. И TTL — 30 дней: если клиент молчит месяц, история истекает и агент начинает заново. Оба решения намеренные.
Эпизодическая память: что произошло и когда
Эпизодическая память — это журнал агента. Структурированная запись прошлых запусков, которую агент читает в начале каждого нового запуска, чтобы не повторяться.
Мой агент ежедневного брифинга каждый день выводил одни и те же устаревшие оповещения, потому что каждый запуск не имел никакого представления о том, что уже было отмечено. Решение: структурированный журнал прошлых оповещений, который агент читает перед генерацией брифинга.
// workers/daily-brief.ts
interface AlertLogEntry {
id: string;
surfacedAt: string; // ISO-временная метка
resolvedAt?: string;
summary: string;
}
async function buildDailyBrief(env: Env): Promise<void> {
const [emails, calendar, tasks] = await Promise.all([
fetchOvernightEmails(env),
fetchTodayCalendar(env),
fetchTopTasks(env),
]);
// Загрузить эпизодическую память: что уже было отмечено
const rawLog = await env.AGENT_KV.get("brief:alert-log");
const alertLog: AlertLogEntry[] = rawLog ? JSON.parse(rawLog) : [];
// Фильтровать только недавние, нерешённые оповещения
const sevenDaysAgo = new Date(
Date.now() - 7 * 24 * 60 * 60 * 1000
).toISOString();
const recentAlerts = alertLog.filter(
(e) => e.surfacedAt > sevenDaysAgo && !e.resolvedAt
);
const brief = await synthesizeBrief(
{ emails, calendar, tasks, recentAlerts },
env
);
// Обновить журнал новыми оповещениями, отмеченными в этом запуске
const newAlerts: AlertLogEntry[] = brief.newAlerts.map((a) => ({
id: crypto.randomUUID(),
surfacedAt: new Date().toISOString(),
summary: a,
}));
const updatedLog = [...alertLog, ...newAlerts].slice(-100); // хранить последние 100
await env.AGENT_KV.put("brief:alert-log", JSON.stringify(updatedLog));
await writeToWorkspace(brief.content, env);
}Теперь агент знает, что он уже говорил. Дублирующиеся оповещения не попадают в брифинг, пока основная проблема не изменится. Когда я отмечаю оповещение как решённое, оно исчезает из активного списка.
Этот паттерн обобщается: любой агент, который производит решения, флаги или рекомендации, выигрывает от журнала. Журнал дёшев (несколько КБ в KV), выгода высока (никаких дублирующихся выводов).
Семантическая память: что вы знаете
Семантическая память — это база знаний. Она отвечает на вопрос «что ты знаешь о X?» во время запроса, а не набивает всё в системный промпт заранее.
Простейшая форма — структурированный поиск в KV или базе данных. Мой агент бронирования Pickleland обращается к профилям клиентов и предпочтениям кортов перед составлением подтверждений:
// workers/booking-agent.ts
interface CustomerProfile {
userId: string;
preferredCourts: string[];
experienceLevel: "beginner" | "intermediate" | "advanced";
specialNotes: string;
}
async function draftConfirmation(
booking: BookingEvent,
env: Env
): Promise<string> {
// Получить профиль клиента из KV (семантическая память — фактические знания)
const profileKey = `customer:${booking.userId}:profile`;
const rawProfile = await env.AGENT_KV.get(profileKey);
const profile: CustomerProfile | null = rawProfile
? JSON.parse(rawProfile)
: null;
const systemPrompt = profile
? `Вы составляете персонализированные подтверждения бронирования. Этот клиент предпочитает ${profile.preferredCourts.join(", ")}, игрок уровня ${profile.experienceLevel}. ${profile.specialNotes}`
: "Вы составляете подтверждения бронирования для площадки для пиклбола.";
const response = await anthropic.messages.create({
model: "claude-haiku-4-5-20251001",
max_tokens: 256,
system: systemPrompt,
messages: [
{
role: "user",
content: `Составьте подтверждение для: ${JSON.stringify(booking)}`,
},
],
});
return response.content[0].type === "text" ? response.content[0].text : "";
}Для больших баз знаний — документация продукта, база знаний поддержки, всё, что слишком велико для контекстного окна — нужно векторное хранилище. Схема работы: вложить запрос, извлечь k наиболее релевантных чанков, внедрить их в контекст. Cloudflare Vectorize обрабатывает это нативно, если вы уже на Workers. Для больших индексов я использовал Upstash Vector. Выбор зависит от масштаба, а не от принципа.
Честная заметка о семантической памяти: это самая сложная из трёх для построения и поддержки. Индекс должен оставаться актуальным. Качество извлечения варьируется. Начните со структурированных поисков — KV, таблица в D1 — и переходите к векторному поиску только тогда, когда структурированный подход не может покрыть нужную поверхность знаний.
Фреймворк принятия решений о памяти
Прежде чем добавлять любую память агенту, ответьте на три вопроса:
-
Нужно ли агенту помнить между запусками? Если каждый вызов подлинно независим — перевод, классификация, разовая генерация — пропустите память. Без состояния проще и дешевле.
-
Повторяется ли агент или действует слепо по отношению к своей истории? Если да, сначала добавьте эпизодическую память. Это исправление с наименьшими усилиями и покрывает большинство жалоб «агент продолжает делать X».
-
Обращается ли агент с каждым пользователем или сущностью одинаково, когда не должен? Если да, добавьте рабочую память (история клиента, профиль пользователя) или семантическую память (система поиска или извлечения).
Ошибка, которую я вижу чаще всего: кто-то добавляет огромную базу знаний (семантическая память) к агенту, который на самом деле давал сбой, потому что у него не было эпизодической памяти — никакого журнала того, что он уже делал. Сложность не соответствует проблеме.
Что я реально использую в продакшене
У 30+ агентов:
- Все имеют по меньшей мере рабочую память — некоторую форму состояния внутри запуска, даже если это просто само контекстное окно.
- Около половины имеют эпизодическую память — журнал прошлых запусков, решений или флагов. Это почти всегда стоит добавлять.
- Три-четыре имеют настоящую семантическую память, поддерживаемую векторным хранилищем. Это агенты, которые отвечают на вопросы по большой, динамической базе знаний.
Cloudflare KV — мой стандартный склад для рабочей и эпизодической памяти. Быстрый, дешёвый и нативно интегрированный в Workers — никакого дополнительного клиента, никаких отдельных учётных данных. Ограничение: KV в конечном счёте согласован и не подходит для частых записей. Для агентов, которые записывают состояние много раз в секунду, я использую Durable Objects или базу данных D1.
Для семантической памяти на векторах я использую Cloudflare Vectorize для малых и средних индексов (менее ~100К векторов) и Upstash Vector для всего большего. Оба имеют первоклассные JavaScript-клиенты.
Вывод оператора
Добавляйте память агенту только тогда, когда безгосударственное поведение вызывает реальные проблемы — повторяющиеся выводы, слепые пятна в истории клиентов, игнорирование прошлых решений. Затем выберите правильный уровень: рабочая память для контекста текущего запуска, эпизодическая для того, что происходило исторически, семантическая для того, что вы знаете. Начните с эпизодической, если не уверены — она исправляет наиболее распространённый режим сбоя с наименьшей сложностью. Не обращайтесь к векторной базе данных, пока не исчерпаете структурированные поиски. Лучшая система памяти — самая простая, которая делает агента корректно работающим.
По теме: Стек агентов, который я использую для 30+ продакшн-агентов · Событийные vs. запланированные агенты · Как я измеряю, работает ли ИИ-агент на самом деле
Нужна помощь в проектировании памяти агентов для вашего случая? Свяжитесь со мной — я проектирую продакшн-системы агентов для операторских команд.
Каждую среду. 28 400+ читателей. Никакой воды.
✓ Проверьте почту — нажмите ссылку подтверждения, чтобы завершить подписку.
✓ Вы подписаны!
✓ Вы уже в списке.
Похожие статьи
Как Создать Первый MCP-Сервер: Практическое Руководство
Обновлено для 2026 года. Точный TypeScript-код для создания и регистрации MCP-серверов — транспорт stdio, определения инструментов и как протестировать их в Claude Desktop за менее чем 30 минут.
AI AgentsКэширование промптов в Claude API: снижаем затраты на ввод без смены модели
Как использовать cache_control, чтобы снизить затраты на ввод в Claude API до 90% на агентах с большими стабильными промптами — инвариант совпадения префикса, что кэшировать, скрытые инвалидаторы и математика точки безубыточности.
AI AgentsClaude Fable 5: первые впечатления глазами оператора
Обновлено для 2026 года. Первые впечатления от Claude Fable 5 от человека, который держит в проде больше 30 агентов: что реально изменилось, ловушка со стоимостью, о которой никто не предупреждает, и стоит ли переходить.
Получайте ИИ-руководство на почту
Каждую среду. 28 400+ читателей. Никакой воды.
Проверьте почту.
Мы отправили письмо для подтверждения — нажмите на ссылку, чтобы завершить подписку. Проверьте папку «Спам», если не видите его в течение минуты.
Вы подписаны.
Добро пожаловать — следующий выпуск скоро придёт на вашу почту.
Вы уже в списке — ждите выпуск каждую среду.