3 апреля 2026 года Андрей Карпати описал реально работающую систему, где он складывает необработанные исследовательские материалы в папку, показывает их LLM, которая с нуля создает и поддерживает всю взаимосвязанную вики-систему. ИИ пишет статьи, создает обратные ссылки между связанными идеями, классифицирует концепции и постоянно обновляет всю систему по мере поступления новых материалов. Вот промпт, который всё это делает LLM Wiki gist
Его новый рабочий процесс превращает необработанные исследовательские материалы в самоподдерживающуюся вики, базу знаний без RAG, только файлы Markdown и LLM, которая выполняет функции библиотекаря. Вместо того чтобы просматривать необработанные документы по каждому запросу, как в RAG, здесь LLM считывает исходный материал один раз и компилирует его в структурированную, организованную вики-систему. Его исследовательская вики-страница по одной теме разрослась примерно до 100 статей и 400 000 слов.
Мы взяли эту идею и инфраструктурно доработали:
|
Аспект |
Karpathy LLM Wiki |
Наша реализация |
|---|---|---|
|
Хранение |
Markdown-файлы в папке |
AlloyDB + pgvector (SQL) |
|
Связи |
|
Типизированные рёбра графа (11 типов) |
|
Поиск |
Нет (или grep) |
Векторный + графовый гибрид |
|
Обновление |
LLM переписывает markdown |
SQL-функции + LLM-классификация из БД |
|
Доступ |
Локальная папка |
HTTP-сервер с авторизацией |
|
Мультипользовательность |
Нет |
Роли admin и reader |
|
Протокол |
— |
MCP (StreamableHTTP + SSE) |
|
Классификация связей |
LLM при записи |
|
Ключевое отличие: у Карпати wiki — это заметки Obsidian, которые LLM читает и пишет. У нас wiki — это база данных с графом, к которой несколько агентов обращаются через стандартизированный протокол. Карпати решал задачу «один человек + один LLM ведут заметки». Мы решаем задачу «несколько AI-агентов + человек совместно работают с растущей базой знаний». Разные масштабы — разная инфраструктура.
С чего всё начиналось
Домашняя wiki уже давно была назрела, так как проект оказался непрост. Смысл проекта — добавить блоки Titans в стандартную Gemma 3 и посмотреть что из этого выйдет. Технология Titans описана здесь
Конечно, сначала мы реализовали подход Karpaty, добавив векторный поиск по эмбеддингам.
Потом мы добавили граф знаний с типизированными рёбрами. Выбор технологии построения графа потребовал размышлений и занял некоторое время: Как выбирали технологию построения графа Но с ИИ-кодером реализация всегда быстрее, чем выбор технологий. Раз-раз, реализовали и увидели неплохой рост recall:
|
Метрика |
До (вектор) |
После (вектор + граф) |
|---|---|---|
|
Avg recall |
46.7% |
68.3% |
|
Enrichment |
— |
92 концепции найдены графом |
|
… И серьезный прирост на абстрактных запросах: |
|
|
|
Запрос |
Было |
Стало |
|---|---|---|
|
«Что общего у MesaNet и Titans» |
0% |
67% |
|
«Альтернативы Softmax attention» |
0% |
67% |
|
«Ассоциативное сканирование» |
50% |
100% |
|
«Дистилляция в TTT» |
67% |
100% |
|
Граф в общем не нужен на точных запросах. Но там, где человек спрашивает «как связано X и Y», граф находит путь: Линейные RNN → Ассоциативное сканирование → NLM → M3 Optimizer — три прыжка по концепциям, которые векторный поиск не найдёт. Граф – это очень круто. |
|
|
И всё было хорошо, пока wiki была личным инструментом. Сервер, который это всё обслуживал, был однопользовательским. stdio-транспорт — процесс запускается, обслуживает одного клиента, умирает. Это агент OpenClaw написал себе такую локальную базу знаний, и у него можно теперь спрашивать в телеграме. Точнее у нее, ее зовут Мнемозина, и она богиня знаний. Но. Хоть и прикольно писать код в телеграме, но иногда хочется вернуться в VS Code, да и проект, ради которого всё вот это, лежит на локальной машине. А значит нужен mcp-server. Stdio-транспорт MCP подразумевает, что сервер — это дочерний процесс клиента. Клиент подключился → сервер родился → клиент отключился → сервер умер. Для CLI-утилиты это нормально, но для сервера знаний, к которому стучатся 3-4 агента одновременно — не работает. Кроме того, чтобы подключить удалённого агента, приходилось пробрасывать порт через SSH-туннель со всеми паролями и явками, или запускать отдельный SSE-прокси. Кстати, запустили такой прокси, нормально отдает наружу. Но без авторизации. Все инструменты — и читающие, и пишущие — доступны любому, кто подключился. Агент-читатель случайно дёрнет graph_classify_edge — и перепишет типы рёбер.
Что изменилось в v3
StreamableHTTP + SSE в одном процессе
Мы перешли на двухтранспортную архитектуру в рамках одного Express-приложения:
POST/GET/DELETE /mcp → StreamableHTTP (новый протокол MCP 2025-11-25)GET /mcp/sse + POST /mcp/messages → SSE (legacy, протокол 2024-11-05)
StreamableHTTP — это свежий стандарт MCP transport. Один endpoint /mcp, на нем три HTTP-метода. Клиент может открыть долговременное соединение, сервер держит сессию в памяти через InMemoryEventStore. Старые клиенты, которые умеют только SSE, подключаются через /mcp/sse — и это тоже работает.
Зачем оставлять два транспорта? Не все MCP-клиенты уже перешли на StreamableHTTP. SSE-legacy гарантирует, что ничего не сломается. Зачем это в нашем камерном проекте? А чтобы было.
Ролевая модель: admin и reader
Два уровня доступа:
|
Роль |
Инструменты |
Что может |
|---|---|---|
|
admin |
Все read + |
Чтение + мутации графа |
|
reader |
|
Только чтение |
Роль определяется при подключении — через Basic Auth. Admin знает пароль админа, reader знает пароль читателя. Инструменты, недоступные роли, просто не регистрируются в MCP-сервере этой сессии. Ограничение здесь на уровне регистрации инструментов, а не runtime-проверки. MCP-клиент получает список доступных инструментов при initialize. Если инструмента нет в списке — он просто не появится в UI, не попадёт в tool_choice модели, не будет случайно вызван.
Кстати интересно получилось: мы дали другому агенту OpenClaw админский доступ, и он сказал, что инструменты изменения wiki он видит, но использовать не имеет права, потому что он простой кодер. То есть, дословно:
Сейчас у меня в подключены 15 инструментов через reader. Если я не прокинул write-инструменты — скорее всего это политика
tools.profile: "coding", которая фильтрует мутационные MCP-инструменты.
Разделение по ролям решается на сервере так:
function buildServer(role) { const server = new McpServer({ name: 'titans-wiki', version: '3.0.0' }); registerReadTools(server); if (role === 'admin') registerAdminTools(server); return server;}
При новом подключении — создаётся инстанс McpServer с нужным набором инструментов, подключается к выбранному транспорту. Всё.
Архитектура v3
┌─────────────────────┐ │ Express + CORS │ │ Port 8000 │ └──────────┬──────────┘ │ ┌──────────▼──────────┐ │ Auth Middleware │ │ Basic / Bearer │ └──────────┬──────────┘ │ ┌───────────────┼───────────────┐ │ │ ┌──────────▼──────────┐ ┌──────────▼──────────┐ │ StreamableHTTP │ │ SSE (legacy) │ │ /mcp │ │ /mcp/sse │ │ POST/GET/DELETE │ │ + /mcp/messages │ └──────────┬──────────┘ └──────────┬──────────┘ │ │ └───────────────┬───────────────┘ │ ┌──────────▼──────────┐ │ buildServer(role) │ │ ┌─ registerRead │ │ └─ registerAdmin │ (только admin) └──────────┬──────────┘ │ ┌──────────▼──────────┐ │ AlloyDB + pgvector │ │ graph_nodes (72) │ │ graph_edges (215) │ │ wiki_pages │ └─────────────────────┘
LLM из SQL: ai.generate() в AlloyDB
Один из самых изящных трюков — классификация рёбер прямо из базы данных:
CREATE OR REPLACE FUNCTION graph_classify_edge( _source_label TEXT, _target_label TEXT, _context TEXT DEFAULT NULL) ... _result := ai.generate(_prompt);...
AlloyDB Omni с версии 1.5.2 поддерживает google_ml_integration — можно вызвать Gemini прямо из plpgsql. Один пакетный прогон — и 205 нетипизированных рёбер получили конкретные типы (depends_on, develops, based_on). Стоимость: $0.01 на весь прогон.
Трудности, с которыми столкнулись
-
Направленность рёбер. MesaNet → Conjugate Gradient Solver — это depends_on? uses? used_in? Пришлось разделить uses (A использует B) и used_in (B применяется в A), плюс depends_on (A не может без B). Двукратный прогон классификации.
-
Дубли рёбер. После LLM-классификации одна пара страниц имела два ребра: depends_on и mentions. Функция
graph_dedup_edges()оставляет ребро с максимальным весом. -
authored_by наоборот. Gemini иногда ставила person → paper вместо paper → person. Правило:
authored_byтолько для конкретных работ, не для обзорных тем. -
Версия AlloyDB. Для
ai.generate()нуженgoogle_ml_integration ≥ 1.5.2. У нас была 1.4.3. Обновление контейнера с 16.8.0 до 16.11.0 с перепривязкой volume — аккуратная операция с данными на борту.
Многопользовательский сценарий: как это работает сейчас
У нас два OpenClaw-агента:
-
Мнемозина (основной) — подключается с ролью admin, может всё: искать, читать, обновлять граф, классифицировать рёбра.
-
Полифем (второй агент) — подключается с ролью reader, только читает.
graph_upsert_nodeиgraph_classify_edgeу него просто не появляются в списке инструментов. -
gemini-cli на локальной машине – reader
-
Cline в VS-Code – reader
Набор инструментов v3
Read-инструменты (доступны всем):
|
Инструмент |
Назначение |
|---|---|
|
|
Чтение содержимого страницы |
|
|
Связи-[[wikilinks]] из страницы |
|
|
Страницы по тегу / все теги |
|
|
Обратные ссылки на страницу |
|
|
BFS-обход от ноды |
|
|
Кратчайший путь между концептами |
|
|
Гибридный retrieval (вектор + граф) |
|
|
Все рёбра определённого типа |
|
|
Статистика графа + сироты |
|
|
Поиск противоречий |
Admin-инструменты (только admin):
|
Инструмент |
Назначение |
|---|---|
|
|
Создать/обновить ноду |
|
|
LLM-классификация типа ребра |
|
|
Удаление дублей, селф-лупов, разворот directed-ошибок |
Чему мы научились
-
Граф + вектор > вектор. На абстрактных запросах гибридный retrieval выигрывает у чистого векторного поиска. На точных — не проигрывает. Нет причин не использовать граф, если данные позволяют.
-
Типы рёбер — это не украшение, а архитектура. 11 типов с весами (depends_on = 0.95, mentions = 0.3) позволяют ранжировать hop-результаты осмысленно, а не просто «найдено на расстоянии 2».
-
Авторизация на уровне регистрации инструментов надёжнее runtime-проверок. Если инструмент не зарегистрирован — клиент его не видит. Модель его не вызывает. Пользователь его не ждёт.
-
LLM из SQL — killer feature для энтерпрайз-графов. Нет нужды в промежуточном слое: вызов Gemini прямо из plpgsql через
ai.generate()классифицирует рёбра. Кстати, там можно и эмбеддинги вычислять для строки, чтобы на SQL передавать текст параметром, а не вектор, но иногда этот вектор нужно повторно использовать на клиенте -
Два транспорта — таков миграционный путь. StreamableHTTP — будущее MCP. SSE — настоящее. Поддержка обоих в одном процессе означает, что клиенты переходят на новый протокол в своём темпе.
Что еще накрутили:
-
Графовый reasoning: не просто находить путь, но и объяснять его. «MesaNet связан с Titans через 3 хопа, потому что оба используют Surrogate Memory, которая основана на Fast Weight Programmers».
-
Авто-обнаружение противоречий: при добавлении нового источника проверять, не противоречит ли он существующим утверждениям.
Что еще интересно попробовать:
-
Версионирование рёбер: отслеживать, когда связь была создана и почему — для растущей wiki это становится критичным.
-
WebSocket-транспорт: для real-time уведомлений об изменениях графа.
Стек: Node.js, Express, AlloyDB Omni, pgvector, MCP SDK, Vertex AI (text-embedding-004), Gemini 2.5 Flash Lite (edge classification)
P.S. Кому интересно почитать про Titans, Miras, MesaNet и прочие технологии Test-Time Training (TTT), напишите, я дам подключение к этому MCP, он не опубликован на публичных хабах. А всякий научпоп – в канале @veriga_pro_AI
ссылка на оригинал статьи https://habr.com/ru/articles/1044200/