Небольшой профиль, неограниченный архив
Здесь сознательно две памяти. Файловая память — MEMORY.md и USER.md — это стабильный «профиль» ~2 КБ, лежащий в промпте. Семантическая память — это архив: каждая запись — самодостаточный факт (рекомендуется 50–500 символов) с областью — agent для общих знаний, user для фактов о клиенте, run для эпизодов сессии. Сверху — оценки уверенности и важности, теги, типизированные рёбра и полный журнал изменений, всё в SQLite с FTS5 и эмбеддингами.
Только добавление, с подтверждениями
Факты никогда не перезаписываются. Когда что-то меняется, новая версия — это новая запись, связанная со старой ребром supersedes; противоречия видны с обеих сторон через ребро contradicts. Чтение по id имеет три режима: active, latest (следовать по цепочке до новейшей версии) и full_history — вся эволюция факта. Журнал изменений здесь не функция, а сама модель хранения.
Поглощать, а не создавать
Запись идёт через конвейер, а не через insert. Сначала валидация, затем детекция секретов — API-ключи, токены и PEM-блоки отклоняются сразу. Близкие факты внутри батча консолидируются (N→1 при схожести 0.85), точные дубликаты убиваются по хешу, косинусная схожесть подтягивает кандидатов. Классификатор — LLM, если доступен, иначе эвристика с порогом 0.97 — присваивает каждому входящему факту ровно один из пяти исходов: duplicate (пропустить), supersede, contradict, related или new. Режим dry_run выводит план, ничего не записывая.
Гибридный поиск с затуханием
Поиск смешивает два сигнала: score = 0.7·cosine + 0.3·BM25. Затем линейное затухание актуальности — score × max(0, 1 − 0.01·age_in_days), один процент в день — не даёт устаревшим фактам обгонять актуальные. Опциональный второй проход ([memory] rerank = true) позволяет LLM переупорядочить расширенный набор результатов (top_k×3). В бенчмарках гибридный поиск имеет медианную задержку около 2 мс.
Замороженный снимок сохраняет кеш горячим
Перед началом сессии главный агент получает определённый дайджест в системный промпт: релевантные воспоминания плюс открытые TODO плюс недавние записи — с реальными id, чтобы агент мог всё проверить через memory_search. Ключевое: снимок заморожен на всю сессию — записи сохраняются сразу, но никогда не меняют промпт. Так кеш префиксов LLM остаётся целым на весь запуск; снимок обновится на следующей сессии. Память, которую дёшево читать, реально используется.
Граф реального мира
Наряду с фактами хранилище поддерживает граф сущностей: узлы — person, company, project, technology, role, location, event, product — дедуплицируются по (имя + тип), поэтому «Ivan Petrov» и «ivan petrov» — один узел. Рёбра типизированы — works_at, leads, founded, located_in — и multi-hop BFS до 4 переходов отвечает на вопросы типа «кто руководит X» или «кто работает в компаниях Казани». Когда поглощённый факт несёт метаданные сущностей, его узлы и рёбра создаются вместе с ним автоматически.
Ничто никогда не удаляется. Дистилляция сжимает, GC архивирует, а полная история остаётся доступной по id — память, которую можно проверять, это память, которой можно доверять.
— Fathom, заметки о дизайне памяти
Обслуживание без удаления
Ночная консолидация — это distill: run-факты сессий заново поглощаются в долговременные знания агента, дубликаты отфильтровываются, оригиналы архивируются. gc — консервативный офлайн-проход в три этапа: удаление строк после TTL, архивирование устаревших неиспользуемых run-фактов и сжатие разросшихся групп областей в консолидированные сводки. Обе доступны из CLI и по HTTP, обе поддерживают --dry-run, и ни одна никогда не уничтожает запись. Поглощение занимает 49–521 мкс на факт, а повторное поглощение 100 уже известных фактов — это быстрый путь дедупликации за 2.2 мс — хранилище достаточно дёшево, чтобы быть всегда включённым.