# План: поддержка агентских продуктов и заимствование техник GraphRAG/LightRAG

Дата: 1 ноября 2026 г. Статус: **этапы 1–5 и часть A этапа 6 выполнены** (фундамент — `docs/03_implementation/DOCGRAPH-FOUNDATION.md`, извлечение — `docs/03_implementation/DOCGRAPH-EXTRACT.md`, операции чтения — `docs/03_implementation/DOCGRAPH-OPS.md`, dual-level поиск — `PLAN.md` §22–23, темы — `PLAN.md` §24, агентские интеграции — `docs/03_implementation/AGENT-INTEGRATIONS.md` и `PLAN.md` §25); часть B этапа 6 и этап 7 не начаты.
Область: RAGRAF (C:/AutoClaw/RAGRAF), издания Community и Enterprise.

## 0. Резюме

Две независимые части, которые нельзя смешивать в одну правку:

* **Часть A — интеграции.** RAGRAF становится штатным источником фактов о коде
  и документах для агентских обвязок: OpenClaw, Hermes Agent, Claude Code,
  OpenAI Codex CLI, OpenCode, Cline, Pi, DeepSeek Harness, AutoClaw, Cursor.
  Поддержка означает не «упомянуть в README», а: конфигурация, смоук-прогон,
  автоматическая проверка в чекере и названные границы.
* **Часть B — документный граф.** RAGRAF закрывает слабое место по документам:
  опциональный LLM-проход извлечения сущностей и отношений для не-кодовых
  чанков, dual-level поиск, темы со сводками и четыре новые фиксированные
  операции. Техники берутся у GraphRAG и LightRAG, но переносятся **не как
  зависимости**, а как переписанные под инварианты RAGRAF решения.

GraphRAG и LightRAG ставятся **только для изучения** реализации — каждый в
отдельном временном окружении вне репозитория (путь стенда задаётся
настройкой и в репозитории не фиксируется). В поставку RAGRAF они не входят
и в зависимостях не появляются.

## 1. Инварианты, которые нельзя нарушить

Всё ниже — уже действующие правила продукта; новая работа обязана им
подчиняться, а не «адаптировать их под технику».

| Инвариант | Что это значит для новой работы |
|---|---|
| Фиксированные операции | никакого «выполнить запрос к графу»; только объявленные операции с известной схемой ответа |
| Доказательность | каждый факт несёт `path:line` либо адрес чанка и цитату; факт без адреса — догадка и не отдаётся |
| Иммутабельность ревизий + drift | слой пишется в ревизию и не меняется задним числом; расхождение фиксируется журналом |
| Degradation-aware | «нет данных» и «не собрано» различаются полем и причиной, а не пустым ответом |
| Единая поверхность | операция появляется сразу в фасаде, CLI, MCP (`TOOLS` + `_TOOL_META` + `_handlers`), документах и чекера |
| Числа в одном изменении | `README.md`, `INTEGRATION.md`, статья, `docs/AGENTS-context.md`, чекер — правятся тем же коммитом (`AGENTS.md:520`) |
| Свойство на каждую правку | тест в группе `core` и подсадка, доказывающая, что тест ловит возврат дефекта |

## 2. Часть A. Поддержка агентских продуктов

### 2.1 Что считается поддержкой: пять уровней

| Уровень | Что сделано | Проверяется |
|---|---|---|
| L0 | продукт установлен на машине-стенде (192.168.31.103) | `winget`/`npm`/`pip` лог + `--version` |
| L1 | конфигурация MCP и файл-шаблон в `integrations/<slug>/` | `tools/check_integrations.py --templates` |
| L2 | смоук-прогон: клиент запускает `ragraf_ensure` и `ragraf_search`, ответ содержит `path:line` | `integrations/<slug>/smoke.ps1`, код возврата |
| L3 | автоматическая проверка в чекере (46-я проверка `Test-AgentIntegrations`) | `tools/Invoke-ProjectDoctrine.ps1` |
| L4 | названные границы: что не работает и почему | раздел «Границы» в README интеграции |

**Требование к сдаче каждой интеграции — L1..L4. Уровень L0 — условие проверки,
но сам по себе поддержкой не считается.**

### 2.2 Матрица продуктов

| Продукт | Тип | Транспорт RAGRAF | Конфигурация | Смоук |
|---|---|---|---|---|
| OpenClaw | OSS-агент, gateway | MCP stdio | `openclaw.json` → `mcp.servers` | headless CLI |
| Hermes Agent (Nous) | self-improving агент | MCP stdio | конфиг агента (YAML) | headless CLI |
| Claude Code | coding-харнесс | MCP stdio | `.mcp.json` / `claude mcp add` | `claude -p` (headless) |
| OpenAI Codex CLI | coding-харнесс | MCP stdio | `~/.codex/config.toml` | `codex exec` |
| OpenCode | coding-харнесс | MCP stdio | `opencode.json` | `opencode run` |
| Cline | расширение VS Code | MCP stdio | `cline_mcp_settings.json` | нет (GUI) → чек-лист |
| Pi | coding-харнесс | MCP stdio | конфиг Pi | `pi` headless |
| DeepSeek Harness | desktop-харнесс | MCP stdio | `cordis.patch.yml` (insert с `id`) | нет (GUI) → чек-лист |
| AutoClaw | desktop-агент | MCP stdio + HTTP | Settings → Tools & connections / `openclaw.json` | GUI → чек-лист |
| Cursor | AI IDE | MCP stdio | `mcp.json` в настройках Cursor | нет (GUI) → чек-лист |
| Manus | облачный SaaS | MCP по HTTP, если клиент это умеет | нет локальной установки | нет; граница названа |

### 2.3 Артефакты части A

```
integrations/
  <slug>/
    README.md        # как подключить, что проверено, границы
    config.<ext>     # готовый шаблон конфигурации именно для этого клиента
    smoke.ps1        # смоук: ensure → search → проверка path:line
docs/03_implementation/AGENT-INTEGRATIONS.md   # общая карта и таблица уровней
tools/check_integrations.py                    # проверка шаблонов, смоуков и текстов
```

Правила артефактов:

1. Шаблон конфигурации обязан быть **рабочим для этого клиента**, а не
   «универсальным»: у каждого свои имена полей и своё место файла.
2. Смоук проверяет не «сервер ответил», а **факт с адресом**: в ответе есть
   `path:line` и ревизия. Ответ без адреса — провал смоука.
3. В README интеграции обязателен раздел «Границы»: чего клиент не умеет,
   что проверено вручную, что не проверено вовсе.
4. Никаких секретов в шаблонах: только имена переменных окружения.

### 2.4 Проверка части A

`tools/check_integrations.py` (новая проверка чекера, 46-я):

* у каждого продукта из матрицы есть каталог `integrations/<slug>/` с тремя
  файлами и разделом «Границы»;
* конфигурационные шаблоны разбираются: JSON — `ConvertFrom-Json`, TOML —
  разборщиком проекта, YAML — `yaml.safe_load`;
* в шаблонах нет значений, похожих на секреты (тот же детектор, что у
  `check_secret_leaks.py`);
* смоук-скрипты, помеченные как автоматические, имеют код возврата и
  печатают `path:line`;
* таблица уровней в `AGENT-INTEGRATIONS.md` совпадает с фактическим набором
  каталогов (нет «объявлено, но не сделано»).

### 2.5 Стенд и установка (сделано 01.11.2026)

Машина-стенд: **192.168.31.103** (`acer-desktop`, Windows 10 Pro 19045,
15,5 ГБ ОЗУ, 152 ГБ свободно, вход `md` по ключу `id_zabbix_ops`, High
integrity). Назначение: чистая установка RAGRAF, проверка обновлений и
совместимости с агентскими клиентами. Локальная машина для этих проверок
неудобна: на ней стоит рабочая копия RAGRAF с живым индексом.

### 2.6 Риски части A

| Риск | Проявление | Мера |
|---|---|---|
| Урезанное окружение клиента | MCP-сервер не стартует, если клиент чистит env | уже проверяется свойством `test_mcp.py`; для каждого клиента — свой смоук |
| Разные имена полей конфигурации | шаблон «универсальный» не работает нигде | шаблон на продукт, проверка разбором |
| GUI-клиенты не автоматизируются | «поддержка» превращается в обещание | уровень L4: чек-лист и явная граница, без зелёной галочки |
| Ключи и токены в шаблонах | утечка в публичный репозиторий | детектор секретов в чекере |
| Расхождение версий клиента | конфиг работает на одной версии и ломается на другой | в README фиксируется проверенная версия, смоук печатает её |

### 2.7 Выполнение части A (02.11.2026)

Сделано и измерено: `integrations/index.json` с одиннадцатью продуктами, каталоги
`integrations/<slug>/` (шаблон + README с разделами «Подключение», «Проверка»,
«Уровни», «Границы»), шесть смоуков и пять чек-листов, `tools/check_integrations.py`
(шесть проверок), 46-я проверка чекера `Test-AgentIntegrations`, свойство
`tests/test_agent_integrations.py` (18 проверок), подсадки `stage-docgraph/mutations6.py`
(четыре, все на инструменте, каждая даёт 1 провал).

Где план разошёлся с фактом и что выбрано:

| Пункт плана | Факт | Решение |
|---|---|---|
| «у каждого продукта три файла: README, config, smoke.ps1» | пять GUI-клиентов не запускаются без графического сеанса | у них `checklist.md` вместо `smoke.ps1`, уровень L2 не заявлен, граница названа |
| «смоук печатает версию клиента» | три клиента отвечают кодом 3 (нет входа, ключа, доверия к каталогу) | смоук называет состояние и код 3; L2 не выдаётся за измеренный |
| «детектор секретов тот же, что у `check_secret_leaks.py`» | тот детектор ищет ЗНАЧЕНИЯ из хранилища, а не форму присваивания | свой детектор формы «ключ: значение» с послаблением на заглушки |
| «шаблоны разбираются `ConvertFrom-Json`» | проверка живёт в Python-инструменте | JSON — `json`, YAML — `yaml.safe_load`, TOML — `tomllib`; разборщик тот же, что у продукта |

## 3. Часть B. Документный граф: что берём у GraphRAG и LightRAG

### 3.1 Что берём, а что нет

| Техника | Источник | Берём? | Почему |
|---|---|---|---|
| Извлечение сущностей и отношений промптом по тексту | GraphRAG `prompts/index/extract_graph.py:6`, LightRAG `prompt.py:56` | **да, переписав** | закрывает слабое место по документам; но у нас обязательны адреса и отпечаток |
| Консолидация описаний сущностей | GraphRAG `prompts/index/summarize_descriptions.py:6` | **да, иначе** | у нас слияние детерминированное по нормализованному имени, а «описание» — это набор цитат, а не пересказ |
| Dual-level ключевые слова (high/low) | LightRAG `prompt.py:484` (`keywords_extraction`) | **да, с кэшем** | дешёвый способ не терять и «тему», и «конкретный термин» |
| Сообщества + отчёты (Leiden + report) | GraphRAG `prompts/index/community_report.py:5` | **частично** | берём идею «кластер → сводка с опорой на записи», но без Leiden и без многоуровневых отчётов |
| Глобальный поиск map-reduce по отчётам | GraphRAG `query/global_search_*` | **нет** | это генерация ответа; RAGRAF отдаёт структуру и цитаты, ответ пишет агент |
| Своя векторная БД, свои хранилища | оба | **нет** | у нас есть `lexvec` (SQLite + vec0); второй движок — второй источник расхождений |
| Полный Leiden с иерархией | GraphRAG | **нет** | недетерминирован по умолчанию и дорог; нам нужны стабильные `theme_id` между ревизиями |

### 3.2 Слой фактов о документах

**Где живёт.** Рядом с документным слоем (таблицы `document`, `document_node`,
`document_chunk` в `lexvec`, `src/ragraf/docindex.py:99`), в тех же правилах:
адрес, провенанс, статус.

Новые таблицы (в хранилище лексики, версионируются по `rev_id`):

```
entity(rev_id, entity_id, name, norm_name, kind, mentions, first_path)
entity_mention(rev_id, entity_id, chunk_id, path, start_line, end_line,
               char_start, char_end, quote_hash)
entity_relation(rev_id, rel_id, src_entity_id, dst_entity_id, kind,
                quote_hash, chunk_id, path, start_line, end_line, weight)
theme(rev_id, theme_id, title, seed_entity_id, size, built_by)
theme_member(rev_id, theme_id, entity_id, weight)
theme_summary(rev_id, theme_id, model, prompt_version, text, citations_json)
```

Правила:

1. `entity_id` выводится детерминированно: `sha1(norm_name + '|' + kind)`.
   `theme_id` — `sha1(seed_entity_id + '|' + sorted(member_ids))`. Одинаковый
   вход даёт одинаковый идентификатор на любой машине.
2. Каждый факт (сущность, отношение, участник темы) обязан иметь
   `entity_mention` с адресом чанка и цитатой. Нет упоминания — нет факта.
3. Цитата хранится хэшем и смещениями, а не копией текста: слой не дублирует
   корпус (иначе он станет вторым корпусом и разойдётся с ним).
4. Слой пересобирается только при смене входа: `chunk_hash`, `model`,
   `prompt_version`, `temperature`, настройки извлечения. Отпечаток слоя
   пишется в `rev.json` рядом с `corpus_hash` — как уже сделано для живого
   слоя баз (там `fingerprint`, см. `src/ragraf/db_writer.py`).
5. Смена отпечатка = запись в журнал расхождений (`drift`), а не тихая замена.

### 3.3 Один LLM-проход, три режима деградации

`src/ragraf/llm.py` — единственный клиент. Провайдер задаётся настройками
(`catalog/settings.json`), ключ берётся из хранилища секретов RAGRAF, а не из
файла конфигурации.

| Состояние | Признак | Что делают операции |
|---|---|---|
| LLM не настроен | `llm.configured=false` | `collected=false`, `reason="LLM не настроен"`, `found=false` |
| LLM недоступен (сеть, квота) | ошибка клиента | `collected=false`, `reason` с кодом драйвера, **без** подмены лексикой |
| Локальная модель (опция) | `llm.provider=local` | тот же контракт, отпечаток включает модель и её версию |

**Запрещено** выдавать «пустой список» за «сущностей нет»: пустота обязана
отличаться от несобранности полем и причиной — то же правило, что вылечено
для живого слоя баз (`src/ragraf/indexer.py:152`).

### 3.4 Dual-level поиск

`search` получает поле `mode`: `auto` (по умолчанию, поведение не меняется),
`code`, `docs`, `themes`. Для `docs`/`themes` дополнительно считается пара
ключей:

* `high_level` — тема запроса (понятие, класс задачи);
* `low_level` — конкретные сущности (имена, идентификаторы, термины).

Промпт — переписанный LightRAG (`prompt.py:484`, keywords_extraction): строгий
JSON, запрет придумывать термины, пустой результат на бессмысленный запрос.
Кэш: `query_keywords(norm_query_hash, model, prompt_version) → json`. При
отсутствии LLM dual-level не включается, `mode=docs` деградирует до обычной
лексики **и говорит об этом** полем `degraded`.

Выдача остаётся прежней формы: чанк, адрес, доказательство. Добавляются
`matched_entities` (с адресами упоминаний) и `match_kind` —
`lexical | vector | entity | theme`.

### 3.5 Темы: кластеры и сводки

Кластеризация **детерминированная**, без Leiden:

1. граф со-встречаемости сущностей в пределах документа (ребро — совместное
   упоминание в одном чанке; вес — число чанков);
2. отсечение рёбер слабее порога (`weight < theme_min_weight`);
3. компоненты связности; компонента больше `theme_max_size` делится по
   разбиению «сильный разрез» с детерминированным порядком (сортировка по
   весу и имени);
4. `seed_entity_id` — сущность с максимумом упоминаний, при равенстве —
   лексикографически первая. Заголовок темы — её `norm_name`, если LLM нет.

Сводка темы (`theme_summary`) — LLM по цитатам участников, строгий JSON,
обязательные `citations` (адреса чанков). Без LLM сводки нет, но темы,
участники и адреса есть: `summary=null`, `collected=true`.

### 3.6 Новые фиксированные операции

Все четыре — только чтение, с обязательными адресами в ответе.

| Операция | Вход | Выход (обязательные поля) |
|---|---|---|
| `themes` | `scope` (корень/подкаталог), `limit` | список тем: `theme_id`, `title`, `size`, `top_entities[]` c адресами, `summary` (может быть null), `citations[]` |
| `entity_neighbors` | `entity` (имя или id), `depth` (1..3), `kind` (сущность/тема) | соседи с `kind`, `weight`, и **каждый** с `path:line` цитаты |
| `doc_relations` | `doc` (путь) или `entity`, `limit` | отношения между документами через общие сущности: `from`, `to`, `via_entities[]`, `citations[]` |
| `global_overview` | `limit` | карта: число документов, сущностей, отношений, тем; топ-темы с адресами; поле `degraded` и причина |

Схема ответа — общий конверт RAGRAF (`op`, `index_rev`, `capability`,
`provenance`, `found`, `error`, `limits`), как у остальных операций фасада.

### 3.7 Поверхности и числа

Каждая операция добавляется в: `src/ragraf/facade.py` (`op_*`),
`src/ragraf/cli.py` (подкоманда), `src/ragraf/mcp_server.py` (`TOOLS`,
`_TOOL_META` с `readOnlyHint`, `_handlers`), `docs/03_implementation/commands.json`,
`README.md` (числа операций и инструментов), `INTEGRATION.md`, статью,
`docs/AGENTS-context.md`, чекер. Числа пишутся **после** последней
переиндексации: `ragraf check` → `tools/registry_line.py --write` → чекер.

### 3.8 Издания и лицензионный гейт

Новая возможность объявляется одной записью — `doc-graph` — и закрывает и
построение слоя, и чтение:

| Уровень | Что даёт |
|---|---|
| Community | документный слой как сейчас (`doc_*`), поиск по документам, `document`/`chunk` адреса |
| Pro/Enterprise | `doc-graph`: LLM-проход, четыре операции, темы и сводки |

Смысл: базовые факты и безопасность остаются бесплатными (правило 2
документа изданий), а платный уровень — это стоимость LLM.

Гейт проверяется свойством: под лицензией Community операция отвечает
`EditionRefusal` с названием нужного издания, под Pro — исполняется
(тот же вид проверки, что уже есть для `siem_export`).

### 3.9 Проверки и свойства

| Свойство | Что ловит | Подсадка |
|---|---|---|
| `test_docgraph_entities.py` | факт без упоминания; недетерминированный `entity_id`; пересборка без смены отпечатка | убрать проверку упоминания — N провалов |
| `test_docgraph_degradation.py` | пустой список вместо `collected=false`; подмена лексикой без пометки | вернуть `return []` — N провалов |
| `test_docgraph_themes.py` | нестабильный `theme_id`; тема без участников; сводка без цитат | сменить порядок сортировки — N провалов |
| `test_docgraph_editions.py` | Community проходит через гейт; Pro отказывает | снять проверку издания — N провалов |
| `test_docgraph_ops.py` | операция без адреса в ответе; расхождение `TOOLS`/`_handlers` | убрать `_TOOL_META` — N провалов |

Все — в группе `core`, с поддельным LLM (детерминированная заглушка), песочным
корпусом документов в `%TEMP%` и без сети.

## 4. Дорожная карта

| Этап | Содержание | Критерий приёмки | Оценка |
|---|---|---|---|
| **1** | Фундамент: `llm.py` (клиент + деградация + кэш + отпечаток), хранилище слоя, миграция, свойства | **ВЫПОЛНЕН 01.11.2026**: свойства зелёные, `collected=false` с причиной на любой стадии отказа, отпечаток в `rev.json` | 1 сессия |
| **2** | Извлечение сущностей/отношений для документов, кэш по чанкам, `ragraf index` с флагом `--doc-graph` | **ВЫПОЛНЕН 01.11.2026**: повторный прогон не делает ни одного вызова LLM, слой совпадает побайтово | 1–2 сессии |
| **3** | `entity_neighbors` + `doc_relations` (две операции, полная поверхность) | **ВЫПОЛНЕН 01.11.2026**: ответы с адресами, гейт изданий работает, числа обновлены | 1 сессия |
| **4** | Dual-level поиск (`mode=docs/themes`), кэш ключей, два уровня одним ответом (`doc_search_levels`) | **ВЫПОЛНЕН 01–02.11.2026**: `mode=docs` находит документ по «теме», не теряя лексику; `doc_search_levels` отдаёт оба уровня с явными связями `via_chunk`/`via_entities`; пустой слой назван кодом; гейт только на глобальном уровне; числа обновлены (65 операций, 72 инструмента) | 1 сессия |
| **5** | Темы: кластеры, `themes`, `global_overview`, сводки | **ВЫПОЛНЕН 02.11.2026**: `theme_id` = seed + отсортированные участники (проверяется recomputation-ом, повторная сборка даёт те же идентификаторы); сводка без цитат не выдаётся и не пишется; темы строятся без модели (`docgraph themes`), сводки — по флагу; числа 67 операций / 74 инструмента | 1–2 сессии |
| **6** | Интеграции части A: 11 каталогов, смоук, чекер | **ВЫПОЛНЕН 02.11.2026**: 11 каталогов, 6 смоуков (три клиента отвечают кодом 3 — не авторизованы), 5 чек-листов вместо смоука у GUI, `tools/check_integrations.py` (6 проверок), 46-я проверка чекера, свойство 18 проверок и 4 подсадки; чекер 46/0/0 | 2 сессии |
| **7** | Документы, числа, публикация | чекер 45+/0/0, ядро дважды, ссылки `файл:строка` без ошибок | 1 сессия |

Порядок не случаен: этап 1 создаёт контракт деградации и отпечатка, без
которого всё остальное превращается в «иногда работает». Этап 3 даёт
полезность раньше тем (сущности и связи уже отвечают на вопросы), этап 6
независим и может идти параллельно любому.

## 5. Риски и откаты

| Риск | Как проявляется | Мера | Откат |
|---|---|---|---|
| Недетерминизм LLM | слой меняется от прогона к прогону, `drift` шумит | `temperature=0`, кэш по `chunk_hash`, отпечаток в ревизии | удалить слой, флаг `--doc-graph` выключен по умолчанию |
| Стоимость | индексация большого корпуса документов стоит денег | лимит чанков на прогон, `budget_seconds`, предварительная оценка числа вызовов | операция `compact` уже умеет убирать слой |
| Ложные связи | «связь по имени» выдана за факт | связь без `entity_mention` не создаётся; в ответе всегда цитата | слой пересобирается, старые ревизии неизменны |
| Утечка содержимого | цитаты дублируют документы в индекс | хранится хэш + смещения, а не текст | — |
| Расхождение поверхностей | операция есть в CLI, но не в MCP | свойство «`TOOLS` = `_handlers` = `_TOOL_META`», чекер | — |
| GUI-клиенты | «поддержка» без проверки | L4: явная граница вместо галочки | — |

## 6. Что мы НЕ делаем

* Не тянем GraphRAG/LightRAG как зависимости и не копируем их модули.
* Не делаем свой Leiden и многоуровневые отчёты.
* Не генерируем ответы за агента: RAGRAF отдаёт структуру, цитаты и карту,
  текст пишет клиент.
* Не превращаем продукт в general-purpose RAG: кодовый слой остаётся главным,
  документный граф — надстройка с явной деградацией.
