Skip to content

Память

То, что агент сохраняет между разговорами, в отличие от окна, которое он читает внутри одного. Этим словом называют две разные вещи, и ведут они себя достаточно по-разному, чтобы их путаница стоила половины дня:

  • Заметки — то, что агент записал о человеке и о работе. Строки, долговечные, подаются в каждый ход.
  • Поиск по разговору — доступ к тому, что было сказано, за горизонтом окна. Это вообще не хранилище: это поиск по хранилищу чата.

Заметки: курируемая страница и датированные записи

Память агента — это markdown, который он написал, а не нарезанные сообщения. Одна курируемая страница — его постоянный отчёт о человеке и о работе — плюс датированные заметки, по одной на день. Любой отдельный элемент ограничен 2 000 знаками; путь записи отвергает более длинный, а не подрезает его молча: заметка, оборванная на середине фразы, хуже отказа, на который агент может отреагировать.

Заметки пишут трое, и четвёртого нет: импорт пакета, собственный инструмент запоминания у агента и куратор — фоновый проход, который перечитывает разговор, когда тот уже ушёл вперёд, и решает, что заслуживает его пережить. Куратор идёт по порогу, а не после каждого хода (примерно каждые четыре обмена или 8 000 знаков разговора), потому что платить за перечитывание каждого хода — это примерно столько же, сколько стоит сам ход.

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

Что получает ход, и единственный потолок над этим

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

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

персона → последний обмен → сводка → заметки → выдержки → более глубокое окно

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

Потолок — 18 000 знаков на весь префикс, это сумма того, что просят слои, поэтому сегодня никому на деле не отказывают. Считается в знаках, а не в токенах: токенайзера здесь нет, а блок едет в каждом ходу — это постоянная цена, а не разовая.

Чтение делается по возможности. Хранилище, которое не ответило, стоит ходу его заметок, но никогда самого хода.

Упорядочивание заметок по смыслу — построено и не запускалось ни разу

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

Векторы живут в infra/vector как L2-нормированные байты float32 с ключом-пространством имён, которое несёт команду, поэтому заметки одного арендатора даже не сравнимы с чужими. Вектор засчитывается только при совпадении модели эмбеддингов и ширины; вектор, разошедшийся хоть в одном из двух, — не слабое совпадение, а невидимка.

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

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

Из наличия индекса следуют две обязанности, и обе обеспечены механически, а не памятью:

  • Всё, что пишет заметку, обязано её переиндексировать. Писатель, который забыл, оставляет заметку, которая есть в базе и которую нельзя вспомнить никогда, — худший вид поломки, потому что ничего не падает. Проверка отвергает нового писателя, который этого не делает.
  • Удаление заметки убирает её вектор. Иначе человек удаляет заметку, экран соглашается, что её больше нет, а поиск продолжает её возвращать — то есть агент помнит то, что ему велели забыть.

Поиск по разговору

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

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

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

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

Память vs знание

  • Память = то, что этот агент знает о человеке и о работе (эта страница) → agent/memory.
  • Знание = каталог и документы магазина, залитые в LightRAG → agent/knowledge.

См. также