Глава 5. Зачем агенту память и почему она опасна¶
1. Начнем с ошибки, которая переживает сам запрос¶
Продолжим тот же кейс поддержки из первых глав.
Пользователь однажды пишет:
Если доступ все еще не активирован, просто создайте срочный тикет. Не тратьте время на уточнения.
Агент обрабатывает письмо, создает тикет и заодно сохраняет эту фразу как устойчивое предпочтение пользователя.
Через две недели приходит уже другой запрос:
Доступ частично работает, но часть ролей пропала. Проверьте статус и подскажите, что именно сломано.
В этой ситуации правильнее сначала проверить детали, а не сразу эскалировать кейс. Но агент вытаскивает старую запись из профильной памяти и уверенно создает срочный тикет без нужного уточнения.
Проблема здесь не в одном плохом ответе. Проблема в другом:
- старая запись пережила исходный запуск;
- ошибка превратилась в устойчивое поведение;
- команда уже не сразу понимает, откуда вообще взялось это решение;
- последствия всплывают позже и в другом контексте.
Вот это и есть главный сдвиг, который приносит память: она делает ошибки долговечными.
2. Почему без памяти агент все равно быстро упирается в потолок¶
При этом память действительно нужна.
Без нее тот же агент поддержки быстро начинает раздражать и пользователей, и команду:
- каждый раз заново спрашивает уже известные детали;
- не помнит, что минуту назад уже проверял статус заявки;
- плохо продолжает прерванный процесс;
- повторно тянет одни и те же факты и увеличивает стоимость запуска.
Развилка здесь не в духе "память нужна или нет". Развилка другая:
- либо память делает систему полезнее и аккуратнее;
- либо память превращает ее в менее предсказуемую, менее безопасную и более дорогую в сопровождении систему.
3. Память это не один ящик, а несколько разных слоев состояния¶
Когда команда говорит "добавим память", обычно смешиваются сразу несколько разных вещей:
- краткоживущий контекст текущего запуска;
- контекст сессии, который нужен только в пределах одной сессии;
- профильная память с устойчивыми предпочтениями;
- проверенные факты о пользователе или бизнес-сущности;
- сводки прошлых сессий;
- артефакты исполнения вроде результатов инструментов или заметок трассы.
Если все это складывается в одно место, хаос начинается очень быстро. Поэтому первое правило простое: не проектируй память как одно абстрактное хранилище. Проектируй ее как набор разных контуров с разным временем жизни, разным владельцем и разными правилами записи.
Память агента полезнее мыслить как несколько слоев состояния, а не как одну базу
flowchart TD
A["Запрос пользователя"] --> B["Контекст сессии"]
B --> C["Планировщик / рантайм"]
C --> D["Краткоживущая рабочая память"]
C --> E["Профильная память"]
C --> F["Извлечение знаний"]
D --> G["Сборка подсказки"]
E --> G
F --> G
G --> H["Ответ модели"] 4. Главная ошибка: считать память просто удобством¶
У памяти есть неприятное свойство: она переживает отдельный запуск. А значит, ошибка в записи живет дольше, чем ошибка в одном ответе модели.
Если агент однажды:
- сохранил ложный факт как "предпочтение пользователя";
- записал в профильную память фразу из сырого пользовательского письма;
- утащил в сводку чувствительный внутренний комментарий;
- положил в хранилище извлечения данные, которые не должны возвращаться этому арендатору;
то проблема становится устойчивой. Ее уже не всегда видно по одной трассе. Она начинает всплывать позже, в других диалогах, в других подсказках и иногда у других пользователей.
Именно поэтому путь записи в память нужно рассматривать как чувствительный путь записи, а не как удобную автоматику.
5. У памяти есть собственные границы доверия¶
Очень полезно смотреть на память не как на нейтральное хранилище, а как на границу доверия.
Для того же агента поддержки есть как минимум четыре разных источника данных:
- доверенные системные аннотации;
- проверенные результаты внутренних сервисов;
- контент, предоставленный пользователем;
- контент из внешних инструментов, документов или писем.
Это не одно и то же. Если сохранять все это без маркировки происхождения, позже рантайм не сможет понять, что можно использовать как контекст уровня инструкций, а что можно использовать только как справочный материал.
Нормальное правило выглядит так:
- доверенные метаданные могут участвовать в решениях политики;
- пользовательский контент не должен внезапно становиться системной инструкцией;
- извлеченный текст должен считаться недоверенным, пока не доказано обратное;
- сводки тоже имеют происхождение и не являются "истиной по умолчанию".
6. Самый опасный путь: писать долговременную память прямо в горячем пути¶
Команды часто делают так: модель ответила, рантайм тут же вызывает save_memory(), и дальше это считается удобной автоматикой. На короткой дистанции это выглядит красиво. Потом почти всегда появляются проблемы.
Для того же агента поддержки это особенно опасно:
- случайная фраза пользователя становится "устойчивым предпочтением";
- фрагмент результата инструмента попадает в профильную память без проверки;
- чувствительный фрагмент письма переживает исходный запрос;
- одна плохая запись потом участвует в десятках следующих ответов.
Почему этот путь опасен системно:
- запись происходит под давлением задержки;
- никто не валидирует, что именно считается достойным сохранения в памяти;
- нет шага нормализации и удаления лишнего;
- нет отдельной политики изоляции арендаторов;
- сложно объяснить, почему факт вообще оказался в памяти.
Поэтому даже для очень умных агентов полезно придерживаться скучного принципа: запись в долговременную память по умолчанию должна быть либо явно разрешена политикой, либо вынесена в фоновый конвейер.
Ниже простой пример такой логики:
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
Этот код намеренно очень простой. Его сила не в "умности", а в том, что правила записи видны и поддаются аудиту.
7. Хорошая система памяти пишет меньше, чем тебе хочется¶
В начале почти всем кажется, что памяти должно быть много. Но на практике хорошая система памяти чаще выигрывает не количеством, а строгостью отбора.
Обычно в память стоит писать только то, что:
- пригодится в будущих запусках;
- имеет понятного владельца и арендатора;
- можно объяснить человеку;
- не несет лишних чувствительных данных;
- не превратит подсказку в свалку.
Полезный вопрос перед каждой записью звучит так:
Если этот фрагмент всплывет через три недели в другом контексте, мне будет комфортно объяснить, почему он здесь?
Если ответ неуверенный, запись, скорее всего, не нужна.
8. Минимальная политика для записи в память¶
Если хочешь начать без переусложнения, можно мыслить о политике записи в память примерно так:
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
Это не про магию. Это про то, чтобы сделать путь записи обозримым и безопасным.
8.1. Полезно разделять политику чтения памяти и политику записи в память¶
Одна из самых практичных мыслей из свежих Google-материалов состоит в том, что память надо мыслить как управляемую подсистему, а не как "просто хранилище для контекста".12
У этого есть прямое следствие: правила чтения и правила записи почти никогда не должны быть одинаковыми.
Например:
- запись в долговременную память может требовать проверки, происхождения и фонового разбора;
- чтение из долговременной памяти может быть разрешено только через фильтры извлечения;
- запись в профильную память может требовать явного сигнала или высокой уверенности;
- чтение профильной памяти может быть разрешено только слою персонализации, но не движку политик.
Если не развести эти пути, система начинает жить по странной логике: все, что однажды записалось, потом можно почти автоматически читать отовсюду.
А это уже не проектирование памяти, а источник тихих инцидентов.
8.2. Устойчивая память должна по умолчанию хранить происхождение¶
Для каждой записи, которая живет дольше одного запуска, полезно по умолчанию хранить хотя бы:
source_type;source_id;writer_identity;tenant_id;written_at;confidenceилиvalidation_state.
Это выглядит как дополнительная бюрократия ровно до первого спора о том, откуда взялся "факт", который агент потом уверенно повторил в другом контексте.
9. Практические правила для проектирования памяти¶
Если нужен короткий каркас для первых решений, он обычно выглядит так:
- Сначала разделяй контекст сессии и устойчивую память, а потом уже спорь о более богатых возможностях памяти.
- Лучше писать меньше, но понятнее: проверенные факты почти всегда ценнее сырого текста.
- Правила записи должны быть жестче правил чтения.
- Каждая долговременная запись должна нести происхождение, метаданные арендатора и идентичность записавшего компонента.
- Если запись может пережить запуск, по умолчанию безопаснее вынести ее в фоновый путь.
10. Что команды чаще всего делают неправильно¶
Почти всегда повторяются одни и те же ошибки:
- сохраняют сырые пользовательские формулировки как устойчивые предпочтения;
- смешивают профильную память, хранилище извлечения и артефакты исполнения;
- позволяют сводкам жить без происхождения и состояния проверки;
- используют непроверенную память в решениях политики;
- долго не проектируют удаление, пересмотр и устаревание записей.
11. Что эксплуатационная команда должна уметь быстро ответить¶
Для того же кейса поддержки после странного поведения памяти команда должна быстро понимать:
- какая запись попала в профильную память;
- откуда она взялась;
- кто ее записал;
- проходила ли она проверку;
- почему она была разрешена для этого арендатора;
- в каких следующих запусках она уже успела повлиять на ответ.
Если на это нельзя ответить, подсистема памяти уже стала источником системного риска.
12. Что делать сразу после этой главы¶
Если ты только подходишь к проектированию памяти, начни с очень короткой последовательности:
- Раздели контекст сессии и устойчивую память.
- Определи типы записей, которые вообще допустимы.
- Добавь происхождение и метаданные арендатора.
- И только потом автоматизируй путь записи.
Если сделать наоборот, память очень быстро станет местом, куда платформа складывает все, что не смогла аккуратно спроектировать.
13. Что читать дальше¶
В следующих главах этой части мы спокойно разберем:
- чем краткосрочная память отличается от долгосрочной;
- зачем профильная память нужна отдельно от хранилища извлечения;
- почему сводки стоит обновлять в фоне;
- как сжатие контекста помогает держать контекст чистым.
Для нашего кейса поддержки это и есть следующий шаг: научиться различать рабочий контекст, профиль пользователя и долгоживущую память так, чтобы потом слой исполнения не действовал на грязном состоянии.
А пока главный вывод простой: память полезна только тогда, когда она проектируется как управляемый слой системы, а не как бесконтрольное накопление текста.