Skip to main content

Долговременная память бота: подробная схема

Это расширенная статья для тех, кто хочет понять внутреннюю механику памяти в Nanobot подробнее. Если вам нужно простое пользовательское объяснение без технических деталей, начните со статьи Память бота: простое объяснение. Этот документ описывает реальную текущую механику памяти в Nanobot. Важно понимать главное:
  • у бота есть не одна память, а несколько разных слоёв;
  • часть данных хранится как сжатые знания;
  • часть хранится как поисковые заметки;
  • часть хранится как точная история завершённых сессий;
  • эти слои решают разные задачи и не заменяют друг друга.
Именно поэтому вопрос «помнит ли бот прошлый разговор?» на практике распадается на несколько разных вопросов:
  • помнит ли он устойчивые факты о пользователе и проекте;
  • может ли он найти старую заметку по смыслу;
  • может ли он восстановить точный текст прошлой сессии;
  • может ли он связать summary-файл с конкретным JSONL-транскриптом.
Сейчас ответ на все четыре вопроса: да, но через разные механизмы.

Коротко

В Nanobot есть 4 основных слоя памяти:
  1. memory/MEMORY.md Это главная долговременная память с важными устойчивыми фактами.
  2. memory/YYYY-MM-DD.md Это промежуточные «сбросы памяти» перед сжатием длинного контекста.
  3. memory/YYYY-MM-DD-HHMM-slug.md Это summary завершённой сессии, создаваемое при /new.
  4. sessions/archive/*.jsonl + sessions/_sessions.jsonl Это точная архивная история завершённых сессий и индекс для доступа к ним.
Отдельно есть аварийный fallback:
  • memory/YYYY-MM-DD-raw-archive.md Это страховочный raw archive, который используется, если консолидация в MEMORY.md несколько раз подряд не смогла корректно завершиться через LLM tool.
Если очень упростить:
  • MEMORY.md отвечает на вопрос «что важно помнить всегда»;
  • dated memory files отвечают на вопрос «какие были важные разговоры и выводы»;
  • archived session JSONL отвечает на вопрос «что именно было сказано слово в слово»;
  • _sessions.jsonl отвечает на вопрос «как найти нужную завершённую сессию».

Главная идея

Nanobot не пытается бесконечно держать весь текст всех разговоров в активном prompt. Вместо этого он:
  • держит текущую живую сессию в sessions/<live>.jsonl;
  • по мере роста диалога извлекает важные знания в долговременную память;
  • при завершении сессии создаёт человекочитаемую summary-память;
  • отдельно архивирует точный transcript с уникальным session_id;
  • даёт инструменты для поиска по памяти и для точного восстановления истории.
Это дешевле и надёжнее, чем пытаться каждый раз скармливать модели всю старую переписку.

Какие типы данных бот хранит

1. Устойчивые знания

Обычно сюда попадают:
  • предпочтения пользователя;
  • язык ответов;
  • формат общения;
  • договорённости по проекту;
  • архитектурные решения;
  • важные ограничения;
  • приоритеты;
  • роли участников;
  • факты, которые будут полезны через много дней или недель.
Примеры:
  • «Пользователь предпочитает русский язык».
  • «Проект использует Python 3.13».
  • «Нельзя делать лишний churn по репозиторию».
  • «Для поиска по истории нужна точная расшифровка сессии, а не только summary».
Это хранится прежде всего в memory/MEMORY.md.

2. Контекст завершённых разговоров

Это не «вечные факты», а сжатые итоги конкретных сессий:
  • что обсуждали;
  • к чему пришли;
  • что решили;
  • какие были action items;
  • какие файлы и темы фигурировали в разговоре.
Это хранится в memory/YYYY-MM-DD-HHMM-slug.md.

3. Точный transcript

Это полный архив сообщений завершённой сессии:
  • user;
  • assistant;
  • tool calls;
  • tool results;
  • timestamps;
  • metadata строки в начале JSONL.
Это хранится в sessions/archive/YYYY-MM-DD-HHMM-slug--shortid.jsonl.

4. Метаданные для навигации

Чтобы бот мог не только хранить transcript, но и находить его позже, есть индекс:
  • sessions/_sessions.jsonl
Каждая строка там описывает одну завершённую сессию:
  • session_id
  • key
  • slug
  • created_at
  • updated_at
  • ended_at
  • message_count
  • transcript_path
  • memory_path
  • session_index_path
Именно этот файл делает архив usable для агента, а не просто «складом jsonl-файлов».

Какие файлы участвуют в памяти

memory/MEMORY.md

Это основная долговременная память. Особенности:
  • читается как часть system/context prompt;
  • обновляется через специальный LLM tool save_memory;
  • хранит уже отфильтрованные, нормализованные знания;
  • предназначен не для полной истории, а для устойчивых фактов.
Это самый важный memory-файл, потому что он попадает в рабочий контекст напрямую.

memory/YYYY-MM-DD.md

Это файл pre-compaction flush. Он нужен, когда разговор разрастается и уже близок к лимиту контекста. Перед тем как старую часть истории сжать/архивировать, бот делает отдельный проход и пытается достать:
  • важные факты;
  • решения;
  • предпочтения;
  • next steps.
Если что-то полезное найдено, оно дописывается в daily file. Это слой защиты от потери контекста при длинных чатах. Механизм управляется memory_flush.enabled и по умолчанию включён.

memory/YYYY-MM-DD-HHMM-slug.md

Это summary завершённой сессии. Он создаётся после /new, если сессия была достаточно содержательной. На практике сейчас summary обычно пропускается для совсем тривиальных сессий, где слишком мало пользовательских сообщений. Файл содержит:
  • Session ID
  • Session Key
  • Ended At
  • Transcript
  • Session Index
  • summary;
  • key messages.
Важно: это уже не просто «заметка по дате», а привязанный к архиву memory-артефакт. Из него можно выйти обратно к точной сессии. Механизм управляется session_memory.enabled и по умолчанию включён.

memory/YYYY-MM-DD-HHMM-session-raw.md

Это fallback для завершённой сессии, если SessionMemoryWriter не смог получить нормальные slug + summary от модели. То есть:
  • закрытая сессия всё равно не теряется;
  • вместо красивого summary-файла сохраняется raw-конспект последних сообщений;
  • дальше эта сессия всё равно архивируется в sessions/archive/*.jsonl.
Важно не путать этот файл с memory/YYYY-MM-DD-raw-archive.md:
  • session-raw относится к неудачному summary завершённой сессии;
  • raw-archive относится к неудачной консолидации кусков истории в MEMORY.md.

sessions/<live-session>.jsonl

Это активная живая сессия текущего чата или треда. Она нужна для текущего рабочего контекста:
  • сюда сохраняются новые сообщения;
  • отсюда строится история для следующего запроса к модели;
  • здесь же хранится last_consolidated, чтобы понимать, какая часть уже была сжата в память.
Это не архив завершённой сессии, а рабочий runtime transcript.

sessions/archive/*.jsonl

Это архив завершённых сессий. Каждый такой файл:
  • хранит точный transcript;
  • имеет уникальный session_id;
  • содержит slug;
  • имеет дату и время окончания сессии до минут;
  • связан с summary memory file.

sessions/_sessions.jsonl

Это индекс завершённых сессий. Без него агенту пришлось бы угадывать нужный JSONL-файл по имени. С ним бот может:
  • перечислить завершённые сессии;
  • отфильтровать их по slug, key, path;
  • выбрать session_id;
  • загрузить точный transcript через session_history.

memory/YYYY-MM-DD-raw-archive.md

Это не основной happy-path, а аварийный fallback внутри MemoryStore. Он нужен на случай, когда LLM несколько раз подряд не смог:
  • вызвать save_memory;
  • вернуть корректный payload;
  • или вообще упал при консолидации.
Тогда Nanobot не выбрасывает кусок истории, а сохраняет его в raw-виде в отдельный dated file. Сейчас логика такая:
  • после нескольких подряд неудачных попыток консолидации создаётся/дополняется memory/YYYY-MM-DD-raw-archive.md;
  • туда пишется временная метка и сырой формат сообщений;
  • это fallback последней линии обороны, а не основной слой обычной памяти.

Что реально попадает в prompt напрямую

Это важная деталь, потому что «хранится на диске» и «автоматически участвует в каждом запросе к модели» — не одно и то же. В каждый обычный запрос напрямую попадают:
  • system prompt;
  • bootstrap-файлы вроде AGENTS.md, SOUL.md, USER.md, TOOLS.md, если они существуют;
  • содержимое memory/MEMORY.md;
  • текущая живая история активной сессии из sessions/<live>.jsonl.
Автоматически в prompt не подмешиваются:
  • memory/YYYY-MM-DD.md;
  • memory/YYYY-MM-DD-HHMM-slug.md;
  • memory/YYYY-MM-DD-raw-archive.md;
  • sessions/archive/*.jsonl;
  • sessions/_sessions.jsonl.
Эти данные лежат в долговременном хранилище, но используются только если агент специально добирается до них через search/retrieval-механику или через отдельные tools.

Как память формируется во время жизни сессии

Ниже полный жизненный цикл.

Шаг 1. Текущий разговор пишется в live session

Каждое новое сообщение сохраняется в текущий session JSONL. Там живут:
  • сообщения пользователя;
  • ответы ассистента;
  • tool calls;
  • tool results.
Это оперативная память текущей беседы.

Шаг 2. Если история стала слишком длинной, бот делает pre-compaction flush

Когда оценка prompt-токенов подходит близко к лимиту контекста, Nanobot не сразу режет старую часть истории. Сначала запускается отдельный проход MemoryFlusher, который:
  • смотрит на последние user/assistant сообщения;
  • просит модель выделить durable knowledge;
  • записывает найденное в memory/YYYY-MM-DD.md.
Это защита от ситуации «контекст пришлось сократить, и полезные факты исчезли».

Шаг 3. Затем старый хвост истории консолидируется в MEMORY.md

После flush старые сообщения могут быть обработаны MemoryConsolidator. Он:
  • выбирает безопасную границу по user-turn;
  • вызывает специальный tool save_memory;
  • обновляет memory/MEMORY.md;
  • двигает last_consolidated.
Важно:
  • transcript в live session не переписывается как summary;
  • бот просто помечает, какая часть уже была «усвоена» в долговременную память.

Шаг 4. Когда пользователь делает /new, живая сессия закрывается

При /new происходит сразу несколько вещей:
  1. Текущая session-state клонируется.
  2. Из live session берётся unconsolidated tail для archive_messages.
  3. Живая сессия очищается и получает новый session_id.
  4. В фоне запускается архивация закрытой сессии.
Это важно: live session и archived session после /new — это уже разные сущности.

Шаг 5. Для закрытой сессии создаётся summary memory file

Если диалог содержательный, SessionMemoryWriter:
  • берёт user/assistant часть последних сообщений;
  • просит модель создать slug и summary;
  • пишет memory/YYYY-MM-DD-HHMM-slug.md.
Если модель не справилась:
  • создаётся raw fallback memory/YYYY-MM-DD-HHMM-session-raw.md.
То есть summary-память не ломает систему: даже при неудаче transcript не пропадает.

Шаг 6. Закрытая сессия архивируется как точный JSONL transcript

После summary создаётся архивный transcript:
  • sessions/archive/YYYY-MM-DD-HHMM-slug--shortid.jsonl
Там хранится:
  • session_id
  • key
  • slug
  • created_at
  • updated_at
  • ended_at
  • message_count
  • transcript_path
  • memory_path
  • metadata
  • все сообщения сессии

Шаг 7. В индекс _sessions.jsonl добавляется запись

Это последний шаг, который делает завершённую сессию discoverable. С этого момента агент может:
  • найти её через session_list;
  • получить session_id;
  • восстановить transcript через session_history(session_id=...).

Что именно делает /new

Команда /new не означает «полностью всё забыть». Она означает:
  • начать новый чистый runtime-контекст;
  • закрыть предыдущую сессию;
  • попытаться извлечь из неё знания;
  • сохранить summary;
  • сохранить точный transcript;
  • создать индексную запись.
То есть после /new бот:
  • уже не держит старый разговор в active prompt;
  • но всё ещё может помнить факты из него;
  • всё ещё может найти summary по смыслу;
  • всё ещё может восстановить точную историю завершённой сессии.

Чем отличаются MEMORY.md, summary-файлы и transcript

MEMORY.md

Это:
  • короткая;
  • curated;
  • постоянно актуализируемая;
  • загружаемая в prompt память.
Не подходит для точного восстановления полного разговора.

memory/YYYY-MM-DD-HHMM-slug.md

Это:
  • summary одной конкретной завершённой сессии;
  • удобный для поиска и чтения человеком файл;
  • мост между semantic memory и точным transcript.
Обычно подходит, если нужно вспомнить:
  • о чём была беседа;
  • к чему пришли;
  • какие были основные выводы.

sessions/archive/*.jsonl

Это:
  • точная история;
  • источник истины для полного восстановления сессии;
  • единственный слой, где хранятся tool results и вся последовательность сообщений.
Обычно нужен, если нужно:
  • проверить дословный ход разговора;
  • найти точный tool output;
  • восстановить полную цепочку действий.

Как бот вспоминает прошлое

Сейчас есть три разных режима recall.

1. Мгновенный recall из MEMORY.md

Это самый быстрый и дешёвый режим. Используется для:
  • пользовательских предпочтений;
  • устойчивых фактов;
  • ключевого project context.
Эти данные попадают в context prompt напрямую.

2. Поиск по memory-файлам

Если включён vector/semantic memory, бот может искать по:
  • memory/MEMORY.md
  • memory/*.md
Для этого используется memory_search. Важно:
  • этот tool регистрируется только при vector_memory.enabled = true;
  • по умолчанию vector memory в текущей конфигурационной схеме выключена.
Он лучше всего подходит для вопросов вида:
  • «что мы раньше решили про API?»;
  • «что пользователь предпочитает?»;
  • «какая была договорённость по архитектуре?»;
  • «в какой сессии обсуждали Telegram/Slack/Redis?».
Это не точный transcript search, а semantic recall по memory-файлам.

3. Точный recall по архиву сессий

Если нужна именно полная история прошлой завершённой сессии, используются два инструмента:
  1. session_list
  2. session_history
Обычный workflow такой:
  1. агент вызывает session_list
  2. получает список завершённых сессий из sessions/_sessions.jsonl
  3. выбирает нужный session_id
  4. вызывает session_history(session_id=...)
  5. получает точный transcript
Это уже не «память по смыслу», а восстановление точной истории.

Как работают session_list и session_history

session_list

Этот tool читает sessions/_sessions.jsonl и возвращает:
  • session_id
  • session key
  • slug
  • ended_at
  • количество сообщений
  • путь к transcript
  • путь к memory file
Он может фильтровать результаты по:
  • session_id
  • key
  • slug
  • memory_path
  • transcript_path
То есть бот больше не зависит от shell/grep, чтобы найти нужную старую сессию.

session_history

Этот tool умеет два режима:
  • по текущей live session через session_key
  • по архивной завершённой сессии через session_id
Во втором случае он:
  • ищет запись в _sessions.jsonl;
  • находит transcript_path;
  • загружает sessions/archive/*.jsonl;
  • возвращает точную историю сообщений.
Это особенно важно для задач вида:
  • «покажи точную историю той сессии»;
  • «какой был tool output в прошлый раз?»;
  • «что именно сказал пользователь в той беседе?»;
  • «какой summary-файл связан с этим transcript?».

Как memory-файл связан с transcript

Теперь связь двусторонняя.

Из memory-файла можно перейти к transcript

В memory/YYYY-MM-DD-HHMM-slug.md записываются:
  • Session ID
  • Transcript
  • Session Index
То есть по самому memory-файлу можно понять:
  • к какой сессии он относится;
  • где лежит точный JSONL transcript;
  • через какой индекс этот transcript находится.

Из transcript/index можно перейти к memory-файлу

В _sessions.jsonl и в metadata архивного JSONL записывается:
  • memory_path
То есть по session_id можно найти не только transcript, но и summary memory file. Это и есть тот «мост» между summary-памятью и точной историей.

Что бот запоминает хорошо, а что хуже

Хорошо запоминаются:
  • явные предпочтения;
  • стабильные проектные факты;
  • архитектурные решения;
  • прямые договорённости;
  • важные next steps;
  • темы завершённых сессий;
  • exact transcript завершённых сессий после /new.
Хуже запоминаются:
  • случайные реплики;
  • мелкий шум;
  • длинные обсуждения без явных выводов;
  • детали, которые никогда не были оформлены как факт, решение или summary.
Именно поэтому лучше формулировать важные вещи явно.

Как помочь боту запомнить важное

Лучшие формулировки:
  • «Запомни, что я предпочитаю русский язык».
  • «Запомни, что в проекте нельзя делать лишний churn по репозиторию».
  • «Считай это важным архитектурным решением».
  • «Это новый приоритет проекта».
  • «Запомни это как долгосрочный контекст».
Если что-то перестало быть правдой, тоже лучше говорить явно:
  • «Больше не считай Redis частью текущей архитектуры».
  • «Теперь предпочитаю короткие ответы».
  • «Это решение отменено, используем другой подход».

Ограничения и компромиссы

Бот не держит всю историю всегда в prompt

Это осознанное решение ради:
  • стоимости;
  • скорости;
  • устойчивости;
  • контроля над размером контекста.

MEMORY.md не является полной историей

Это не лог, а curated memory. Если нужен exact transcript, надо идти в архив сессий.

Summary не равен transcript

Summary-файл может передавать смысл, но не заменяет дословную историю.

Архивная точная история появляется после закрытия сессии

Смысл sessions/archive/*.jsonl в том, что это слой завершённых сессий. Текущая активная беседа живёт отдельно, в live session JSONL.

Semantic search и exact history search — разные вещи

memory_search отвечает на вопрос «что вообще раньше было по этой теме». session_history(session_id=...) отвечает на вопрос «что точно было сказано в конкретной сессии».

Практические сценарии

Сценарий 1. Пользователь сказал постоянное предпочтение

Пример:
  • «Отвечай мне по-русски».
С высокой вероятностью это должно оказаться в memory/MEMORY.md.

Сценарий 2. Длинная рабочая сессия

Когда разговор становится слишком большим:
  • бот может сбросить durable facts в memory/YYYY-MM-DD.md;
  • затем сжать старый хвост диалога.

Сценарий 3. Пользователь делает /new

Тогда:
  • live session очищается;
  • старая сессия получает summary memory file;
  • создаётся архивный transcript;
  • в _sessions.jsonl добавляется запись;
  • новой live session назначается новый session_id.

Сценарий 4. Нужно восстановить точную прошлую беседу

Тогда агент может:
  1. вызвать session_list
  2. найти нужную запись
  3. взять session_id
  4. вызвать session_history(session_id=...)

Если сформулировать совсем просто

У Nanobot нет одной «магической памяти». У него есть:
  • короткая рабочая память текущей сессии;
  • долговременная curated memory;
  • semantic memory-файлы по прошлым разговорам;
  • точный архив завершённых сессий;
  • индекс, который связывает всё это вместе.
Это даёт важный баланс:
  • бот не тащит весь прошлый мир в каждый prompt;
  • но и не теряет важные знания;
  • а если нужно, может восстановить точную завершённую сессию через session_id.

Итог

Текущая схема long-term memory в Nanobot устроена так:
  • MEMORY.md хранит устойчивые знания;
  • memory/YYYY-MM-DD.md страхует длинные диалоги перед сжатием;
  • memory/YYYY-MM-DD-HHMM-slug.md хранит summary завершённой сессии;
  • sessions/archive/*.jsonl хранит точный transcript завершённой сессии;
  • sessions/_sessions.jsonl связывает session_id, transcript и memory file;
  • memory_search ищет по memory-файлам;
  • session_list находит завершённые сессии;
  • session_history достаёт точную историю по session_id или текущей live session.
Если нужна краткая память — используется MEMORY.md. Если нужно понять, о чём был прошлый разговор, — помогают dated memory files и memory_search. Если нужна точная история, — используются _sessions.jsonl и session_history.