AI Agents

Как добавить память ИИ-агенту: паттерны сохранения состояния для продакшена

Alejandro Rioja
Alejandro Rioja
7 мин чтения
TL;DR

Агенты без состояния — те, что забывают всё при завершении Worker — подходят для разовых задач. Как только агент должен помнить, что произошло вчера, узнавать возвращающегося клиента или опираться на предыдущие результаты, нужна память. Есть три паттерна: рабочая память (контекст «в полёте», живёт в KV на время запуска), эпизодическая память (что произошло и когда, журнал для запросов) и семантическая память (что вы знаете, извлекается через векторный поиск или структурированные данные). Подключите нужный паттерн к нужной задаче.

Бесплатная рассылка

Каждую среду. 28 400+ читателей. Никакой воды.

Содержание

Обновлено июнь 2026.

TL;DR: Агенты без состояния — те, что забывают всё при завершении Worker — подходят для разовых задач. Как только агент должен помнить, что произошло вчера, узнавать возвращающегося клиента или опираться на предыдущие результаты, нужна память. Есть три паттерна: рабочая память (контекст «в полёте», живёт в KV на время запуска), эпизодическая память (что произошло и когда, журнал для запросов) и семантическая память (что вы знаете, извлекается через векторный поиск или структурированные данные). Подключите нужный паттерн к нужной задаче.

[Взгляд оператора] Я не раз упирался в стену безгосударственности. Агент ответов в соцсетях, который снова и снова представлялся клиентам, с которыми уже разговаривал 20 раз. Агент ежедневного брифинга, который четыре дня подряд сигнализировал об одной и той же проблеме, потому что не помнил, что уже делал это вчера. Добавление правильного типа памяти исправило оба случая. Вот что я использую.

Почему агенты без состояния продолжают ломаться

Агент без состояния начинает каждый запуск только с тем, что вы явно ему передаёте: системный промпт, сообщение пользователя и данные, которые вы получаете в момент вызова. У него нет осведомлённости о предыдущих запусках, предыдущих пользователях или предыдущих решениях.

Для разовой задачи классификации — прочитать комментарий, вернуть категорию — без состояния правильно. Это быстро, дёшево и предсказуемо.

Поверхность сбоя появляется в момент, когда вам нужна непрерывность:

  • Агент для клиентов, который не распознаёт историю клиента
  • Агент контента, который рекомендует статью, уже рекомендованную на прошлой неделе
  • Агент модерации, который продолжает эскалировать уже решённый кейс
  • Ежедневный брифинг, который бесконечно показывает один и тот же устаревший сигнал

Всё это симптомы одной проблемы: у агента нет способа переносить контекст между запусками.

Три типа памяти

Фреймворк, который я нахожу полезным в продакшене:

  1. Рабочая память — что агент знает прямо сейчас, в течение одного запуска. Хранится в KV или в оперативной памяти в течение жизни вызова.
  2. Эпизодическая память — что произошло и когда. Структурированный журнал, который агент читает в начале каждого запуска для ориентации.
  3. Семантическая память — что он знает о мире, клиентах или базе знаний. Извлекается через структурированные запросы или векторный поиск по необходимости.

Вам не всегда нужны все три. Большинству агентов, которые я запускаю, нужны рабочая + эпизодическая. Семантическая память — самая сложная в построении и занимает своё место только тогда, когда база знаний слишком велика для контекстного окна.

Рабочая память: контекст в полёте

Рабочая память — это состояние, которое живёт в течение одного запуска агента. Простейшая форма — переменные в области видимости функции. Более интересная форма — общий ключ KV, который подзадачи в одном запуске читают и пишут.

Мой агент ответов в соцсетях использует рабочую память для накопления контекста при обработке пакета комментариев в одном сообщении очереди. В начале он читает историю последних разговоров каждого клиента из KV, добавляет новый контекст по ходу обработки и записывает обратно в конце.

typescript
// 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 дней: если клиент молчит месяц, история истекает и агент начинает заново. Оба решения намеренные.

Эпизодическая память: что произошло и когда

Эпизодическая память — это журнал агента. Структурированная запись прошлых запусков, которую агент читает в начале каждого нового запуска, чтобы не повторяться.

Мой агент ежедневного брифинга каждый день выводил одни и те же устаревшие оповещения, потому что каждый запуск не имел никакого представления о том, что уже было отмечено. Решение: структурированный журнал прошлых оповещений, который агент читает перед генерацией брифинга.

typescript
// 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 обращается к профилям клиентов и предпочтениям кортов перед составлением подтверждений:

typescript
// 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 — и переходите к векторному поиску только тогда, когда структурированный подход не может покрыть нужную поверхность знаний.

Фреймворк принятия решений о памяти

Прежде чем добавлять любую память агенту, ответьте на три вопроса:

  1. Нужно ли агенту помнить между запусками? Если каждый вызов подлинно независим — перевод, классификация, разовая генерация — пропустите память. Без состояния проще и дешевле.

  2. Повторяется ли агент или действует слепо по отношению к своей истории? Если да, сначала добавьте эпизодическую память. Это исправление с наименьшими усилиями и покрывает большинство жалоб «агент продолжает делать X».

  3. Обращается ли агент с каждым пользователем или сущностью одинаково, когда не должен? Если да, добавьте рабочую память (история клиента, профиль пользователя) или семантическую память (система поиска или извлечения).

Ошибка, которую я вижу чаще всего: кто-то добавляет огромную базу знаний (семантическая память) к агенту, который на самом деле давал сбой, потому что у него не было эпизодической памяти — никакого журнала того, что он уже делал. Сложность не соответствует проблеме.

Что я реально использую в продакшене

У 30+ агентов:

  • Все имеют по меньшей мере рабочую память — некоторую форму состояния внутри запуска, даже если это просто само контекстное окно.
  • Около половины имеют эпизодическую память — журнал прошлых запусков, решений или флагов. Это почти всегда стоит добавлять.
  • Три-четыре имеют настоящую семантическую память, поддерживаемую векторным хранилищем. Это агенты, которые отвечают на вопросы по большой, динамической базе знаний.

Cloudflare KV — мой стандартный склад для рабочей и эпизодической памяти. Быстрый, дешёвый и нативно интегрированный в Workers — никакого дополнительного клиента, никаких отдельных учётных данных. Ограничение: KV в конечном счёте согласован и не подходит для частых записей. Для агентов, которые записывают состояние много раз в секунду, я использую Durable Objects или базу данных D1.

Для семантической памяти на векторах я использую Cloudflare Vectorize для малых и средних индексов (менее ~100К векторов) и Upstash Vector для всего большего. Оба имеют первоклассные JavaScript-клиенты.

Вывод оператора

Добавляйте память агенту только тогда, когда безгосударственное поведение вызывает реальные проблемы — повторяющиеся выводы, слепые пятна в истории клиентов, игнорирование прошлых решений. Затем выберите правильный уровень: рабочая память для контекста текущего запуска, эпизодическая для того, что происходило исторически, семантическая для того, что вы знаете. Начните с эпизодической, если не уверены — она исправляет наиболее распространённый режим сбоя с наименьшей сложностью. Не обращайтесь к векторной базе данных, пока не исчерпаете структурированные поиски. Лучшая система памяти — самая простая, которая делает агента корректно работающим.


По теме: Стек агентов, который я использую для 30+ продакшн-агентов · Событийные vs. запланированные агенты · Как я измеряю, работает ли ИИ-агент на самом деле

Нужна помощь в проектировании памяти агентов для вашего случая? Свяжитесь со мной — я проектирую продакшн-системы агентов для операторских команд.

Читать дальше

Похожие статьи

Читать дальше

Получайте ИИ-руководство на почту

Каждую среду. 28 400+ читателей. Никакой воды.

↵ — все результаты esc esc — закрыть