Глава 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. С чего начать, если памяти у тебя пока нет¶
Если ты только подходишь к этой теме, хороший порядок такой:
- Сначала раздели session context и persistent memory.
- Потом определи типы записей, которые вообще допустимы.
- После этого добавь provenance и tenant metadata.
- И только потом автоматизируй write path.
Если сделать наоборот, память очень быстро станет местом, куда платформа складывает все, что не смогла аккуратно спроектировать.
10. Что читать дальше¶
В следующих главах этой части мы спокойно разберем:
- чем short-term memory отличается от long-term;
- зачем нужен profile memory отдельно от retrieval store;
- почему summaries стоит обновлять в background;
- как compaction помогает держать контекст чистым.
А пока главное takeaway простое: память полезна только тогда, когда она проектируется как управляемый слой системы, а не как бесконтрольное накопление текста.