Agent Memory

Память агента — это 4 разных вида хранилища: context window, conversation history, long-term memory (vector DB) и episodic memory (логи траекторий). Управление памятью = управление и качеством, и стоимостью.

Суть

  • Context window — то, что в промпте прямо сейчас (см. Context Window); быстро, но ограничено.
  • Conversation history — прошлые ходы текущей сессии; обрезаются при переполнении.
  • Long-term — факты/предпочтения в vector DB (Qdrant, Pinecone, pgvector); переживает сессию.
  • Episodic — логи прошлых успехов/провалов как справочник для будущих задач.

Зачем это нужно

Без памяти агент тащит весь контекст на каждом шаге → взрывной рост токенов. Кейс из материала: наивный агент-химик (IBM Materials) съел 20 млн токенов против 1234 у агента с указателями — разница ~16 000×.

Как работает

  • Memory pointers: большие данные хранятся вне контекстного окна, агент работает с ними через короткие идентификаторы-указатели, подтягивая по запросу (а не пересылая целиком). Управление контекстом = управление стоимостью (см. Agent CostControl).
  • Долгая память реализуется через retrieval — это и есть RAG поверх vector DB; выбор хранилища — Vector Databases, а сохранение/перенос индекса памяти между машинами — Vector Store Persistence.
  • В multi-agent состояние шарится (общий invocation_state), в single-agent — локальный state.
  • Персистентность состояния для долгоживущих агентов: статус задач пишется на диск (например, SQLite agent_state.db), чтобы агент пережил рестарт и продолжил с чекпоинта (см. Task DAG в Agent Architecture). В LangGraph это встроенный checkpointer (SQLite/Postgres/Redis) с восстановлением сессии по thread_id — см. LangGraph Checkpointers.
  • Bi-temporal memory (двухвременная память): хранить не только факт, но и историю смены убеждений — поле invalidated_reason отвечает на вопрос «почему агент передумал» (выбрал SQLite вместо PostgreSQL). Даёт долгосрочную связность (coherence) и аудируемость.
  • Память как факты, а не тексты (Mem0): отдельный слой (Mem0) извлекает из диалога факты и хранит их с разрешением конфликтов/дедупликацией — в отличие от RAG, который хранит сырой текст. По функции типы памяти раскладывают на working (текущий контекст), episodic (история сессий), semantic (факты о мире/пользователе), procedural (как выполнять задачи: workflow, правила). В multi-agent общий слой памяти становится механизмом координации команды (см. Multi Agent Systems).

Альтернативный взгляд: память как backend-сервис со своим SLA

Память подаётся не как «4 хранилища», а как backend-сервис со своим SLA: latency, ошибки, таймауты, стоимость. Два следствия: (1) не модель решает идти в память — это делает оркестрация по явным правилам (policy); (2) принципиально различаются read (чтение по ключу, детерминированно: «положили — достали ровно то») и retrieve (всегда вопрос вероятности — поиск по смыслу). Операции памяти явные: memory.read / update / write / retrieve. Такой взгляд делает память тестируемой и предсказуемой, в отличие от «модель сама вспомнит».

Три уровня памяти виртуального сотрудника

Факультатив про архитектуру MAS в продакшене раскладывает опыт долгоживущего агента на три архитектурных слоя, и это ортогонально делению на context window / history / long-term выше: там вопрос «где физически лежит», здесь — «какого рода это знание».

  • Эпизодическая память (векторный RAG) — прошлые сессии и диалоги, доступные семантическим поиском: «найди похожие обсуждения по проекту X». Отвечает на вопрос «было ли уже такое».
  • Семантическая память (граф) — жёсткие факты и связи сущностей вида User_102 → Prefers → Python_Async. Отвечает на вопрос «что мы точно знаем». Граф здесь не украшение: связи важнее текста, а по вектору связь не ищется.
  • Процедурная память (системные промпты) — усвоенные правила работы, инструкции, ограничения. Отвечает на вопрос «как мы это делаем».

Практический смысл разделения в том, что уровни требуют разной дисциплины. Эпизодическую можно чистить по времени, семантическую — нельзя, она накапливает истину; процедурная вообще меняется через ревью, а не автоматически.

Жёсткое ограничение поверх всех трёх: в долговременную память нельзя писать персональные данные, ключи и временные переменные — фильтры ставятся детерминированными, до записи в хранилище, а не при чтении (см. DLP for LLM).

Пример

Bi-temporal память: факт не удаляется, а помечается неактуальным (valid_to + invalidated_reason). В контекст модели идёт только query_valid(), но полная история (почему агент «передумал») остаётся для аудита.

class BiTemporalMemory:
    def __init__(self): self.records = []

    def store(self, fact, kind="observation"):
        rec = {"id": uuid.uuid4().hex[:8], "fact": fact, "kind": kind,
               "valid_to": None, "invalidated_reason": None}   # valid_to=None -> актуально
        self.records.append(rec); return rec["id"]

    def invalidate(self, fact_id, reason):                     # «агент передумал»
        for r in self.records:
            if r["id"] == fact_id and r["valid_to"] is None:
                r["valid_to"] = datetime.now().isoformat()
                r["invalidated_reason"] = reason               # почему отменили — для аудита

    def query_valid(self, kind=None):                          # в контекст идёт только актуальное
        return [r for r in self.records
                if r["valid_to"] is None and (kind is None or r["kind"] == kind)]

Summary или pointers

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

Отсюда правило: summary достаточно, когда важен ход разговора, а не детали (кто что решил, к чему пришли); pointers нужны, когда в истории есть точные значения, за которые придётся отвечать — суммы, идентификаторы, формулировки договорённостей. Признак, что выбрали неверно: агент уверенно называет число, которого в текущем контексте нет, — оно пришло из выжимки, где потеряло точность.

На практике их совмещают: свежие шаги целиком, старые — выжимкой, а всё, что помечено как факт, выносится в отдельный слой памяти со ссылкой на источник (Mem0).

Память делает ошибки долговечными

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

Механика на примере: пользователь однажды написал «не тратьте время на уточнения, сразу эскалируйте», агент сохранил это как устойчивое предпочтение — и через две недели, когда ситуация требовала как раз уточнения, уверенно эскалировал. Плохого ответа в моменте не было; была плохая запись двумя неделями раньше.

Отсюда три следствия, разобранные отдельно: путь записи нуждается в политике (Memory Write Policy), слои памяти — в разных правилах и границах (Memory Boundaries), а сама память — в ревью модели угроз, а не только качества данных (Memory Poisoning).

Связано с

  • Memory Write Policy — правила, по которым запись вообще попадает в память
  • Memory Boundaries — три слоя с разными вопросами и разными правилами
  • Context Compaction — фоновое уплотнение вместо роста без правил
  • Agent Anatomy — память как один из 4 компонентов
  • Context Window — самый «горячий» вид памяти
  • RAG — механизм долгосрочной памяти
  • Vector Databases · Vector Store Persistence — где живёт и как переносится long-term память
  • Agent CostControl — память напрямую влияет на бюджет
  • LangGraph Checkpointers — персистентность сессионного состояния по thread_id