> Практический кейс интеграции LLM Wiki и локального поиска в рабочий процесс Cursor. Ссылка на исходные конфигурации и skills — в конце статьи.
Большой контекст в Cursor помогает ровно до момента, когда проект начинает жить дольше одного чата. В одной сессии обсуждается исходная постановка задачи, в другой — устройство витрины, в третьей внезапно выясняется, что показатель должен считаться немного иначе, а ещё через неделю приходится заново объяснять агенту, почему первое очевидное решение уже было отвергнуто.
С кодом ситуация хотя бы относительно понятна: он лежит в репозитории, его можно перечитать. С требованиями, договорённостями и результатами исследований всё сложнее. Они распределяются между транскриптами встреч, сообщениями в чате, SQL-запросами, заметками и памятью участников проекта.
Ранее эту проблему я рассматривал со стороны встреч в AudioToText: запись превращается в структурированный транскрипт, затем поверх него можно выделять сущности и сопоставлять позиции участников. В текущем проекте возник следующий вопрос: как довести этот материал до coding agent так, чтобы он не просто находил отдельный фрагмент по запросу, а постепенно получал поддерживаемую картину проекта?
Отправной точкой стала концепция LLM Wiki Андрея Карпати. В ней между сырыми источниками и ответами модели появляется отдельный слой — связанная Markdown-wiki, которую агент дополняет по мере появления новых материалов. Сырые документы остаются неизменными, а производное знание постепенно накапливается в тематических страницах, индексе и журнале изменений.
На бумаге всё выглядело достаточно просто. На практике путь прошёл через настройку QMD, попытку вынести ingest в DeepSeek Harness, локальную модель, отдельный MCP-контур и, наконец, возврат к более скучной, но устойчивой схеме: Cursor, набор project skills и вызов Cursor CLI для фоновой обработки.
В этой статье разберу именно этот путь — включая решения, от которых пришлось отказаться. Это не ещё один гайд по созданию raw/, wiki/ и index.md: основной предмет здесь — граница между LLM-инструкцией и детерминированной оркестрацией, то есть что разумно оставить внутри skill, что вынести во внешний скрипт и в какой момент отдельный agent harness оказывается лишним.
Что хотелось получить
Задача появилась не из желания завести ещё одну папку с Markdown-файлами. Нужен был рабочий контур для задач, в которых контекст складывается постепенно.
Типичный сценарий выглядел так:
-
На установочной встрече заказчик формулирует исходные требования.
-
Запись проходит через AudioToText и превращается в транскрипт.
-
Во время анализа модели данных появляются SQL-фрагменты, ограничения и гипотезы.
-
В Cursor уточняется реализация конкретного блока.
-
Часть решений меняется после проверки на реальных данных.
-
Через несколько сессий агенту снова требуется весь этот контекст.
Просто прикладывать к каждому чату все документы можно, но это плохо масштабируется. Модель каждый раз повторно разбирает один и тот же материал, значительная часть контекстного окна уходит на уже известные вещи, а противоречащие друг другу формулировки приходится разрешать заново.
Архитектурная основа взята в готовом виде из концепции LLM Wiki Карпати: неизменяемый слой сырых источников, отдельный слой синтезированного знания и обязательная привязка каждого утверждения к первоисточнику. Чтобы превратить эту схему из концепта в повседневный рабочий инструмент внутри Cursor, к ней добавились прикладные инженерные требования:
-
из текущего чата в Cursor должно быть легко зафиксировать полезный фрагмент диалога и отправить его в сырые источники;
-
фоновый ingest хотелось вынести на локальные модели — на старте задача казалась достаточно прямолинейной, чтобы не тратить на неё облачные лимиты;
-
поиск по накопленной базе должен работать локально и быстро;
-
все операции должны запускаться детерминированно — одними и теми же командами, а не зависеть от того, насколько удачно сформулирован очередной промпт.
Последний пункт оказался важнее, чем казалось в начале.
Базовая структура wiki
В проекте используется каноническая структура LLM Wiki: неизменяемый слой raw/ для первоисточников, синтезируемый слой wiki/ (с разбивкой на sources, entities, concepts и analyses), а также SCHEMA.md и AGENTS.md с правилами валидации и инструкциями для агента. На случай роста базы в исходной заметке Карпати в качестве опционального локального поисковика упоминается QMD — он также сразу вошёл в нашу схему:
PharmacyCategories/├── raw/│ ├── assets/│ └── 2026-10-17--initial-audit.md├── wiki/│ ├── sources/│ ├── entities/│ ├── concepts/│ ├── analyses/│ ├── index.md│ ├── overview.md│ └── log.md├── AGENTS.md└── SCHEMA.md
PharmacyCategories/ — отдельная база знаний внутри проекта. Такая организация позволяет разделять знания по предметным областям, то есть самостоятельным бизнес-контурам. Например, сначала хранилище может содержать только данные о продажах, а позднее в него добавляется логистика, которую необходимо связать с продажами для сквозной аналитики. Для логистики можно создать соседнюю базу с такой же структурой, сохранив связи с сущностями из блока продаж. Каталог wiki/ каждой базы регистрируется в QMD как отдельная коллекция, поэтому при поиске можно обращаться к нужной предметной области. Исходники из raw/, код и остальные файлы проекта в поисковую выдачу не попадают. К материалам из raw/ агент обращается напрямую только во время ingest и проверки источников.
Поскольку базовая структура каталогов и назначение операций ingest, query и lint уже описаны в материалах о LLM Wiki, повторять их обоснование нет смысла. Дальше речь пойдёт о конкретной реализации этой схемы в Cursor: настройке QMD, выборе модели для ingest, оформлении операций в skills и их запуске через Cursor CLI.
Почему одного Markdown мало
Пока в wiki несколько десятков страниц, достаточно открыть index.md и пройти по ссылкам. Но хотелось заранее проверить сценарий, в котором источников и связанных страниц становится заметно больше.
QMD — локальный поисковый движок для Markdown, который сочетает полнотекстовый BM25-поиск, векторный поиск, query expansion и reranking. Его базовый workflow состоит из двух этапов: сначала найти кандидатов, затем получить полные документы через get или multi_get.
Коллекция описывалась в конфигурации QMD примерно так:
collections: pharmacy-categories: path: /Users/user/Sensu/PharmacyCategories/wiki pattern: "**/*.md" context: "": "База знаний блока категорий аптек: sources, entities, concepts, analyses." concepts: "Бизнес-термины, правила и алгоритмы расчёта (LFL, What-If моделирование)." entities: "Компоненты системы: Oracle, DWH, OLAP-куб, витрины и дашборды."
После настройки коллекции индекс обновляется командами qmd update и qmd embed -c pharmacy-categories, а сам поиск выполняется в два шага: сначала отбор кандидатов (search, vsearch или гибридный query), затем чтение нужных карточек по docid через get или multi-get.
Примеры команд поиска и чтения карточек в QMD
# точный термин или имя таблицы (BM25)qmd search "T_BI_CAT_MODELING" -c pharmacy-categories -n 5# семантически близкие страницы (векторный поиск)qmd vsearch "сопоставление категорий между периодами" -c pharmacy-categories -n 5# гибридный поиск с query expansion и rerankingqmd query "как работает Like-for-Like для категорий" -c pharmacy-categories -n 5# чтение выбранных документов по docidqmd get "#a1b2c3"qmd multi-get "#a1b2c3,#d4e5f6"
Такое разделение оказалось полезным не только ради скорости. В контекст агента сначала попадает короткий список кандидатов, а уже затем — содержимое нескольких действительно релевантных страниц. QMD поддерживает поиск по docid, пакетное чтение и получение диапазона строк, что позволяет не затягивать в prompt целый большой документ.
Здесь важно уточнить: в этой реализации QMD используется не как самостоятельная RAG-система поверх сырых документов, а как retrieval-компонент поверх уже синтезированных страниц wiki/. В классическом RAG первичные источники обычно нарезаются на чанки, а найденные top-k фрагменты автоматически подмешиваются в prompt перед генерацией ответа. QMD в нашей схеме решает более узкую навигационную задачу: он ранжирует поддерживаемые Markdown-карточки с помощью полнотекстового и семантического поиска, но не подменяет ими рабочий контекст целиком. Агент сначала видит компактную карту релевантных страниц, а затем сам решает, какие карточки или диапазоны строк открыть. За счёт этого в контекст попадает не случайный набор сырых чанков, а связанное, структурированное и привязанное к источникам знание из LLM Wiki.
Отдельную роль при работе через MCP-сервер играет поле context. В QMD его можно задать как для всей коллекции, так и для отдельных папок или конкретных файлов. При поиске через MCP QMD возвращает эти описания вместе со сниппетами — они работают как навигационная подсказка для LLM. По ним модель сразу понимает назначение документа или раздела (это первоисточник, бизнес-правило или описание таблицы) и точнее решает, какие именно карточки стоит открывать полностью через get.
В итоге QMD был подключён к Cursor через MCP, и на этом этапе сложилась первая рабочая схема:
raw → LLM ingest → wiki → qmd update/embed → Cursor query (через QMD)
Но оставался открытый вопрос: какая модель должна выполнять ingest?
Первая попытка: DeepSeek Harness
Использовать основную модель Cursor для разбора каждого транскрипта было жалко. Ingest — это объёмная, но во многом рутинная работа: прочитать источник, классифицировать материал, найти связанные страницы, обновить frontmatter, индекс и журнал.
Логичным казалось вынести эту часть во внешний агентный контур и подключить к нему локальную модель через oMLX. На эту роль подошёл бы любой CLI-агент — например, Qwen Code или другие доступные альтернативы, — но хотелось поэкспериментировать с DeepSeek Harness (DSH). В теории разделение ролей выглядело хорошо:
-
Cursor отвечает за интерактивную разработку и работу с кодом;
-
DSH обслуживает wiki;
-
локальная модель выполняет дешёвый ingest;
-
QMD предоставляет обоим агентам общий поисковый слой.
Для DSH был подготовлен профиль с тремя компонентами:
-
QMD как stdio MCP-сервер.
-
Локальная директория со skills.
-
OpenAI-compatible endpoint oMLX с локальной моделью Qwen.
Упрощенный фрагмент профиля DSH
- id: qmd name: deepseek-ai/dsh-mcp-client config: serverName: qmd transport: stdio command: /opt/homebrew/bin/qmd args: [mcp] cwd: /Users/user/Sensu toolCallTimeoutMs: 180000- id: skill-filesystem name: deepseek-ai/dsh-skill-filesystem config: providerName: sensu-workspace includeDefaultRoots: false customSkillDirs: - /Users/user/Sensu/.dsh/skills- id: agent-default-model name: deepseek-ai/dsh-agent-default-model config: provider: omlx model: Qwen3.5-9B-MLX-8bit
Отдельный skill llm-wiki-ingest в DSH должен был:
-
Проверить состояние коллекции QMD.
-
Найти новый raw-файл.
-
Прочитать
SCHEMA.mdи существующий индекс. -
Найти связанные страницы через QMD.
-
Создать source page.
-
Обновить entities, concepts и analyses.
-
Дополнить
index.mdиlog.md. -
Запустить переиндексацию.
То есть DSH рассматривался не как ещё один чат, а как отдельный исполнитель хорошо определённой операции.
Почему DSH не стал финальным решением
Сразу зафиксирую главное: отказ произошёл не из-за того, что на связке DSH и локальной модели не удалось получить качественный ingest — технически контур полностью заработал: DSH видел skill, MCP подключался к QMD, а модель читала источники и обновляла карточки по схеме.
Решающей стала эксплуатационная цена отдельного контура и слишком большая задержка для интерактивного workflow внутри IDE.
Во-первых, появилось сразу несколько независимых контуров конфигурации:
-
правила wiki;
-
skills Cursor;
-
skills DSH;
-
профиль DSH;
-
параметры локального inference-сервера;
-
MCP-настройки Cursor;
-
MCP-настройки DSH;
-
QMD-коллекции и модели эмбеддингов.
Каждый компонент по отдельности выглядел понятным. Вместе они создавали систему, в которой сбой ingest мог быть связан с моделью, skill, профилем, cwd, MCP, таймаутом или индексом QMD.
Во-вторых, первоначальный выбор компактной модели Qwen3.5-9B-MLX-8bit оказался приемлемым для классификации и чернового конспекта, но менее стабильным на многошаговой записи в несколько файлов. Чем больше в skill появлялось защитных инструкций, тем сильнее он начинал напоминать отдельное приложение, только реализованное в Markdown. Позже проблему качества удалось полностью закрыть переходом на mlx-community/Qwen3.8-27B-OptiQ-4bit с отключённым reasoning: в связке с DSH эта модель справляется с задачей на отлично, и сам контур остаётся полностью рабочим. Единственный минус упирается уже в физику локального инференса — время выполнения операции напрямую зависит от скорости генерации токенов (token generation), которая лимитируется пропускной способностью памяти (memory bandwidth) устройства. В моих экспериментах инференс выполнялся на Mac Studio M4 Max, где скорость token generation составила около 15 токенов в секунду.
В-третьих, экономия токенов подписки не равна снижению общей стоимости системы. Локальный ingest добавил холодный запуск модели, ожидание генерации, контроль MCP-процесса и диагностику профиля. Для фоновой пакетной обработки без спешки это вполне рабочий вариант. Для интерактивного режима, когда несколько raw-файлов хочется быстро разобрать без отрыва от основной задачи в IDE — уже не всегда.
Наконец, главная проблема была организационной: рабочая точка входа всё равно оставалась в Cursor. Именно там обсуждалась задача, создавался важный контекст и принималось решение, что сохранить. Переключение в отдельный UI ради обслуживания wiki ломало сценарий.
После нескольких итераций стало понятно, что нужен не второй интерактивный агент, а единый набор операций внутри Cursor. DSH остался полезным экспериментом: он помог отделить pipeline от конкретной IDE и проверить локальную модель. Но в финальной схеме от него было решено отказаться.
Если свести всю эволюцию контура в одну таблицу, путь от первого прототипа до финальной реализации выглядел так:
|
Итерация |
Исполнитель |
Что получилось |
Почему пошли дальше / результат |
|
1. QMD + ручной ingest |
Основной чат Cursor |
Рабочий прототип базы и поиска |
Чтение источников и запись карточек засоряют окно рабочего чата |
|
2. DSH + |
Отдельный harness + oMLX |
Быстрый локальный разбор простых заметок |
Нестабильность на многошаговой записи в несколько файлов |
|
3. DSH + |
Отдельный harness + oMLX |
Валидация |
~15 минут ожидания на документ и дублирование контуров конфигурации |
|
4. Чистый |
Основной чат управляет CLI |
Быстрый ingest (~4,5 мин) в режиме |
Цикл |
|
5. Тонкий skill + wrapper + CLI |
|
Финальная схема |
Ровно один вызов |
Финальная схема: skills в Cursor
Итоговое решение получилось заметно проще:
Cursor chat │ ├── /save-raw ───────→ raw/YYYY-MM-DD--slug.md │ ├── /ingest-cli ─────→ run-ingest.sh │ ├── Cursor Agent CLI (--model auto) → wiki/ │ ├── validate-wiki.py (детерминированный lint) │ └── qmd update / embed / cleanup │ ├── /context-cli ────→ run-context.sh → описания context в .qmd/index.yml │ └── Query / Lint ────→ AGENTS.md + skill qmd → поиск, ответ с источниками и аудит
Операции были разделены между правилами хранилища (AGENTS.md) и четырьмя прикладными project skills в .cursor/skills/. Сам формат skill — это SKILL.md с YAML-frontmatter, Markdown-инструкциями и, при необходимости, вспомогательными скриптами; такая упаковка позволяет превратить длинный промпт или многошаговый пайплайн в повторно используемую операцию с понятным триггером.
Вместо одного универсального «обслужи wiki» появились четыре чётко разграниченных skill:
|
Skill |
Назначение |
|
|
Сформировать аналитическую выжимку последних $N$ ответов текущего диалога и сохранить её в |
|
|
Тонкая оболочка над |
|
|
Тонкая оболочка над |
|
|
Унифицировать работу агента с локальным поиском QMD (структурированные запросы |
При этом обычные исследовательские запросы к базе (Query) и смысловой аудит (Lint) не потребовали отдельных slash-команд: они описаны напрямую в AGENTS.md и используют стандартный инструментарий чата в связке со skill qmd и MCP-сервером.
Такое разделение оказалось принципиальным. У каждого skill есть один ожидаемый результат, а тяжёлые пакетные операции (ingest-cli и context-cli) вынесены в детерминированные обёртки со своими проверками.
Skill /save-raw
Самой практичной частью повседневной работы стал save-raw. Он нужен в ситуации, когда полезный контекст появился прямо внутри диалога с Cursor: агент разобрал сложный SQL, нашёл ограничение модели данных, проверил прототип дашборда или сформулировал архитектурное решение.
Копировать всё вручную неудобно, а автоматически сохранять каждый ответ — значит быстро превратить raw/ в свалку. Поэтому операция запускается явно и принимает параметр N — количество последних содержательных ответов модели в текущей сессии (по умолчанию N = 1).
Пример вызова:
/save-raw 4
Skill выполняет несколько шагов:
-
Берёт последние $N$ ответов ассистента вместе с исходными вопросами пользователя, исключая сам вызов команды (при необходимости сверяясь с транскриптом сессии).
-
Очищает материал от диалогового шума, приветствий и служебных реплик, оставляя только фактологию, доменную логику, формулы и ссылки на объекты кода.
-
Формирует заголовок, kebab-case slug и раскладывает выжимку по фиксированной структуре из четырёх разделов.
-
Сохраняет документ в
raw/YYYY-MM-DD--<slug>.mdи выводит краткий отчёт. -
Не редактирует содержимое файла после записи.
Пример создаваемого raw-файла
# Аналитический аудит прототипа Power BI: расхождение расчёта категорий**Дата составления:** 2026-10-16 **Контекст:** Проверка логики расчёта категории между витриной DWH и OLAP-кубом. **Охват сессии:** Последние 4 ответа модели в сессии диалога Cursor ---## 1. Резюме и ключевые выводыРасхождение появляется до загрузки в OLAP на этапе предрасчёта в витрине...## 2. Детальный разборДля исторического периода используется версия классификатора на дату расчёта...## 3. Технические артефакты и объектыТаблицы `T_BI_CAT_MODELING`, процедура пересчёта, меры куба...## 4. Открытые вопросы и дальнейшие шагиТребуется подтвердить правило для ретроспективного пересчёта LFL...
Важно, что save-raw не пытается сразу раскладывать знания по карточкам wiki/. Строго говоря, для сессий в Cursor такой файл уже является производным артефактом модели, а не дословным логом чата: в данном workflow raw/ означает не обязательно побайтовую копию первичного канала, а неизменяемый входной снимок (snapshot) для последующего ingest. Для внешних встреч и документов таким артефактом выступает исходный транскрипт или заметка, а для диалогов в Cursor — зафиксированная в момент сохранения выжимка с указанием охвата сессии, тогда как полный первичный диалог при необходимости остаётся доступен в JSONL-транскриптах самого IDE.
Разделение фиксации в raw/ и последующего синтеза в wiki/ защищает от смешивания двух этапов обработки: классификация, связывание с существующими сущностями и интеграция в граф выполняются отдельно на этапе ingest. Если схема wiki или логика разбора через месяц станут лучше, сохранённый в raw/ входной артефакт всегда можно пересобрать в карточки повторно.
Ingest и поддержка контекстов через Cursor CLI (/ingest-cli и /context-cli)
Оставался вопрос с разбором накопленных raw-файлов. Выполнять многошаговый ingest прямо в текущем чате Cursor неудобно: операция читает много файлов, занимает контекстное окно и мешает основной задаче.
Здесь пригодился подход, который ранее уже разбирался в статье про генерацию технической документации на связке LangGraph + Cursor CLI + MCP: основной workflow запускает отдельный экземпляр Cursor Agent CLI и передаёт ему одну ограниченную задачу. В том проекте CLI-агент вызывался итеративно для каждой меры Power BI, а управление оставалось во внешнем LangGraph-контуре.
Почему одного SKILL.md оказалось мало
В LLM Wiki полноценный LangGraph оказался избыточным, однако и «чистая» реализация skill через один только SKILL.md с текстовой инструкцией вызвать Cursor CLI не подошла. В первой итерации /ingest-cli был написан именно так: предполагалось, что агент в чате прочитает SKILL.md, найдёт необработанные файлы, вызовет в терминале agent --model auto, а затем обновит индекс QMD. На практике выяснилось, что при такой схеме управление процессом и трассировка всех промежуточных шагов остаются за моделью основного чата, из которого вызывается команда:
-
Предварительные шаги до запуска CLI: основная модель сама выполняла
Globи чтениеwiki/sources/*.md, чтобы сопоставить их сraw/и собрать промпт для CLI (3–4 инструментальных шага); -
Цикл ожидания фонового процесса: инструмент
Shellв Cursor по умолчанию ждёт завершения команды 30 секунд (block_until_ms: 30000), после чего переводит её в фон. Поскольку разбор документа через CLI занимал около 4,5 минут, основная модель в чате каждую минуту просыпалась, вызывалаAwaitShellи читала лог терминала (Read terminal), на каждом таком шаге заново прокачивая через себя весь накопленный контекст диалога; -
Пост-обработка после CLI: завершив вызов агента, основная модель тратила ещё несколько шагов на запуск
qmd update && qmd embed, проверку ссылок и опрос MCP-сервера.
В результате вместо экономии получалось около десятка лишних шагов тяжёлой модели основного чата, хотя вся идея заключалась в том, чтобы переложить работу на CLI в режиме auto.
Что делает run-ingest.sh
Чтобы свести участие модели основного чата к ровно одному инструментальному вызову, всю оркестрацию пришлось вынести из SKILL.md во внешний bash/python-wrapper run-ingest.sh. Сам SKILL.md превратился в жёсткую «тонкую оболочку»: агенту чата запрещено самостоятельно исследовать raw/, wiki/ или терминалы — его единственное действие при вызове /ingest-cli состоит в однократном запуске run-ingest.sh с увеличенным таймаутом ожидания (block_until_ms: 1800000 и required_permissions: ["all"], поддерживаются также режимы --plan, --force и передача конкретного файла).
Скрипт run-ingest.sh внутри одного процесса терминала берёт всю механику на себя:
-
Находит в
raw/все.mdи.txtфайлы (кромеassets/), для которых вwiki/sources/*.mdещё нет карточки с соответствующим полемresource: raw/.... -
Для каждого нового файла поочерёдно запускает изолированный процесс Cursor Agent CLI с моделью
auto; -
При этом вложенному агенту явно запрещено трогать
raw/, правила схемы или запускать индексацию. Его задача — только обновить карточкиwiki/, каталогindex.mdи журналlog.md; -
После того как агент завершил запись, внешний скрипт сам запускает детерминированный валидатор
validate-wiki.py, а затем выполняет обслуживание индекса (qmd update,qmd embed -c pharmacy-categories,qmd cleanup) и сравнивает контрольные суммы файлов до и после запуска, выводя в чат аккуратную сводку созданных и обновлённых страниц.
Пример итогового вывода run-ingest.sh
Wiki validation passed: 4 sources, 13 indexed cards, 0 errors.Ingest завершён успешно.Обработанные ресурсы:- raw/2026-10-17--powerbi-prototype-audit-against-customer-mockup.mdИзменённые wiki-файлы:- wiki/analyses/scenarios-open-questions-and-limitations.md (updated)- wiki/concepts/counterfactual-category-simulation.md (updated)- wiki/concepts/demand-price-elasticity.md (created)- wiki/concepts/like-for-like-comparison.md (updated)- wiki/entities/olap-cube.md (updated)- wiki/entities/oracle-dwh.md (updated)- wiki/entities/pharmacy-category.md (updated)- wiki/entities/power-bi-category-modeling-prototype.md (created)- wiki/index.md (updated)- wiki/log.md (updated)- wiki/overview.md (updated)- wiki/sources/2026-10-17--powerbi-prototype-audit-against-customer-mockup.md (created)QMD: документов — 17, векторов — 17.
Локальный DSH против Cursor CLI
Отдельно стоит отметить флаг --model auto. В моём тарифе и сценарии использования этот режим оказался самым практичным компромиссом для обслуживания базы знаний: он даёт достаточное качество при разборе источников, связывании карточек и соблюдении SCHEMA.md, не расходует квоту выбранной вручную флагманской модели из основного coding-чата и при этом работает заметно быстрее локального инференса.
Нагляднее всего разницу показал контрольный прогон на одном и том же состоянии базы (10 предметных карточек, 119 wikilinks) и одном входном документе из raw/ (~6,2 КБ, 89 строк с таблицей трассировки требований и формулами). Оба контура — локальный DSH + mlx-community/Qwen3.8-27B-OptiQ-4bit + oMLX (с отключённым reasoning и предварительно прогретым сервером на Mac Studio M4 Max) и Cursor Agent CLI с --model auto — работали по одним и тем же правилам AGENTS.md и SCHEMA.md поверх общей коллекции QMD:
|
Метрика |
|
|
|
Общее время выполнения (Wall time) |
~15 мин 45 сек ( |
~4 мин 36 сек ( |
|
Выделение новых страниц |
1 карточка источника ( |
1 карточка источника + 2 новые карточки ( |
|
Обновление связей в графе |
Точечно обновлены 3 предметные карточки (всего затронуто 7 файлов) |
Сквозное обновление 6 предметных карточек + актуализация |
|
Соблюдение |
|
|
Разумеется, это не синтетический бенчмарк моделей, а одиночный контрольный замер wall time двух конкретных рабочих контуров: на результат влияют также реализация agent loop, механика tool calling и то, насколько глубоко каждая среда решила обновить смежные карточки графа. Тем не менее для повседневной работы разница оказалась наглядной — около 4,5 минут против 15 с лишним минут на локальной 27B-модели при более полной проработке связей.
Как обновляется QMD context
По такому же принципу устроен и второй инфраструктурный skill — /context-cli (run-context.sh + context_cli.py). Как упоминалось выше, QMD возвращает вместе с результатами поиска навигационные описания context из .qmd/index.yml. Поддерживать их вручную при росте базы утомительно, а перегенерировать описания для всех страниц после каждого ingest — накладно. Поэтому context_cli.py хранит в .qmd/context-cli-state.json слепок content_sha256 и context_sha256 для каждой карточки и папки wiki/:
-
в штатном режиме (без флагов) скрипт находит только новые объекты, у которых ещё нет записи в
context, и дописывает их; -
в режиме
--auditон сверяет текущие SHA-256 хеши файлов с сохранённым состоянием, помечает только реально изменившиеся карточки и их родительские директории и передаёт в read-only вызовCursor Agent CLI --model autoстрого этот дельта-список.
Например, когда после ingest в базе появилась карточка concepts/demand-price-elasticity.md и обновилась entities/power-bi-category-modeling-prototype.md, скрипт не заставляет модель перечитывать все 17 документов коллекции, а точечно обновляет в .qmd/index.yml только затронутые ключи:
context: concepts: "Слой концепций: ключевые бизнес-термины, правила и алгоритмы расчетов (LFL, сценарное What-If моделирование, регламентный перерасчет категорий)." concepts/demand-price-elasticity.md: "Коэффициент ε чувствительности объёма спроса к изменению цены при смене категории аптеки: в прототипе — What-If слайдер [0; 1.5] для корректировки расчётной валовой прибыли и поиска точки безубыточности перевода." entities/power-bi-category-modeling-prototype.md: "Интерактивный HTML/Chart.js прототип из шести экранов сценарного моделирования категорий аптек; эталон UX и метрик для целевого PBIX с покрытием макета заказчика около 98–100%."
Зачем вызывать Cursor CLI из Cursor
Причина не в рекурсии ради рекурсии. Вынос операции в отдельный процесс под управлением скрипта даёт несколько полезных свойств:
-
ingest и аудит контекстов не загрязняют контекстное окно текущего рабочего чата;
-
каждому вызову CLI-агента передаётся ровно один raw-файл и одна атомарная задача;
-
валидация структуры (
validate-wiki.py) и переиндексация QMD гарантированно выполняются детерминированным скриптом после работы модели, а не зависят от того, не забыла ли LLM вызвать команду в конце длинного промпта; -
одна и та же команда одинаково запускается из чата через
/ingest-cli, вручную из терминала или по расписанию; -
нет второго UI и отдельной системы профилей — используются те же
AGENTS.md,SCHEMA.mdи MCP-настройки репозитория.
То есть связка run-ingest.sh + Cursor Agent CLI работает здесь как изолированный конвейер сборки знаний со встроенным контролем качества.
Что происходит внутри ingest
Если внешний скрипт run-ingest.sh отвечает за детерминированную оркестрацию, валидацию и индексацию, то задача самого CLI-агента внутри вызова — не просто пересказать документ, а аккуратно встроить новый материал в существующий граф знаний без дублей.
Поэтому перед созданием любой новой страницы агент обязан выполнить поиск по уже накопленной базе. Например, новый материал может упоминать Like-for-Like, таблицу T_BI_CAT_MODELING и What-If-сценарий. Вместо трёх новых почти одинаковых страниц агент сначала ищет существующие карточки через QMD:
qmd query "Like-for-Like T_BI_CAT_MODELING What-If" \ -c pharmacy-categories -n 10
После этого полные кандидаты извлекаются через qmd get или multi_get. Такой порядок — search, затем retrieve — закреплён в skill qmd и не даёт базе расползаться на дублирующиеся заметки.
Как мы уже условились в начале статьи, подробно разбирать внутреннюю механику базовых операций ingest, query и lint здесь не будем — канонические правила работы с карточками, перекрёстными ссылками и разрешением противоречий детально описаны в исходной концепции LLM Wiki и других обзорных публикациях. В нашем проекте все эти регламенты зафиксированы в SCHEMA.md и AGENTS.md:
-
Сборка и обновление графа (
ingest): создание карточки источника (resource: raw/<filename>), слияние новых фактов с существующими карточкамиentities,conceptsиanalyses(с инкрементомsource_count, пополнениемCitationsи маркировкой противоречийcontested: true), а также обновлениеindex.mdиlog.md; -
Двухэтапный поиск и чтение (
query): формирование комбинированного запроса (intent,lex,vec,hydeс фильтром по коллекции) по правиламqmd/SKILL.md, чтение выбранных карточек черезqmd get/multi_getи синтез ответа со ссылками[[wikilinks]]; -
Контроль целостности (
lint): сочетает автоматическую проверку структуры и ссылок скриптомvalidate-wiki.pyна каждом прогоне с периодическим семантическим аудитом по регламентуAGENTS.md(поиск смысловых расхождений, осиротевших карточек и пропущенных связей).
Сквозной пример
Рассмотрим упрощённый сценарий из задачи по категориям аптечной сети.
Во время установочной встречи обсуждался отчёт по категориям и Like-for-Like-сравнение. Транскрипт был получен через AudioToText и сохранён в raw/.
Первый ingest создал:
wiki/sources/2026-09-21--requirements-meeting.mdwiki/concepts/like-for-like-comparison.mdwiki/entities/tb-icat-modeling.mdwiki/analyses/category-comparison-requirements.md
Позже в Cursor разбирался SQL-процесс. Выяснилось, что исторические данные используют другой вариант классификатора. Обсуждение оказалось важнее конкретного куска кода, поэтому последние четыре сообщения были сохранены командой:
/save-raw 4
После вызова /ingest-cli запустился изолированный ingest через Cursor CLI. Агент нашёл существующие страницы по Like-for-Like и T_BI_CAT_MODELING, обновил их, добавил секцию с расхождением правил, создал analysis page и дополнил log.md, а скрипт провалидировал ссылки и обновил индекс QMD.
В новой Cursor-сессии вопрос:
Какая версия классификатора должна использоваться при ретроспективном расчёте Like-for-Like?
уже не требовал повторно прикладывать транскрипт и SQL. Агент через QMD нашёл две тематические страницы и вернул не только текущее предположение, но и незакрытое противоречие между постановкой и фактической реализацией.
Именно в этот момент wiki начала приносить пользу. Не тогда, когда в каталоге появились красивые Markdown-файлы, а когда новая сессия смогла восстановить ход решения без пересказа всей истории.
Что оказалось лишним
Эксперимент помог отделить обязательные компоненты от тех, которые были добавлены скорее из архитектурного энтузиазма.
Отдельный agent harness
DSH был полезен как лабораторный стенд, но для текущего объёма источников оказался лишним эксплуатационным слоем. Если wiki будет обрабатывать сотни документов по расписанию, к нему можно вернуться. Для интерактивного проектного workflow проще использовать Cursor CLI как исполнителя.
Локальная модель для всей операции
Как уже описано выше, после смены модели вопрос качества локального ingest был снят, но осталось время ожидания. Для интерактивного цикла внутри IDE это оказалось решающим: запуск через Cursor CLI в режиме --model auto возвращает результат за минуты, а не за десятки минут. Локальный контур разумнее оставить для пакетной обработки без спешки.
Автоматическое сохранение всех диалогов
Полный журнал чатов создаёт ощущение надёжной памяти, но ухудшает соотношение сигнал/шум. Явный /save-raw N заставляет решить, действительно ли найденный контекст заслуживает долговременного хранения.
Что получилось в итоге
Финальная архитектура укладывается в четыре слоя без внешних агентных фреймворков:
┌─────────────────────────────────────────────┐│ Cursor ││ coding, обсуждение, /save-raw, запуск skill │└──────────────────┬──────────────────────────┘ │┌──────────────────▼──────────────────────────┐│ Wrapper-скрипты + Cursor Agent CLI ││ изолированный ingest, validate-wiki.py │└──────────────┬───────────────────┬──────────┘ │ │┌──────────────▼──────────┐ ┌─────▼──────────┐│ LLM Wiki │ │ QMD ││ sources/entities/ │ │ BM25/vector/ ││ concepts/analyses │ │ reranking │└──────────────┬──────────┘ └─────┬──────────┘ │ │┌──────────────▼───────────────────▼──────────┐│ Raw sources + schema + append-only log │└─────────────────────────────────────────────┘
В сравнении с первоначальным вариантом исчезли отдельный UI DSH, дублирующий набор DSH-skills и второй контур конфигурации. При этом сохранились все полезные свойства эксперимента: жёсткая граница между фиксацией источника (/save-raw) и компиляцией знания (/ingest-cli), QMD как единый retrieval-слой и выполнение тяжёлых операций в изолированном процессе с детерминированной проверкой.
Это решение нельзя назвать бесплатной памятью. Wiki требует схемы, контроля источников и периодической проверки. Но обслуживание становится предсказуемой инженерной операцией, а не просьбой к модели «пожалуйста, ничего не забудь».
Вместо заключения
Изначальная идея была довольно прямой: подключить LLM Wiki, добавить локальный поиск и поручить дешёвой модели раскладывать документы. По пути получилась небольшая распределённая система из двух агентных сред, двух наборов skills, MCP, inference-сервера и нескольких конфигураций.
Работать она могла. Пользоваться ею регулярно — уже другой вопрос.
Финальное решение оказалось менее эффектным: один рабочий интерфейс, четыре прикладных skills, QMD и отдельный вызов Cursor CLI для тяжёлых операций. Но именно такая схема пережила несколько реальных циклов сохранил контекст → обработал источник → продолжил задачу в новой сессии.
Главный вывод для меня здесь не в выборе конкретного инструмента. Долговременная память coding agent начинается не с векторной базы и не с размера контекстного окна — она держится на разделении ответственности между моделью и детерминированным кодом:
-
осознанная фиксация контекста: в неизменяемый
raw/попадает не весь поток чатов, а явно отобранная выжимка (/save-raw); -
двухэтапный retrieval: поиск через QMD только отбирает кандидатов с учётом
context, а ответ и обновление графа строятся после чтения конкретных страниц; -
изоляция и жёсткая оркестрация: LLM отвечает только за смысловое слияние фактов в изолированном вызове CLI, а порядок шагов, проверку инвариантов (
validate-wiki.py) и переиндексацию гарантируют обычные wrapper-скрипты.
Когда эти правила выполняются, LLM Wiki перестаёт быть папкой с заметками. Она становится частью рабочего контура разработки — наряду с кодом, тестами и документацией.
Исходники проекта и skills:
-
LangGraph + Cursor CLI + MCP для автоматической генерации документации
-
Репозиторий с конфигурацией,
SCHEMA.md,AGENTS.md,example-vaultи Cursor skillscursor-llm-wiki-qmd
ссылка на оригинал статьи https://habr.com/ru/articles/1095974/