Memory MCP Server, часть 2: как проект вырос из semantic search в memory backbone для инженерных агентов

от автора

В первой части я рассказывал про agent-memory-mcp версии 0.1.0: MCP-сервер на Go, SQLite-хранилище, четыре типа памяти, semantic search, RAG по документации, PathGuard и transport через stdio/HTTP.

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

После этого проект прожил несколько месяцев реального использования. И почти всё интересное произошло не там, где я изначально ожидал.

Оказалось, что главная проблема не в том, чтобы “куда-то записать память” и потом сделать cosine similarity. Это базовый слой. Реальная боль начинается дальше:

  • как не смешать embeddings от разных моделей и не получить уверенно неправильный recall;

  • как не превратить память в свалку через две недели;

  • как понять, что знание устарело;

  • как заставить агента не просто помнить, а поддерживать рабочий контекст проекта;

  • как автоматически забирать полезные выводы из сессий, но не сохранять каждый шумный промежуточный шаг;

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

На момент подготовки текста проект дошёл до v0.8.0 (tag от 2026-05-06). Ниже — не changelog, а разбор того, почему архитектура изменилась именно так.

Репозиторий: github.com/ipiton/agent-memory-mcp


Коротко: что было и что стало

В 0.1.0 ядро выглядело так:

Было в 0.1.0

Зачем

4 типа памяти: episodic, semantic, procedural, working

Не хранить всё как плоские заметки

Semantic search через embeddings

Искать по смыслу, а не только по словам

RAG-индекс документов

Отделить память агента от проектной документации

SQLite

Zero-ops: один бинарник, один файл БД

stdio + HTTP transport

Работать и как MCP-сервер, и как HTTP-сервис

PathGuard

Не дать агенту читать произвольные пути

К 0.8.0 это превратилось в другой класс системы:

Стало

Почему появилось

local-only режим и reembed

Fallback между embedding-моделями оказался опаснее отказа

Hybrid retrieval + source-aware ranking

Чистый semantic search слишком шумит на инженерных запросах

Trust/freshness metadata

Агенту важно знать не только “похоже”, но и “можно ли этому верить”

Session close pipeline

Полезные знания рождаются в конце работы, а не только в ручных store_memory

Claude Code hooks

Ручная дисциплина не масштабируется даже для одного разработчика

Canonical knowledge + project bank

Нужен слой поддерживаемых знаний, а не только raw memory

Stewardship layer

Память стареет, дублируется и конфликтует сама с собой

Sedimentation

Не все знания должны всплывать одинаково часто

Structure-aware RAG

Markdown-документы нельзя резать только по символам

Multi-hop recall

Некоторые ответы требуют цепочки связей, а не одного похожего текста

SQLite/WAL hardening

“Один файл БД” всё равно требует нормальной эксплуатации

Главная смена фокуса:

agent-memory-mcp перестал быть просто memory tool. Он стал memory + docs + repo context layer for engineering agents.

Или короче: memory backbone для инженерных агентов.


Урок 1. Time-to-value важнее списка возможностей

Первая версия была инженерно понятной, но не очень дружелюбной на старте. Нужно было разобраться с env-переменными, путями к данным, embedding-провайдерами, настройками MCP-клиента, индексированием документов.

Для меня как автора это нормально: я знаю, где что лежит. Для пользователя — нет.

Поэтому одним из первых направлений стал solo local режим:

.agent-memory/  rag-index/  memory-store/  logs/

Одна директория данных, один ожидаемый layout, один быстрый smoke path:

agent-memory-mcp store -content "Solo local smoke check" -type working -tags "smoke,local"agent-memory-mcp recall "solo local smoke"agent-memory-mcp indexagent-memory-mcp search "agent memory"

Появились:

  • автозагрузка .env из текущего проекта;

  • config command для генерации MCP-конфигов под Claude Desktop, Cursor и Codex;

  • Homebrew tap и GoReleaser;

  • make local-smoke;

  • документация “start local in 3 minutes” вместо длинной простыни про все режимы сразу.

Это не выглядит как архитектурная фича, но на практике сильно меняет продукт.

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


Урок 2. Fallback embeddings был неправильной отказоустойчивостью

В первой статье я уже писал про проблему embedding model mismatch. В 0.1.0 архитектура выглядела красиво: Jina AI как primary, OpenAI-compatible API и Ollama как fallback.

На схеме это отказоустойчивость.

В реальности это ловушка.

Embeddings от разных моделей живут в разных векторных пространствах. Даже если размерность совпала, cosine similarity между векторами от разных моделей не имеет полезного смысла.

Самый неприятный режим отказа: система не падает. Она возвращает результаты. Просто неправильные.

То есть агент не говорит “не смог найти”. Он уверенно приносит нерелевантный контекст, и дальше весь reasoning строится на мусоре.

После этого в проекте появились три изменения.

local-only режим

Если выставить:

MCP_EMBEDDING_MODE=local-only

то сервер не ходит в Jina и OpenAI-compatible endpoints. Embeddings генерируются только через локальный Ollama.

Это решает сразу две задачи:

  1. privacy — текст документов и памяти не уходит наружу;

  2. consistency — все вектора живут в одном пространстве.

embedding_model как часть данных

Для memory store и RAG-индекса стало важно явно знать, какой моделью построен embedding.

При recall система больше не смешивает записи из разных embedding spaces. Если запись была построена другой моделью, она не участвует в semantic similarity как равная текущим записям.

Да, это может дать меньше результатов. Но “меньше результатов” лучше, чем “неправильные результаты с высоким score”.

reembed

Смена модели теперь трактуется не как runtime fallback, а как миграция данных.

agent-memory-mcp reembed

Это принципиальная разница:

  • fallback отвечает на вопрос “что делать, если провайдер временно недоступен?”;

  • re-embed отвечает на вопрос “как перевести корпус на новую модель?”.

В первой версии эти две вещи были смешаны. После реального использования стало понятно, что их надо развести.


Урок 3. Semantic search недостаточен для инженерной памяти

Cosine similarity хорошо работает как базовый recall. Но инженерные запросы редко бывают просто “найди похожий текст”.

Агент спрашивает:

  • “какой runbook подходит для этого инцидента?”;

  • “почему мы отключили HPA?”;

  • “что недавно меняли в ingress?”;

  • “какие caveats есть у этой миграции?”;

  • “мы уже пробовали такой подход?”.

Для таких вопросов важно не только смысловое сходство.

Важны:

  • тип источника: ADR, runbook, postmortem, changelog, Helm, Terraform, CI config;

  • свежесть;

  • confidence;

  • owner;

  • last verified date;

  • совпадение ключевых терминов;

  • контекст проекта или сервиса;

  • является ли запись canonical или это сырая заметка из сессии.

Так появился hybrid retrieval.

Source-aware ingestion

RAG-индекс начал классифицировать инженерные артефакты:

  • docs;

  • ADR/RFC;

  • changelog;

  • runbooks;

  • postmortems;

  • CI configs;

  • Helm/Terraform/Kubernetes файлы.

Это нужно не для красивой метаданной. Это меняет ranking.

Если запрос похож на incident response, runbook и postmortem должны иметь другой вес, чем обычный README. Если запрос про миграцию, caveat из прошлой миграции может быть важнее общего architectural overview.

Hybrid ranking

Вместо “только cosine” ranking стал учитывать несколько сигналов:

  • semantic similarity;

  • keyword/BM25-like matching;

  • recency;

  • source type;

  • trust/freshness;

  • importance;

  • lifecycle status.

Это менее элегантно, чем один score из embedding-модели. Зато лучше соответствует реальным вопросам агента.

В инженерном поиске точное слово иногда важнее “похожего смысла”. Если в запросе есть HPA, ingress, billing-api или migration #47, нельзя полностью отдавать ранжирование на откуп embedding-модели.

Explainable retrieval

Ещё один вывод: если retrieval странный, его надо уметь разбирать.

Появился debug mode, который показывает:

  • какие фильтры применились;

  • какие score-компоненты участвовали;

  • какие boosts сработали;

  • почему результат оказался выше или ниже.

Потом появился lightweight retrieval console на /console.

Для обычного пользователя UI не обязателен. Для разработки retrieval — очень полезен. Иначе tuning превращается в гадание: “кажется, стало лучше”.

Eval suite

К v0.7.0 в проекте появился RAG eval harness с fixture corpus и baseline metrics.

Это важный сдвиг: ranking перестал быть набором интуитивных весов, которые можно случайно сломать. Теперь изменения в retrieval можно прогонять через regression gate.

Да, тестовый deterministic embedder не заменяет реальную embedding-модель. Но он фиксирует contract: если поменяли chunking, ranking или source boosts, нужно увидеть, какие вопросы стали хуже искать ожидаемые документы.


Урок 4. Память нельзя только дописывать

Самая большая ошибка в memory-системах — считать, что проблема решается append-only storage.

Агент сохраняет:

  • одно решение;

  • потом уточнение;

  • потом workaround;

  • потом “workaround больше не нужен”;

  • потом похожую запись другими словами;

  • потом session summary, где всё это повторено ещё раз.

Через месяц recall начинает приносить смесь актуальных решений, устаревших фактов и промежуточных мыслей.

В первой версии у памяти были типы. Но типа недостаточно.

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

Lifecycle status

Появились состояния вроде:

  • active;

  • outdated;

  • superseded;

  • canonical.

mark_outdated может связать старую запись с новой через supersession chain. Старое знание не удаляется, потому что оно всё ещё полезно для истории: “почему мы тогда так сделали?” Но в обычном recall оно должно ранжироваться ниже.

Отсюда же выросли temporal-запросы:

  • recall_as_of — что было верно на конкретную дату;

  • knowledge_timeline — как менялось знание по теме.

Это уже не просто “заметки”. Это маленькая модель эволюции инженерного знания.

Canonical knowledge

Raw memory полезна, но агенту нужен слой “поддерживаемого знания”.

Так появился canonical layer:

  • promote_to_canonical;

  • list_canonical_knowledge;

  • recall_canonical_knowledge;

  • project summaries, которые сначала показывают canonical context, а потом сырые записи.

Идея простая: не все сохранённые записи равны. Некоторые — наблюдения. Некоторые — решения. Некоторые прошли проверку и должны всплывать первыми.

Project bank

Потом стало ясно, что “список memories” — плохой интерфейс для поддерживаемого проекта.

Нужны представления:

  • decisions;

  • runbooks;

  • incidents;

  • caveats;

  • migrations;

  • review queue;

  • canonical overview.

Так появился project_bank_view.

Это хороший пример того, как низкоуровневые memory tools постепенно превращаются в доменные инструменты. Агенту проще спросить “покажи caveats по миграциям”, чем самому фильтровать сырые записи по tags/type/metadata.


Урок 5. Конец сессии важнее ручного store_memory

В первой версии я в основном думал о ручном сохранении:

Нашли важное решение -> вызвали store_memory.

Но реальная работа с агентом устроена иначе.

Во время сессии происходит много шума:

  • гипотезы;

  • неудачные проверки;

  • чтение файлов;

  • промежуточные планы;

  • правки;

  • тесты;

  • откаты;

  • уточнения.

Если сохранять всё подряд, память умирает. Если полагаться на ручную дисциплину, часть знаний теряется.

Лучшее место для извлечения reusable knowledge — конец сессии.

К этому моменту уже понятно:

  • что действительно изменилось;

  • какие решения были приняты;

  • какие подходы не сработали;

  • что надо проверить позже;

  • какие старые записи стали устаревшими.

Так появился session close pipeline.

close_session

close_session анализирует summary законченной работы и строит план:

  • new — создать новую запись;

  • update — обновить существующую;

  • merge — объединить дубли;

  • outdate — пометить старое знание как устаревшее;

  • raw_only — сохранить только сырое summary без автоматических выводов.

Главное — он не обязан всё применять сразу.

Каждое действие получает rationale и risk level. Низкорисковые действия можно применить автоматически, спорные уходят в review queue.

Session modes

Потом появился ещё один слой: режим сессии.

Кодинг-сессия и incident-сессия должны закрываться по разным правилам.

Для обычной coding-сессии можно агрессивнее auto-apply low-risk updates. Для incident или migration лучше review-first: там цена неверного вывода выше.

Поэтому session close учитывает mode:

  • coding;

  • incident;

  • migration;

  • research;

  • cleanup.

Это не “красивое enum-поле”. Это способ не применять одинаковую политику к разным типам инженерной работы.

Dead ends

Отдельно появился store_dead_end.

Это важная категория памяти, которую легко недооценить. Агенту нужно помнить не только что сработало, но и что уже пробовали и почему отказались.

Пример:

Пробовали ускорить deploy через parallel helm upgrades.Отказались: race в shared CRD migration, два релиза одновременно ломают schema ownership.

Через три месяца похожая идея снова выглядит заманчиво. Хорошая memory-система должна поднять прошлый dead end как warning, а не дать команде повторить тот же круг.


Урок 6. Автоматизация нужна, но только с review boundary

Следующий логичный шаг после close_session — интеграция с реальными агентами.

Для Claude Code появились hooks:

Hook

Что делает

SessionStart

вызывает context-inject и добавляет свежий контекст в начало сессии

SessionEnd

вызывает auto-capture, читает transcript и запускает extract -> plan -> apply

PreCompact

сохраняет checkpoint перед сжатием контекста

Это сильно меняет ergonomics.

Раньше агент должен был помнить: “в начале вызови recall, в конце сохрани summary”.

Теперь часть этого становится инфраструктурой.

Но тут есть опасность. Автоматическая память без review boundary быстро становится автоматическим загрязнением.

Поэтому hooks не делают “всё в canonical”. Они сохраняют raw summary, строят план, применяют безопасное и складывают спорное в review queue.

Dedup для checkpoint’ов

Ещё один практический момент: автоматические checkpoint’ы легко дублируются.

Если PreCompact или SessionEnd несколько раз сохраняют близкие summaries, память зарастает одинаковыми “session checkpoint” записями.

Поэтому появился Jaccard dedup:

  • сравниваем checkpoint с недавними записями в том же context;

  • если похожесть выше threshold, запись пропускается;

  • threshold/window/min length настраиваются через MCP_CHECKPOINT_DEDUP_*.

Это не сложная ML-задача. Это простая санитарная защита, без которой automation быстро портит corpus.

Performance hooks тоже имеет значение

Один из релизов был почти комично практичным: lightweight context-inject.

Хук SessionStart должен быть быстрым. Если для старта сессии поднимать весь Store, инициализировать embeddings, RAG и фоновые компоненты, пользователь будет ждать там, где он хотел просто начать работу.

В итоге context-inject получил read-only fast path: открыть SQLite в WAL mode=ro, взять недавние записи и pending summaries, вывести в stdout.

Вывод: hooks — это не просто “вызвать CLI”. Это часть UX. Они должны быть быстрыми, предсказуемыми и не ломать запуск инструмента.


Урок 7. Stewardship: память должна обслуживать сама себя

После lifecycle и session close стало понятно, что нужна отдельная подсистема обслуживания знаний.

Так появился stewardship layer.

Его задача — не отвечать на запросы агента, а поддерживать качество корпуса.

steward_run делает maintenance cycle:

  1. ищет дубли;

  2. ищет конфликты;

  3. ищет stale entries;

  4. предлагает canonical promotion candidates.

Действия делятся на:

  • safe_auto_apply;

  • review_required.

Спорные действия попадают в stewardship inbox.

Почему это отдельный слой

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

Но это плохая граница ответственности.

Recall должен быстро отвечать на вопрос. Stewardship должен периодически обслуживать базу знаний.

Это разные режимы:

  • recall path — latency-sensitive;

  • stewardship path — batch/maintenance, может делать больше анализа и оставлять audit trail.

Drift detection

Особенно полезным оказался drift_scan.

Память может ссылаться на файл, runbook или архитектурное решение. Но проект меняется. Файл удалили, runbook переписали, конфиг переехал.

Drift scan сравнивает memory against live sources и поднимает случаи вроде:

  • source file изменился после last verification;

  • source path больше не существует;

  • canonical entry давно не проверялась.

Для инженерного агента это критично. Хуже устаревшей документации только устаревшая память, которую агент считает актуальной.

Verification model

Появились:

  • verify_entry;

  • verification_candidates;

  • canonical health diagnostics.

Это кажется бюрократией, пока не видишь, как агент использует старую запись из прошлого месяца и уверенно предлагает неактуальный rollback path.

Для живого проекта знание без freshness и verification — это просто текст с неизвестной надёжностью.


Урок 8. Sedimentation: не вся память должна всплывать одинаково

Один из экспериментальных слоёв — memory sedimentation.

Идея такая: у памяти есть type, но type отвечает на вопрос “что это за знание?”.

Например:

  • episodic — событие;

  • semantic — факт;

  • procedural — процедура;

  • working — текущий контекст.

Но есть другой вопрос: насколько это знание load-bearing?

Для этого появился sediment_layer:

Layer

Поведение

surface

сессионное/задачное, всплывает только при совпадении context

episodic

участвует в recall с небольшим penalty

semantic

обычный слой

character

важное знание, которое должно всплывать почти всегда

Почему не заменить этим type?

Потому что это ортогональные измерения.

Инцидент может остаться episodic по природе, но со временем стать настолько важным, что его надо поднимать как character-level warning. Например: “никогда не запускать эту миграцию параллельно с deploy worker’ов”.

Sedimentation cycle предлагает переходы:

surface -> episodic -> semantic -> character

Тривиальные переходы можно auto-apply. Нетривиальные — через review queue.

Сейчас это экспериментальный слой. Но сама мысль оказалась полезной: relevance — это не только similarity. У памяти есть “геология”: свежие поверхностные записи, рабочие факты, подтверждённые знания и очень важные ограничения, которые должны переживать отдельные сессии.


Урок 9. RAG по Markdown нельзя строить только на char chunks

В первой версии RAG был простым:

  1. прочитать документ;

  2. разбить на чанки;

  3. построить embeddings;

  4. искать по cosine similarity.

Это работает, но плохо понимает структуру Markdown.

Большинство инженерных документов уже имеют смысловую структуру:

# Service Deployment## Rollback### Database migration rollback

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

В v0.7.0 появился structure-aware Markdown chunking:

  • парсится дерево заголовков;

  • каждый chunk получает breadcrumb;

  • шумные секции вроде TOC/References/Changelog можно отбрасывать;

  • у результата есть SectionPath и SectionKey;

  • ExpandSection позволяет достать весь раздел по pointer’у.

Это улучшает не только качество поиска, но и UX результата.

Вместо “вот похожие 800 символов” агент может получить: “это кусок из Deployment > Rollback > Database migration rollback, хочешь развернуть весь раздел?”

Для RAG по инженерным документам это важнее, чем кажется. Документация живёт разделами, а не случайными окнами символов.


Урок 10. Иногда нужен multi-hop recall

Single-hop recall отвечает на вопрос:

какие записи похожи на этот запрос?

Но часть инженерных вопросов устроена иначе:

почему мы выбрали X, какие feedback memories к этому привели, и какие incident/postmortem потом подтвердили или опровергли решение?

Это уже цепочка.

Для этого появился слой memory_triples:

subject --relation--> object

Triple extractor может асинхронно извлекать 3-7 связей из новой memory-записи. Потом recall_multihop делает graph walk по этим связям и возвращает не только найденные memories, но и path, который к ним привёл.

Примерно так:

billing migration -> caused -> index lockindex lock -> mitigated_by -> rollback runbookrollback runbook -> references -> incident 2026-02

Важно: это optional layer. Он требует LLM-backed extractor и backfill через:

agent-memory-mcp index-triples

Я не хочу делать вид, что graph memory бесплатна. Она добавляет complexity, latency, фоновые goroutines и новые failure modes.

Но для вопросов “как одно решение связано с другим” обычный semantic search часто слаб. Он ищет похожий текст, а не объясняет цепочку.


Урок 11. Shared service начинается с неприятных деталей

Первая версия была local-first. Это всё ещё базовая идея: один пользователь, локальная SQLite, stdio transport.

Но постепенно появился shared-service path:

solo local -> team laptop -> shared service

Для этого добавились:

  • Docker Compose recipe;

  • shared env template;

  • nginx reverse proxy example;

  • HTTP auth token;

  • безопасные дефолты для host binding;

  • threat model;

  • backup/restore docs;

  • Homebrew service mode;

  • config hot-reload для RAG-настроек.

HTTP defaults

HTTP mode теперь bind’ится на 127.0.0.1 по умолчанию.

Если хочется слушать 0.0.0.0, нужен MCP_HTTP_AUTH_TOKEN, либо явный небезопасный opt-in:

MCP_HTTP_INSECURE_ALLOW_UNAUTHENTICATED=true

Это тот случай, где лучше раздражать пользователя явной ошибкой, чем случайно открыть память проекта в сеть.

Brew services и hot-reload

Homebrew service preset тоже оказался не просто packaging.

Сервис может стартовать на login, хранить config в predictable path и подхватывать часть изменений без рестарта:

kill -HUP $(pgrep agent-memory-mcp)

Но здесь проявилась ещё одна практическая вещь: Homebrew sandbox не даёт post_install писать в ~/. Поэтому автоматическая настройка Claude hooks через brew install сначала появилась, потом была исправлена, потом убрана из post_install как неправильная граница.

Интеграции с пользовательским home directory должны быть явным agent-memory-mcp setup, а не скрытым side-effect пакетного менеджера.


Урок 12. SQLite всё ещё хороший выбор, но его надо уважать

В первой статье я защищал SQLite: один файл, простой backup, zero-ops, достаточно для локального и небольшого team-сценария.

Я всё ещё считаю это правильным выбором.

Но v0.8.0 появился не из теории, а из неприятного инцидента.

index_documents завис на SQLITE_BUSY примерно на 25 часов. Снаружи это выглядело как постоянная ошибка:

{  "code": -32000,  "message": "document indexing failed",  "data": "failed to mark index state dirty: ... database is locked"}

Диагноз:

  • внешний процесс БД не держал;

  • lock держал сам agent-memory-mcp;

  • на диске был rollback journal, хотя ожидался WAL;

  • одна write-транзакция не откатилась корректно;

  • без busy timeout любая contention превращалась в мгновенный hard error.

Корневой урок: “мы включили WAL в DSN” не равно “WAL реально включился”.

После этого появился internal/dbutil:

  • открыть SQLite;

  • явно применить PRAGMA busy_timeout=5000;

  • явно применить PRAGMA journal_mode=WAL;

  • проверить возвращённый mode;

  • выставить synchronous=NORMAL;

  • использовать общий helper и для memories.db, и для vectors.db.

Плюс аудит транзакций на defer tx.Rollback().

Плюс логирование ошибок index_documents/search в service log, а не только в JSON-RPC response.

Это скучная часть архитектуры. Но именно она отличает “игрушку, которую можно перезапустить” от инструмента, который можно оставить в фоне.


Урок 13. Performance проблемы появляются там, где появляется зрелость

Когда в системе были только store/recall/search, performance был простым.

Потом появились:

  • stewardship scans;

  • session tracker;

  • sediment cycle;

  • archive sweep;

  • multi-hop recall;

  • review queue;

  • background checkpoints.

И сразу всплыли N+1 и лишние roundtrip’ы в SQLite.

Например, один steward regression дал примерно 24x slowdown.

Причина оказалась не в “SQLite медленный”, а в архитектурной мелочи: cache-resident memory не хранил raw Metadata, а steward’у нужны были поля вроде service/type/lifecycle. Поэтому он ходил через Store.List, который тянул full corpus из SQLite.

Фикс:

  • добавить Metadata в cached memory;

  • сделать ListLightweight(filters) cache-only path;

  • переключить loadActiveMemories.

Benchmark на 2000 memories: примерно 32ms -> 8.6ms/op.

Похожая история была в других местах:

  • RecallMultihop делал Get per result -> заменено на batch getBatch;

  • getBatch получил chunking по 500 ids, чтобы не упереться в SQLite variable limit;

  • sediment cycle перестал перезагружать review queue на каждый candidate;

  • archive sweep стал pre-load’ить existing review items;

  • access stats стали flush’иться в одной транзакции;

  • session checkpoints ушли с hot path в bounded background pool.

Это нормальная эволюция. Сначала делаешь понятную модель. Потом реальные workflows показывают, где она N+1.

Главное — чинить root cause, а не добавлять sleep/retry вокруг симптома.


Что подтвердилось

Несмотря на большое количество изменений, несколько решений из 0.1.0 подтвердились.

Go подходит

Один бинарник, нормальная concurrency-модель, простой deploy, хорошая стандартная библиотека, удобный CLI/service story.

Для локального MCP-инструмента это всё ещё хороший выбор.

SQLite подходит

После WAL/busy-timeout hardening SQLite остаётся правильным default.

Postgres или vector DB не нужны на старте. Они добавили бы operational burden раньше, чем появилась бы реальная необходимость.

Типизированная память лучше плоских заметок

episodic/semantic/procedural/working пережили все изменения.

Поверх них появились lifecycle, trust, sediment layers, engineering taxonomy, но базовая идея осталась: память агента не должна быть одним мешком строк.

MCP как граница интеграции работает

Один memory layer можно подключать к разным агентам и клиентам. Это по-прежнему главная причина существования проекта.


Что пришлось пересмотреть

“Semantic search решает recall”

Нет. Semantic search — только один сигнал.

Для инженерного знания нужен hybrid retrieval, metadata, freshness, source type, keyword matching, lifecycle и sometimes reranking.

“Fallback между embedding-провайдерами повышает надёжность”

Не в такой форме.

Fallback между разными embedding spaces создаёт silent correctness bug. Теперь смена модели — это migration через reembed, а не прозрачное runtime-переключение.

“Пусть агент сам сохраняет важное”

Частично работает, но недостаточно.

Нужны session close, hooks, dedup, review queue и policies. Иначе агент либо забывает сохранить важное, либо сохраняет слишком много.

“Память можно чистить вручную”

На маленьком corpus — можно.

Как только появляются auto-capture, checkpoints, incidents, runbooks, migrations и canonical entries, нужен stewardship.

“SQLite WAL включается одной строкой”

Лучше проверить.

И лучше иметь busy timeout, rollback discipline, logging и shutdown drain.


Что всё ещё не идеально

v0.8.0 не делает проект “завершённым”.

Открытые зоны:

  • testability и architectural splits ещё продолжаются (T57/T58);

  • некоторые файлы всё ещё слишком большие (tools_registry, rag, config);

  • multi-hop layer требует optional LLM extractor и backfill;

  • sedimentation пока experimental и требует больше production data;

  • local neural reranker описан как design, но не реализован;

  • для больших team-corpus всё ещё может понадобиться другой storage/retrieval backend.

Важно другое: теперь эти ограничения видны явно. Они не спрятаны под видом “у нас semantic search, значит всё хорошо”.


Практические выводы, если вы строите похожую систему

1. Записывайте embedding model рядом с embedding.
Не смешивайте вектора разных моделей в одном similarity search. Silent mismatch хуже ошибки.

2. Делайте local-first путь простым.
Один layout данных, один smoke test, один быстрый старт. Memory tool должен начать приносить пользу до того, как пользователь ушёл читать документацию.

3. Не доверяйте одному score.
Для инженерных запросов cosine similarity — только часть ranking. Добавляйте source type, keyword matching, freshness, confidence и domain-specific boosts.

4. Разделяйте raw memory и maintained knowledge.
Сырые session summaries полезны, но агенту нужен canonical/project-bank слой.

5. Любая автоматическая запись должна иметь санитарные границы.
Dedup, review queue, risk levels, mode-aware policy. Иначе automation превращает память в мусор быстрее, чем ручной ввод.

6. Учитывайте старение знания с первого дня.
Outdated/superseded/verified/freshness — это не enterprise bureaucracy, а защита от уверенно неправильного контекста.

7. Эксплуатационные мелочи не вторичны.
WAL, busy timeout, graceful shutdown, service logs, auth defaults — всё это напрямую влияет на доверие к memory layer.


Заключение

Первая версия agent-memory-mcp отвечала на вопрос:

как дать AI-агенту persistent memory?

После нескольких месяцев использования вопрос изменился:

как сделать так, чтобы агент вспоминал правильное, актуальное, проверенное и полезное знание, а не просто похожий текст из прошлого?

Именно поэтому проект вырос в сторону hybrid retrieval, session capture, canonical knowledge, stewardship, sedimentation, multi-hop graph recall и operational hardening.

Самый важный вывод для меня: memory для AI-агентов — это не storage feature. Это жизненный цикл знания.

Знание появляется в сессиях, проходит через review, становится canonical, стареет, конфликтует с новыми фактами, иногда превращается в dead end, иногда становится load-bearing правилом проекта.

Если всё это не моделировать, агент будет “помнить”, но не обязательно будет помнить правильно.

Репозиторий: github.com/ipiton/agent-memory-mcp

ссылка на оригинал статьи https://habr.com/ru/articles/1033388/