MarathonMemBench — бенчмарк нового типа для тестирования памяти вашего LLM-агента

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Эта статья — про долговременную память AI-агентов и про то, как её измерить. Я сделал MarathonMemBench — платформу, где LLM-персона часами беседует с тестируемым агентом: рассказывает факты о своей жизни, меняет решения, путается и поправляется, а под конец — когда начало разговора давно вытеснено из контекста — устраивает экзамен на всё сказанное. Подключить можно любого агента, у которого есть чат, — больше от него ничего не требуется. Дальше будут: обзор существующих бенчмарков памяти и почему каждый из них требует дорабатывать агента под себя, устройство платформы, результаты open-source агентов — неожиданно сильные — и вскрытие их памяти после прогонов. Но начать придётся с корня проблемы — с конечности контекста языковых моделей.

Кажется будущее из фильма «Она» уже наступило
Кажется будущее из фильма «Она» уже наступило

У этой статьи есть предыстория. В октябре я писал о гипотезе: достаточно умная языковая модель сможет решить проблему долговременной памяти сама — обычными вызовами инструментов. Тогда это было не более чем предположение. Но ровно через месяц вышла Opus 4.5, а ещё через месяц Andrej Karpathy написал, что агентные способности моделей перешли порог в агентском кодинге и он, программист с двадцатилетним стажем, «никогда не чувствовал себя настолько отставшим». Эта статья — про другую способность, о которой говорят намного реже: самостоятельно вести долговременную память. И, как будет видно по цифрам, здесь всё оказалось неожиданно хорошо ровно в той же степени.

Конечность контекста — фундаментальное ограничение языковых моделей

Если задуматься, вся история триумфа языковых моделей — это эксплуатация одного неожиданного открытия: архитектура трансформера для предсказания следующего токена рождает интеллект, выраженный в словах. Всё, что мы видим последние годы, весь этот триллион инвестиций, — по сути развитие одной этой идеи. Модели становятся больше, обучающих данных — больше; придумали SFT — дообучение на примерах образцовых диалогов, потом RLHF — обучение с подкреплением на человеческих оценках ответов, совсем недавно — reasoning, когда модель учат сначала «подумать» и только потом отвечать. Но ничего из этого не выходит за рамки обучения предсказанию следующего токена на трансформере.

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

«А почему конечность контекста — это проблема?» — скажет кто-то. Я ведь наоборот: переключаюсь на новую сессию, начинаю новую задачу и спокойно её делаю. И мне даже хорошо, что новая сессия ничего не помнит про старую.

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

Но есть и другие задачи, где хотелось бы, чтобы модель ничего не забывала. Представим себе персонального помощника - работать с ним отдельными сессиями уже неудобно. Хотелось бы, чтобы он помнил всё, о чём шла речь: чтобы можно было сказать «а помнишь, три недели назад мы… — так вот, давай…». То есть ждать от модели поведения человека с блокнотом в руках, который по ходу записывает и в любой момент может к записанному вернуться.

LLM + харнес = LLM с бесконечным контекстом

Если архитектура сегодня не способна решить эту проблему — как же к ней подступиться? А решить её на самом деле можно. В последнее время огромную популярность получили агенты на базе LLM. Упрощённо говоря, агент — это LLM, которая генерирует не просто текст, а инструкции, которые должны быть выполнены «снаружи».

Исполняет эти инструкции харнес. Мне недавно попалось удачное определение: харнес — это управляющая инженерная среда вокруг модели, которая превращает вероятностный искусственный интеллект в повторяемого программного исполнителя. Практически это набор инструментов плюс операции вокруг цикла модели, которые принято называть хуками. Отсюда возникает идея: почему бы харнесу не взять на себя и проблему конечности контекста? Тем или иным способом обрезать контекст (компактизация, суммаризация), параллельно экстрактить из него информацию в долгосрочную память — и уметь эту информацию из памяти доставать.

И это уже сейчас работает. Известные open-source-агенты — например, OpenClaw или Hermes — прямо из коробки умеют складывать информацию из общения с пользователем в папку с Markdown-файлами. И пользоваться этой информацией при необходимости. Также эти агенты умеют компактизировать длинную беседу, так что теоретически она может длиться бесконечно (правда, у OpenClaw на момент написания статьи компактизация работала с ошибками).

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

Существующие бенчмарки памяти — и почему ни один мне не подошёл

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

Off-policy и on-policy бенчмарки

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

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

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

  • Ответы ассистента в этих беседах не являются ответами нашего агента и будут для него, скажем так, чужеродными.

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

On-policy — вместо заранее предопределённых бесед бенчмарк с LLM под капотом ведёт реальную беседу с тестируемым агентом. Соответственно, не нужно специального способа загрузки беседы, и все ответы ассистента — настоящие ответы агента. Теоретически этот подход позволяет тестировать агента исключительно через чат-интерфейс; на практике ни один существующий бенчмарк этого не делает (см. ниже).

Из минусов — заметно большая длительность и стоимость прогонов.

Группа первая: off-policy

Всем нужен способ вложить в агента чужую историю. Различаются они тем, куда именно и как её вкладывают.

Старейший и самый цитируемый — LoCoMo: заранее сгенерированные очень длинные беседы (в среднем ~300 реплик, до 35 сессий) плюс заготовленные вопросы с референсными ответами. Чтобы его запустить, агент должен уметь проглотить чужую стенограмму целиком и предоставить отдельный вход, через который потом задаются вопросы. Записанные беседы там вообще не user-assistant, а диалоги двух синтетических персонажей между собой — то есть агенту предлагается «вспоминать» разговор, в котором он не участвовал даже формально.

LongMemEval — нынешний канон жанра: 500 вопросов, встроенных в длинные user-assistant истории (варианты примерно на 115K и 1.5M токенов). Схема та же: стенограмма грузится одним куском, вопросы задаются через отдельный интерфейс. Ни того, ни другого у продукта с обычным чатом нет.

Остальные — вариации на ту же тему: MemoryAgentBench подаёт чужую историю инкрементально, чанками по 512–4096 токенов («inject once, query multiple times»), но специальный вход для загрузки всё равно нужен; BEAM — то же, только стенограммы до 10 миллионов токенов; SubtleMemory «проигрывает» готовые истории через механизм формирования памяти агента, нарезая их под гранулярность конкретной системы; а ClawArena и вовсе пишет историю прямо в session store продукта — .jsonl-файлы сессий, метаданные, агентские файлы в workspace.

Для обычного агента, у которого есть только чат, эта группа отпадает целиком и сразу.

Группа вторая: on-policy

Здесь чужую переписку не грузят — с агентом разговаривают. Казалось бы, то, что нужно. Но каждый бенчмарк требует чего-то своего на этапе тестирования.

Ближе всех подходит AMemGym — он единственный прямо декларирует on-policy-оценку для чат-ассистентов: их LLM-симулятор пользователя ведёт с агентом настоящую беседу. Но проблема — в проверке. Проверочные вопросы (в базовой конфигурации — 200 вопросов на 11 контрольных точках) задаются не в чате, а в обход него: состояние агента нужно уметь «заморозить» и отвечать по замороженной копии — так, чтобы ни вопросы, ни ответы не попали в память и не испортили дальнейшую беседу. Я пробовал его запускать: без снапшота состояния агента и встраивания в их Python-обвязку он не работает.

MEMPROBE идёт с другой стороны: беседа честная, зато проверяется не поведение агента, а содержимое его хранилища. Для этого агент обязан предоставить программный доступ к памяти: get_all_memories() для полного дампа и search(query, k) для чтения top-k записей. По сути, это аудит внутренностей, а не тест агента.

MemoryArena даёт агенту прожить свою историю самому — но исключительно внутри их окружений (веб-навигация, планирование, поиск, формальные задачи). Своё окружение принести нельзя, а значит, и своего агента «как есть» — тоже.

Остальные — в том же духе: STATE-Bench устроен по принципу «bring your own memory» — агент, инструменты и симулятор пользователя их, от вас только memory-модуль, подключаемый через их retrieval hook; MemGym требует физически отделить память от reasoning-модели и подключить обе через их общий интерфейс; а в ClawMark агент работает внутри их пяти stateful-сервисов (файлы, почта, календарь, база знаний, таблицы), и результат проверяют 1537 детерминированных чекеров по итоговому состоянию этих сервисов.

Что с ними со всеми не так

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

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

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

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

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

Мой MarathonMemBench

Сразу нужно сказать: MarathonMemBench — это не просто бенчмарк, а платформа для создания и проигрывания бенчмарк-сценариев. Движок и тестовые прогоны конфигурируются файлом config.jsonc; прогоны могут быть длинными, поэтому их можно запускать несколько параллельно. А бенчмарк — это папка с файлом benchmark.json и несколькими markdown файлами.

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

Агент персоны

В основе платформы — отдельный LLM-агент, симулирующий пользователя-персону: живого человека со своим бэкграундом и текущими делами. Персона ведёт разговор с агентом: рассказывает, обсуждает, переспрашивает, меняет решения, оценивает ответы. Личность и сюжет целиком заданы данными бенчмарка. Проще всего понять, как работает агент персоны, — прочитать его системный промпт src/system.md и набор инструментов в src/tools.

Сам системный промпт универсален — персоны в нём нет. Подогнать его под конкретный сценарий, не меняя сам промпт, позволяет файл бенчмарка instructions.md: его содержимое инжектируется в конец системного промпта, в раздел Your scenario, и содержит общую информацию о персоне. Например, что весь разговор укладывается в один свободный день, а пишет персона по-деловому, короткими сообщениями и любит точные цифры.

Типовой сценарий состоит из двух частей. Сначала персона выгружает на агента множество фактов, меняет темы, меняет значение этих фактов, а потом через некоторое время начинает задавать вопросы по тому, что говорила ранее.

Сценарий = последовательность фаз

Сценарий, которому следует агент персоны, состоит из последовательности фаз. Фаза — это просто текстовое описание того, о чём персона должна рассказать агенту. Например:

  • на работе тебя повысили до senior-дизайнера, зарплата выросла до 4000 евро.

  • сегодня отдых от бега. К тебе приехала погостить на неделю младшая сестра Аня.

Фазы объединяются в главы. Глава — это один markdown-файл, где каждый ##-раздел — одна фаза. Главы перечисляются в поле chapters в benchmark.json:

{
    "chapters": [
        "chapter1.md",
        "chapter2.md",
        ...
    ]
    ...
}

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

Файлы с фактами

Но просто перечислять факты в фазах оказалось неэффективно — слишком много текста. А мы хотим вывалить на агента огромное число фактов на разные темы. Для этого я придумал специальный сценарий для агента персоны — рассказать всё из отдельного файла с фактами: «Скажи, что теперь расскажешь про семью — расскажи факты из family», где family.md:

# Семья

## 1. Жена Лена
1.1 Лене 31 год, UX-дизайнер, работает удалённо
1.2 день рождения Лены — 14 марта
...

Такой формат записи фактов намного компактнее, чем писать в фазе «а теперь расскажи о …». Агент персоны загружает файл с фактами инструментом facts_read и начинает в живом диалоге рассказывать факты из списка, выбирая адекватную моменту последовательность изложения и отвечая на встречные вопросы агента.

Чтобы агент персоны гарантированно не забыл рассказать какой-то факт из списка, у него есть ещё два инструмента: facts_mark_told — вызывается с id факта сразу после того, как факт рассказан, — и facts_get_untold — возвращает список ещё не рассказанных фактов.

JS-блок в фазе

В начале фазы также можно добавить специальный блок JavaScript-кода, который выполняет некоторые действия:

vars.require_all_facts = "family";

Например, конструкция выше заставит next_phase возвращать ошибку, пока агент персоны не расскажет все факты из файла family.md.

Профиль персоны

В каждом бенчмарке предусмотрен специальный файл с фактами profile.md — главная информация о персоне. Пример содержимого:

# Сергей

## 1. О себе
1.1 Сергей Волков, 34 года
1.2 Бэкенд-разработчик, работаю удалённо на европейскую компанию
1.3 Год назад переехал в Батуми из Питера
1.4 Живём с женой Леной, есть кот Босс

## 2. Квартира и ремонт
2.1 Купил двухкомнатную квартиру без отделки, 65 м², 9-й этаж, район Новый бульвар
...

Особенность этого файла заключается в том, что он всё время присутствует в контексте агента персоны (как CLAUDE.md в Claude Code). Это позволяет персоне не терять свою идентичность и в моменты, когда она бурно рассказывает про ремонт квартиры, не забывать о том, кто она такая.

Компактизация контекста агента персоны

Агент персоны спроектирован так, чтобы диалог мог идти бесконечно. Компактизация контекста конфигурируется в файле config.jsonc:

    "compaction": {
        "max_tokens": 30000,
        "trim_to_tokens": 10000
    },

После того как размер контекста отрастает до max_tokens, история просто обрезается — сохраняются последние сообщения общим объёмом trim_to_tokens токенов. Если быть точнее, обрезание всегда происходит по границе фазы. Также в контекст добавляется сообщение с содержимым profile.md и списком всех файлов с фактами, которые загружал агент персоны.

Актуализация фактов

Факты могут меняться — например, персона поменяла работу или получила повышение. Чтобы после компактизации профиль персоны и другие файлы с фактами оставались актуальными, агент персоны должен их обновлять — для этого у него есть два инструмента: facts_edit и facts_add. Удалить факт тоже можно, записав в него , — а его id в дальнейшем можно переиспользовать. Пример сценария обновления факта: 10 км за 52:30, новый личный рекорд! Обнови факт в профиле.

Тестирование агента

Чтобы добавить в сценарий проверку, нужно добавить в фазу строчку вида: ❓ 57.1 Сколько всего километров я пробежала с 1 июня? Правильный ответ: 279 км. Встретив такую строку, агент персоны задаст вопрос агенту и оценит его ответ при помощи специального инструмента log_evaluation. Результат оценивается по шкале 0–10. При этом если ожидаемый ответ — одно число, возможны только две оценки: 0 и 10. А если персона просит агента сообщить несколько фактов, оценка будет пропорциональна числу верных ответов.

Агенту иногда свойственно отвечать, что он чего-то не знает, хотя эта информация есть у него в постоянной памяти. Чтобы адекватно оценить такой кейс, персона следует простой процедуре: если агент отвечает что-то вроде «ты мне не говорил», она просит его поискать — и если со второй попытки ответ правильный, результат засчитывается с коэффициентом 0.5.

Компактизация/переключение сессии на стороне тестируемого агента

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

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

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

Чтобы переключить сессию на тестируемом агенте, нужно добавить в фазу вот такой блок кода:

await bench.newSession();

Состояние и результаты прогона

Каждый прогон получает свою папку results/<id>/, где id — имя прогона из config.jsonc. Главное в ней — файл state.json: полное текущее состояние прогона — на каком месте сценария находится персона, история её сообщений, все выставленные оценки и живые копии всех файлов с фактами (правки меняют именно их — исходные markdown-файлы бенчмарка остаются нетронутыми). Состояние сохраняется при каждом изменении, поэтому прогон можно в любой момент остановить по Ctrl-C и запустить снова — он продолжится с того же места. А чтобы начать прогон с нуля, достаточно удалить его папку.

Итоги собираются в общий журнал results/report.md: по завершении прогона — даже упавшего — туда дописывается запись: средний балл, распределение оценок, аудит покрытия (все ли запланированные проверки получили оценку), время прогона, наблюдения о компактизациях агента. А самое полезное — каждый неидеальный ответ приводится целиком: вопрос, ожидаемый ответ, ответ агента и комментарий оценщика. Вот реальная запись одного из прогонов OpenClaw:

## 2026-07-26 16:10 — openclaw-1: max @ openclaw
						
Источник: https://habr.com/ru/articles/1066974/


Интересные статьи

Интересные статьи

Под катом делюсь обзором своего самописного PHP-фреймворка Gy — попытки сделать легковесного «убийцу» Битрикса весом 350 Кб. Расскажу, как я реализовал вызов компонентов, зачем написал кастомный SQL-д...
Привет! Меня зовут Максим Гарбарт, я тестировщик компании Битрикс24. Моя работа связана с проверкой одного из важнейших компонентов системы – сервиса авторизации. Для пользователей это, как правило, л...
C++ ISO Standards Group, организация, отвечающая за стандартизацию языка C++, так же известная как WG21, исключила из своих рядов longtime-контрибьютора после того, как тот использовал простое слово "...
Создадим прототип SPA в Bitrix без использования популярных JS фреймворков и библиотек, все будет сделано средставами Bitrix.И самое главное без потери серверного рендеринга и SEO страниц.ПогналиВсего...
В блоге Telegram рассказали о релизе новой версии мессенджера. Разработчики добавили встроенный браузер, мини-магазин приложений и реализовали механику дарения «Звёзд».