Мы научили ИИ-агента забывать — и он стал помнить лучше. claude code.. claude code. mcp.. claude code. mcp. skillmem.. claude code. mcp. skillmem. SQLite.. claude code. mcp. skillmem. SQLite. память ИИ-агентов.

У меня за плечами больше 700 часов работы с Claude Code над одним личным проектом. За это время я насмотрелся на одну и ту же картину: агент открывает новую сессию — и не помнит вообще ничего. Ни того, что вчера полтора часа убил на баг, который решался одной строкой. Ни того, что подход, который он сейчас с энтузиазмом предлагает, я уже отклонил на прошлой неделе с подробным объяснением почему. Каждая сессия — это День сурка, только вместо будильника — системный промпт.

Существующие инструменты памяти для агентов эту проблему вроде бы решают — но не совсем ту, которая у меня болела. Большинство из них (claude-mem, mem0 и им подобные) хранят факты и прогоняют каждую запись через LLM: суммаризация, экстракция сущностей, иногда переранжирование при чтении. Это работает, но у подхода есть цена — в буквальном смысле. Если каждая запись в память стоит вызова модели, агент невольно начинает записывать только «важное», а важное определяется тем, сколько вы готовы на это потратить. В результате обучается он не после каждой задачи, а после отобранных.

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

Репозиторий открытый, Apache-2.0: github.com/liza-studio/skillmem. Ставится одной командой:

pip install skillmem
skillmem init --claude-code

Дальше — про то, как это устроено внутри и почему именно так.

Идея: память как навык, а не как факт

skillmem не хранит «пользователя зовут так-то» или «дедлайн такого-то числа». Это задача других инструментов, и она у них решена неплохо. skillmem хранит процедуры: как решалась конкретная неприятная задача.

Единица памяти — это skill с четырьмя полями:

  • trigger — когда это применимо («деплой падает с ошибкой миграции БД»);

  • steps — что конкретно делать;

  • outcome — сработало или нет (success/partial/failure);

  • lessons — что важно не забыть в следующий раз.

Записывается это через mem_learn (MCP-инструмент) или из командной строки:

skillmem learn skill-deploy-migration-lock 
  -t "Деплой виснет на миграции под нагрузкой" 
  --trigger "деплой + миграция БД + прод под трафиком" 
  --steps "1. Дренаж трафика перед миграцией. 2. Explicit lock_timeout=5s. 3. Ретрай с backoff, не голый rerun" 
  --outcome success 
  --lessons "Голый rerun миграции при активном lock плодит дубли задач в очереди"

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

Почему это должно быть бесплатно

Ключевое архитектурное решение: путь записи не трогает LLM вообще. Это обычный INSERT в SQLite плюс морфологический разбор текста (стемминг) для полнотекстового индекса. Миллисекунды, ноль токенов, ноль сетевых вызовов.

Это не оптимизация ради оптимизации. Она меняет то, чему агент может позволить себе учиться. Если запись стоит денег, вы неизбежно фильтруете: «это достаточно важно, чтобы записать?» Если запись стоит ноль — фильтровать не нужно, агент пишет после каждой нетривиальной задачи, а решение о том, что действительно ценно, откладывается на потом — на момент, когда навык либо пригождается снова (и усиливается), либо нет (и угасает).

Стек под капотом полностью локальный и офлайн:

  • SQLite + FTS5 — полнотекстовый поиск с ранжированием BM25;

  • Snowball-стемминг отдельно для русского и английского — не общий «мультиязычный» стеммер, а два специализированных, что заметно точнее на морфологически богатых языках;

  • ONNX-эмбеддинги paraphrase-multilingual-MiniLM-L12-v2, 384 измерения, инференс на CPU, без сети;

  • Reciprocal Rank Fusion (RRF, K=60) — стандартная формула для слияния лексического и семантического ранжирования в один список.

Двуязычность не как фича для галочки

Я живу и работаю на двух языках одновременно, и практически все memory-инструменты, что я пробовал, English-first в самом неприятном смысле: русский запрос либо не находит английский навык, либо находит с шумом. Для меня это было личной болью, а не абстрактным требованием к «интернационализации».

В skillmem запрос на русском находит навык, записанный на английском, и наоборот — полностью локально, без похода к переводческому API. Работает это благодаря сочетанию раздельного стемминга (BM25-часть понимает морфологию каждого языка отдельно) и мультиязычной эмбеддинг-модели (семантическая часть работает в общем векторном пространстве независимо от языка ввода). RRF сводит два сигнала в один ранжированный список.

Забывание как фича, а не баг

Тут стоит остановиться подробнее, потому что это центральная метафора проекта — и она не риторическая, а реализована буквально в коде хранилища.

У каждого навыка есть числовая «сила» (strength). Она:

  • растёт на +0.15 при каждом полезном mem_reinforce (агент явно подтверждает: этот навык сейчас помог);

  • угасает по кривой, похожей на кривую забывания Эббингауза: за период простоя сила умножается на коэффициент 0.85, с полом 0.05 — навык никогда не обнуляется мгновенно, но и не остаётся вечно на пике без подтверждений.

Жизненный цикл навыка — три состояния: active → stale (30 дней без обращений) → archived (90 дней). Архивные навыки исключаются из recall, чтобы не засорять выдачу мусором, который агент явно перестал использовать. Но — и это важно — они никогда не удаляются. Перед архивацией делается снапшот в JSONL, и в любой момент навык можно вернуть в строй одной командой:

skillmem skills                  # список с полосками силы
skillmem decay --days 14         # ручной прогон угасания + lifecycle sweep
skillmem restore skill-deploy-migration-lock

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

Защита от подмены задним числом

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

Поэтому каждое изменение каждой записи попадает в hash-chain на SHA256: новая запись хэшируется вместе с хэшем предыдущей версии, образуя цепочку, которую нельзя переписать задним числом без разрыва последующих звеньев. Перед хэшированием текст нормализуется в Unicode NFC — иначе одна и та же строка, введённая на macOS (где файловая система любит NFD) и на Linux, давала бы разные хэши при идентичном содержимом, и цепочка ложно бы «рвалась» на ровном месте.

Проверка целостности — отдельная команда:

skillmem verify --strict

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

Замер: LongMemEval

Голословных заявлений о качестве поиска не хочется — поэтому в репозитории есть воспроизводимый бенчмарк на LongMemEval (Wu et al., ICLR 2025), стандартном датасете для оценки долгосрочной памяти диалоговых агентов. Прогон — полный oracle-набор, n=479, гибридный поиск (BM25 + эмбеддинги + RRF), k=5, только CPU:

Тип вопроса

n

hit@5

MRR

Всего

479

0.871

0.622

single-session-assistant

56

0.982

0.746

knowledge-update

72

0.944

0.676

single-session-user

64

0.938

0.719

multi-session

125

0.848

0.568

single-session-preference

30

0.833

0.465

temporal-reasoning

132

0.780

0.579

Медиана — 0.76 секунды на запрос на CPU ноутбука. Никаких LLM в цикле поиска, а значит и никакого недетерминизма: повторный прогон на том же датасете даёт те же цифры до третьего знака. Воспроизвести можно самому:

python bench/longmemeval.py --sample 0 -k 5

Полные условия прогона и как правильно репортить такие цифры — в bench/README.md. Мы сознательно проговариваем режим поиска и модель эмбеддингов при каждой публикации числа, и было бы хорошо, если бы это стало нормой для подобных инструментов вообще — голая цифра hit@5 без контекста ничего не говорит о применимости к вашей задаче.

Интеграция с Claude Code

Всё это можно дёргать вручную через CLI, но смысл в автоматизации. skillmem init --claude-code одной командой прописывает:

8 MCP-инструментов: mem_search, mem_get, mem_list, mem_write, mem_update, mem_learn, mem_recall, mem_reinforce.

6 хуков жизненного цикла Claude Code:

Событие

Хук

Что делает

SessionStart

mcp-guard

Предупреждает, если настроенные MCP-серверы пропали относительно эталона

SessionStart

inject

Краткий брифинг по заголовкам user/feedback-записей

SessionStart

session-history

Резюме последних 3 сессий в этом проекте

UserPromptSubmit

verify-gate

Напоминание «сначала проверь» на времязависимые запросы (RU/EN триггеры)

UserPromptSubmit

auto-recall

Релевантные навыки и предупреждения по тексту промпта

PreToolUse

tool-recall

Навыки/предупреждения по команде Bash или редактируемому файлу

Stop

session-recap

Сворачивает сессию в markdown-заметку через claude -p

Stop

migrate

Индексирует новые заметки в БД

Все хуки best-effort: сломанная база или отсутствующая модель эмбеддингов никогда не блокирует работу Claude Code, просто хук молча пропускает шаг.

Пример вызова MCP-инструмента вручную (для отладки или из другого клиента):

skillmem recall "деплой бота на прод падает"

или через MCP-протокол — тот же mem_recall с параметром запроса, который агент вызывает автоматически на старте сессии через auto-recall.

skillmem работает и как обычный MCP-сервер в Claude Desktop — просто без автоматических хуков, потому что хуки это механизм самого Claude Code, а не MCP-протокола:

{
  "mcpServers": {
    "skillmem": { "command": "skillmem-mcp" }
  }
}

Восемь mem_*-инструментов доступны и там — просто вызываются по запросу, а не автоматически.

Экспорт и отсутствие vendor lock-in

База — обычный файл SQLite у вас на диске, но на случай, если понадобится взять данные с собой (или просто держать их читаемыми человеком в Obsidian), есть round-trip в markdown:

skillmem export-all ./vault
skillmem import-vault ~/Obsidian/Notes

Экспорт — YAML-frontmatter плюс тело в markdown, по файлу на запись. Реимпорт такого дампа даёт те же записи обратно, это протестировано отдельно, а не декларировано на словах.

Почему SQLite и брутфорс-косинус, а не векторная БД

Логичный вопрос: почему не Pinecone/Qdrant/pgvector? Ответ упирается в масштаб задачи. Личная память агента — это тысячи, максимум десятки тысяч записей, не миллионы. На таком объёме поиск ближайшего соседа брутфорсом — обычное матричное умножение numpy по всей колонке эмбеддингов — занимает субмиллисекунды. Специализированный векторный индекс (HNSW и подобные) на этом масштабе не даёт измеримого выигрыша в скорости, зато добавляет: отдельный процесс или зависимость, свой режим отказа, свою процедуру бэкапа, часто отдельный сетевой порт. Ради ускорения операции, которая и так занимает доли миллисекунды, это плохой обмен.

Если бы задача была про миллионы записей на множество пользователей — расчёт был бы другим. Для локальной памяти одного агента — нет.

Кросс-платформенность

Планировщик для decay-задачи и еженедельного экспорта работает на всех трёх основных ОС через нативный механизм каждой:

skillmem schedule install        # decay ежедневно 04:15, export по воскресеньям 04:30

— на macOS это launchd, на Windows — schtasks, на Linux — systemd user timers с откатом на cron, если таймеров нет. Всё покрыто 130 тестами, CI гоняется на трёх ОС при каждом пуше.

Ограничения — честно

Проект молодой, и не всё в нём одинаково хорошо:

  • temporal-reasoning — самая слабая категория бенчмарка: hit@5 0.780 против 0.871 в среднем. Вопросы вида «что изменилось между событием A и событием B» гибридный лексико-семантический поиск решает хуже, чем однократные факты или предпочтения — вероятно, потому что временная связь между двумя записями не выражена явно ни в тексте, ни в эмбеддинге. Это открытый issue #2, решения пока нет.

  • Полная автоматизация (хуки) работает только в Claude Code. В Claude Desktop и других MCP-клиентах все 8 инструментов доступны, но вызывать их приходится явно — auto-recall на каждый промпт и session-recap на завершение сессии физически не могут сработать там, где нет самого механизма хуков.

  • Семантический поиск — не из коробки. Базовая установка даёт только лексический BM25-поиск; чтобы включить ONNX-эмбеддинги и RRF-слияние, нужно ставить с экстрой: pip install 'skillmem[semantic]'. Без этого двуязычный «русский запрос находит английский навык» работать не будет в полную силу — стемминг остаётся, а векторный мост между языками пропадает.

  • Проект не претендует на замену инструментам, которые хранят факты, — это другая задача. skillmem специально узкий: он про процедурную память, а не про general knowledge base.

Что дальше и как помочь

Мне нужна была эта штука для собственного агента, и она уже год как в проде на моих задачах — 700+ часов набитых шишек прямо сейчас лежат в этой базе в виде навыков. Публикую под Apache-2.0, потому что не вижу причин, почему тем же граблям должен учиться каждый в одиночку.

Что было бы полезно от сообщества:

  • прогонять bench/longmemeval.py на своих машинах и репортить расхождения — детерминированный пайплайн должен давать одинаковые числа везде, если нет — это баг;

  • идеи по temporal-reasoning (issue #2) — если у кого-то есть опыт с time-aware ранжированием на BM25/эмбеддингах, это то место, где гибридный поиск сейчас проигрывает больше всего;

  • обратная связь по хукам в других MCP-клиентах, помимо Claude Code и Claude Desktop.

Репозиторий: https://github.com/liza-studio/skillmem Установка: pip install skillmem && skillmem init --claude-code Лицензия: Apache-2.0.

PR и issues приветствуются.

Автор: mrPetrukovich

Источник