Перейти к содержанию

Глава 5. Зачем агенту память и почему она опасна

1. Почему без памяти агент быстро упирается в потолок

Если у агента нет памяти, каждый новый run начинается почти как с чистого листа. Это терпимо для простых задач, но быстро ломается в реальной жизни:

  • агент не помнит предпочтения пользователя;
  • забывает, что уже делал минуту назад;
  • каждый раз заново тянет одни и те же факты;
  • не умеет аккуратно продолжать длинный процесс.

Поэтому память кажется естественным следующим шагом. И это правда так. Но здесь есть важная развилка: память может сделать агента полезнее, а может сделать систему намного менее предсказуемой.

2. Память это не одна коробка, а несколько разных слоев

Когда команда говорит “добавим память”, обычно смешиваются сразу несколько разных вещей:

  • краткоживущий рабочий контекст run;
  • user profile и устойчивые предпочтения;
  • извлеченные факты о мире и бизнес-сущностях;
  • summaries прошлых сессий;
  • артефакты выполнения вроде trace notes или tool outputs.

Если все это складывается в одно место, очень быстро начинается хаос. Поэтому первое правило простое: не проектируй memory как один абстрактный storage. Проектируй ее как набор разных контуров с разным временем жизни, уровнем доверия и правилами записи.

Память агента удобнее мыслить как несколько слоев, а не как одну базу

flowchart TD
    A["User request"] --> B["Session context"]
    B --> C["Planner / runtime"]
    C --> D["Short-term working memory"]
    C --> E["Profile memory"]
    C --> F["Knowledge retrieval"]
    D --> G["Prompt assembly"]
    E --> G
    F --> G
    G --> H["Model response"]

3. Главная ошибка: считать память просто удобством

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

Если агент один раз:

  • сохранил ложный факт как “предпочтение пользователя”;
  • записал в profile memory кусок недоверенного текста;
  • утащил в summary чувствительный фрагмент документа;
  • положил в retrieval store то, что не должно возвращаться этому tenant;

то проблема становится persistent. Ее уже не всегда видно по одному trace. Она начинает всплывать позже, в других диалогах, в других промптах и иногда у других пользователей.

Именно поэтому memory write path должен рассматриваться как security-sensitive path, а не как “техническая мелочь”.

4. У памяти есть собственные trust boundaries

Очень полезно смотреть на память не как на нейтральное хранилище, а как на границу доверия.

Есть как минимум четыре разных источника данных:

  • trusted system annotations;
  • validated outputs внутренних сервисов;
  • user-provided content;
  • content, пришедший из внешних инструментов или документов.

Это не одно и то же. Если ты сохраняешь все это без маркировки происхождения, позже runtime не сможет понять, что можно использовать как instruction-grade context, а что можно использовать только как reference.

Нормальное правило выглядит так:

  • trusted metadata может попадать в policy decisions;
  • user content не должен внезапно становиться system instruction;
  • retrieved text должен считаться untrusted, пока не доказано обратное;
  • summaries тоже имеют provenance и не являются “истиной по умолчанию”.

5. Самый опасный путь: писать память прямо в hot path без фильтра

Команды часто делают так: модель ответила, рантайм тут же вызывает save_memory(), и дальше это считается удобной автоматикой. На короткой дистанции это выглядит красиво. На длинной почти всегда появляются проблемы.

Почему это опасно:

  • запись происходит под давлением latency;
  • никто не валидирует, что именно считается memory-worthy;
  • нет шага нормализации и удаления лишнего;
  • нет политики по tenant isolation;
  • сложно объяснить, почему факт вообще оказался в памяти.

Поэтому даже для очень “умных” агентов полезно придерживаться скучного принципа: запись в долговременную память по умолчанию должна быть либо явно разрешена policy, либо вынесена в background pipeline.

Ниже простой пример такой логики:

from dataclasses import dataclass


@dataclass
class MemoryCandidate:
    kind: str
    tenant_id: str
    content: str
    source: str
    contains_pii: bool = False


def should_persist(candidate: MemoryCandidate) -> bool:
    if candidate.kind not in {"profile_preference", "validated_fact", "session_summary"}:
        return False
    if candidate.source not in {"trusted_service", "approved_summarizer"}:
        return False
    if candidate.contains_pii:
        return False
    return True

Этот код намеренно очень простой. Его сила не в “умности”, а в том, что правила записи видны и поддаются аудиту.

6. Хорошая memory system пишет меньше, чем тебе хочется

В начале почти всем кажется, что памяти должно быть много. Но на практике хорошая memory system чаще выигрывает не количеством, а строгостью отбора.

Обычно в память стоит писать только то, что:

  • пригодится в будущих runs;
  • имеет понятный owner и tenant;
  • можно объяснить человеку;
  • не несет лишних чувствительных данных;
  • не превратит prompt в свалку.

Очень полезный вопрос перед каждой записью: “Если этот фрагмент всплывет через три недели в другом контексте, мне будет комфортно объяснить, почему он здесь?”

Если ответ неуверенный, запись, скорее всего, не нужна.

7. Память влияет не только на качество, но и на безопасность

Вот почему тема памяти не сводится к UX:

  • она влияет на утечки данных;
  • она меняет поверхность prompt injection;
  • она создает долговременные ошибки;
  • она осложняет расследование инцидентов;
  • она повышает требования к provenance, retention и deletion.

В хорошо устроенной платформе memory subsystem обычно имеет:

  • отдельные типы записей;
  • retention rules;
  • clear ownership;
  • provenance metadata;
  • механизм удаления и исправления;
  • policy gates перед persistent write.

Это уже не “chat history”. Это полноценный слой архитектуры.

8. Минимальная политика для записи в память

Если хочешь начать без переусложнения, можно мыслить о memory write policy примерно так:

memory:
  allowed_kinds:
    - profile_preference
    - validated_fact
    - session_summary
  deny_sources:
    - raw_user_prompt
    - external_html
    - unvalidated_tool_output
  require_tenant_id: true
  reject_if_contains:
    - secrets
    - access_tokens
    - payment_card_data
  write_mode:
    profile_preference: background_review
    validated_fact: immediate_if_trusted
    session_summary: background_only

Опять же, это не про магию. Это про то, чтобы сделать путь записи обозримым и безопасным.

9. С чего начать, если памяти у тебя пока нет

Если ты только подходишь к этой теме, хороший порядок такой:

  1. Сначала раздели session context и persistent memory.
  2. Потом определи типы записей, которые вообще допустимы.
  3. После этого добавь provenance и tenant metadata.
  4. И только потом автоматизируй write path.

Если сделать наоборот, память очень быстро станет местом, куда платформа складывает все, что не смогла аккуратно спроектировать.

10. Что читать дальше

В следующих главах этой части мы спокойно разберем:

  • чем short-term memory отличается от long-term;
  • зачем нужен profile memory отдельно от retrieval store;
  • почему summaries стоит обновлять в background;
  • как compaction помогает держать контекст чистым.

А пока главное takeaway простое: память полезна только тогда, когда она проектируется как управляемый слой системы, а не как бесконтрольное накопление текста.