Эфир 18.11 · Поток 318 ноября в 19:00 МСК — бесплатный практический эфир по ИИ-агентам · Живой Поток 3 стартует 23.11Эфир 18 ноября →Поток 3 (23.11)
Авторская колонка

Пятислойная иммутабельная память для ИИ-агента: почему одной векторной базы никогда не хватит

Попытка хранить всю долговременную память ИИ-агента в одном векторном индексе проваливается, потому что точный поиск по словам, семантическая близость, многошаговый обход связей и история смены решений требуют физически разных структур данных. Инкрементальный индекс SQLite FTS5 с ранжированием BM25 поверх 66 995 сессий и 538 956 сообщений (1,35 ГБ) избавил от падений среды разработки и ускорил старт сессии с 15 секунд до десятых долей секунды. Для защиты памяти от деградации внедрены детерминированная санитизация ключей через UUID5, битемпоральная запись без удаления старых фактов и ночной цикл консолидации с затуханием по распределению Вейбулла.

Пятислойная иммутабельная память для ИИ-агента: почему одной векторной базы никогда не хватит
Hamidun News.
Слушать статью
Паспорт материала и цифры
ПараметрДанные
Главный редактор
Объём эпизодического индекса66 995 сессий и 538 956 сообщений в базе SQLite FTS5 размером 1 353 МБ
База второго мозга и эмбеддингов10 019 структурированных карточек знаний и `brain.db` объёмом 2,37 ГБ
Ключевой факт #2Скорость старта после отказа от тяжёлых хуков: падение латентности инициализации с 5–15 с до < 0,2 с
Пороги ночной консолидациисклейка дублей при косинусном сходстве ≥ 0,70 и детектор противоречий с порогом 0,40 без вызова LLM

Точкой отсчёта для моей архитектуры памяти стал глухой краш редактора кода. При попытке открыть историю диалогов с ИИ-агентом процесс Extension Host превысил лимит памяти и рухнул вместе с терминалом. За полгода работы накопилось 2,45 ГБ сырых JSONL-логов сессий, и расширение на старте пыталось прочитать их заголовки с диска. Вдобавок заводская настройка cleanupPeriodDays=30 молча удаляла сессии старше месяца — пока я не заменил её на 99999, часть зимнего архива исчезла.

Ревизия показала, что две из четырёх моих баз памяти мертвы, так как требовали ручного запуска демонов. Главное же открытие состояло в том, что «память агента» — это пять разных классов вопросов, и под каждый нужен свой движок.

Почему SQLite FTS5 выиграл у Qdrant и grep на полумиллионе сообщений?

Для эпизодической памяти («где мы обсуждали настройку вебхуков три месяца назад») grep по гигабайтам текста был слишком медленным, а подъём локального Qdrant с fastembed на старте сессии тормозил запуск на 5–15 секунд и сжирал сотни мегабайт RAM.

Победил встроенный в Python движок SQLite FTS5 с ранжированием BM25 и режимом WAL. Индексатор search_chats.py сверяет file_mtime с таблицей sessions: если расхождение меньше секунды, файл пропускается. Сырые логи старше 14 дней и дампы свыше 20 МБ уезжают в холодный архив — рабочий каталог похудел с 2,45 ГБ до 527 МБ, краши прекратились, а индекс вырос до 66 995 сессий и 538 956 сообщений (290 544 от человека и 248 412 от ассистента) при весе 1 353 МБ. Для критичных справочников (вроде 20-главной инструкции, собранной из 2 240 сообщений и 177 расшифрованных через Deepgram голосовых заметок) действует 4-уровневое резервирование: конспект в рабочей памяти, указатель в MEMORY.md (строго до 200 строк и ~22 КБ), правило маршрутизации и векторный слепок.

Зачем нужен детерминированный UUID5 в векторной базе и как работает битемпоральность?

Когда точных слов в запросе нет (человек спрашивает «чем мыть фрукты без химии», а в базе заметка про щелочной режим ионизатора pH 11.5), вступает семантический слой на эмбеддингах (размерность 384 локально и 1536 для книг). В жизни идентификаторы документов выглядят как строки knowledge:42 или session/2026-04-10/msg-17 с кириллицей, но векторный движок Qdrant требует в качестве point_id либо целое число, либо строгий UUID. Случайный uuid4() плодит дубликаты при переиндексации, поэтому мы ввели детерминированный uuid5 поверх пространства имён DNS:

```python import uuid

def to_deterministic_point_id(raw_doc_id: str) -> str:

# Превращает любую строку с кириллицей и спецсимволами в стабильный ASCII UUID5 return str(uuid.uuid5(uuid.NAMESPACE_DNS, raw_doc_id))

point_id = to_deterministic_point_id(doc_id) payload = {**metadata, "doc_id": doc_id, "content": text_chunk} ```

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

Чтобы агент помнил историю смены решений («почему весной мы выбрали другой движок?»), мы позаимствовали идею из Datomic и запретили UPDATE с затиранием прошлого. Каждая связь имеет поля valid_from, valid_to и ingested_at, а в базе второго мозга также счётчики востребованности use_count и last_used. Когда решение устаревает, у старого ребра проставляется valid_to, и рядом вставляется новая запись со ссылкой supersedes.

Как алгоритмический сон с кривой Вейбулла очищает память от противоречий?

Чтобы база не превращалась в свалку, фоновый пайплайн консолидации (dream и модули sleeptime) периодически проводит уборку в три этапа:

  1. Кластеризация дублей (SHMR): матрица косинусной близости на эмбеддингах размерности 1024 (Qwen3-Embedding-0.6B в LanceDB) схлопывает записи со сходством >= 0.70.
  2. Селективное затухание по распределению Вейбулла: мимолётные наблюдения (observation) затухают за 336 часов (~2 недели), рабочие факты — за 2 160 часов, а личные предпочтения (preference), процедуры и паттерны ошибок (error_pattern) защищены списком EXEMPT_TYPES.
  3. Поиск противоречий без LLM: детерминированный сканер ищет конфликты за 7 дней по взвешенным сигналам (negation: 0.35, antonym: 0.25, numeric: 0.25, temporal: 0.15) при пороге 0.40.

Эта пятислойная схема памяти (SQLite FTS5, LanceDB и темпоральный граф) встроена в ядро Nomad AI и облачной платформы agent.nomadnet.ai. Курс по инженерии памяти доступен в Hamidun Academy (72-часовой триал), а скрипты индексации лежат в библиотеке Vibe Club.

Сюжет

Вопросы и ответы по материалу

Почему SQLite FTS5 выиграл у Qdrant и grep на полумиллионе сообщений?

Для эпизодической памяти («где мы обсуждали настройку вебхуков три месяца назад») grep по гигабайтам текста был слишком медленным, а подъём локального Qdrant с fastembed на старте сессии тормозил запуск на 5–15 секунд и сжирал сотни мегабайт RAM. Победил встроенный в Python движок SQLite FTS5 с ранжированием BM25 и режимом WAL. Индексатор search_chats.py сверяет file_mtime с таблицей sessions: если расхождение меньше секунды, файл пропускается.

Зачем нужен детерминированный UUID5 в векторной базе и как работает битемпоральность?

Когда точных слов в запросе нет (человек спрашивает «чем мыть фрукты без химии», а в базе заметка про щелочной режим ионизатора pH 11.5), вступает семантический слой на эмбеддингах (размерность 384 локально и 1536 для книг). В жизни идентификаторы документов выглядят как строки knowledge:42 или session/2026-04-10/msg-17 с кириллицей, но векторный движок Qdrant требует в качестве point_id либо целое число, либо строгий UUID.

Как алгоритмический сон с кривой Вейбулла очищает память от противоречий?

Чтобы база не превращалась в свалку, фоновый пайплайн консолидации (dream и модули sleeptime) периодически проводит уборку в три этапа: Кластеризация дублей (SHMR): матрица косинусной близости на эмбеддингах размерности 1024 (Qwen3-Embedding-0.6B в LanceDB) схлопывает записи со сходством >= 0.70.

ЖХ
Жемал Хамидун — CPO Alpina Digital, продакт-менеджер AlpinaGPT. Делает AI-продукты с 2019 года. Hamidun News — pet project, который вырос в production-агрегатор с 6000+ читателей.
✦ Закрытый клуб практиков · Vibe Club

Внедряйте ИИ быстрее рынка вместе с инженерами и фаундерами

Внутри Vibe Club (hamidun.com/club): еженедельные живые мастермайнды и разборы архитектуры с Жемалом Хамидуном, закрытый Tech Radar, LMS и обновляемая база из 370+ боевых скиллов.

Вступить в Vibe Club →Тест-драйв Академии (72ч)
Что вы думаете?
Загружаем комментарии…