# Отчёт: починка RAGRAF и SagaAI по логу ошибок

Дата: 7 октября 2026 г.
Источник задачи: `docs\SagaAI\Fixes\Errors.html` (дамп интерфейса
DevAgent, 249 КБ). Из него извлечён текст (918 строк) и разобраны сообщения об
ошибках.

> **Обновление (второй заход).** Первая правка закрывала дефект в окружении
> агента, но отказ воспроизводился из приложения — см. раздел 6
> «Интерпретатор RAGRAF: настоящая причина». Она найдена замером и закрыта
> отдельным окружением плюс проверкой интерпретатора запуском.

## Короткий итог

Ошибка, которую видел владелец, имела **две независимые причины** — по одной в
каждом проекте. Обе воспроизведены вызовом, обе закрыты, на каждую написан
тест, который ловит именно этот дефект.

| Что видел пользователь в логе | Настоящая причина | Где исправлено |
|---|---|---|
| `RAGRAF завершился с кодом 2 — ошибка конфигурации: не установлен PyYAML` | маркер `.interpreter` перезаписывался **до** проверки, поэтому один неудачный запуск чужим Python ломал все последующие | `src/ragraf/cli.py` |
| `TypeError: 'NoneType' object is not subscriptable` | `subprocess.run(..., text=True)` без `encoding`: при несовпадении кодировок поток чтения падал, `proc.stdout` оставался `None`, а срез `[-4000:]` давал эту ошибку | `dev_agent/tool_executor.py` |
| `UnicodeEncodeError: 'charmap' codec ... position 174-188` на `--help` | вывод RAGRAF не принуждался к UTF-8, а русский текст справки не влезал в cp1252 | `src/ragraf/cli.py` |

Отдельно найдены и закрыты ещё четыре дефекта, которые в логе не проявились, но
были воспроизведены прогоном (раздел «Найдено дополнительно»).

---

## 6. Интерпретатор RAGRAF: настоящая причина отказа

**Симптом.** Владелец повторил проверку и получил тот же отказ:
`operation=index_status` → код 2, «ошибка конфигурации: не установлен PyYAML».

**Первая версия диагноза была неполной.** Я проверял `import yaml` в своей
сессии и видел `6.0.3` — и решил, что зависимости на месте, а виноват только
испорченный маркер. Это было верно для сессии агента и неверно для приложения.

**Что показал замер.** PyYAML лежит не в самом vendor-Python AutoClaw, а в
пользовательском каталоге платформы:

```
C:\Users\<user>\AppData\Roaming\AutoClaw-oversea-official\toolchain\python\Python313\site-packages
```

Он подключается переменной `PYTHONUSERBASE`, и эта переменная задана **только в
сессии, которую создаёт AutoClaw** (`Process`-уровень). В реестре `User` и
`Machine` её нет. Приложение SagaAI, запущенное из проводника, работает без неё:

| Проверка | Результат |
|---|---|
| `PYTHONUSERBASE` — User | не задан |
| `PYTHONUSERBASE` — Machine | не задан |
| `PYTHONUSERBASE` — моя сессия | `...\AutoClaw-oversea-official\toolchain\python` |
| `import yaml` с переменной | `6.0.3` |
| `import yaml` без переменной | `ModuleNotFoundError` |
| RAGRAF без переменной (как приложение) | **rc=2, «не установлен PyYAML»** |

**Почему это было невидимо.** Маркеру `.interpreter` доверяли молча: ни один
кандидат не проверялся. Поэтому инструмент отвечал отказом конфигурации, хотя
рядом лежало рабочее окружение.

**Правка — две части.**

1. **Собственное окружение RAGRAF.** Создано `.venv`
   (Python 3.14.7), установлены все зависимости из `requirements.txt`. Окружение
   автономно: `user site` отключён, поэтому оно не зависит ни от
   `PYTHONUSERBASE`, ни от настроек AutoClaw. Маркер `.interpreter` переключён на
   него. Замер `selftest`: все компоненты `[OK]` кроме `joern` (не установлен,
   для базового слоя не требуется).

2. **Проверка интерпретатора запуском** (`_has_yaml`) в функции RAGRAF SagaAI:
   берётся первый кандидат, который действительно может импортировать PyYAML.
   Маркер остаётся приоритетным, но перестаёт быть единственным источником
   истины. Проверено в четырёх условиях — выбор всегда верный.

**Проверка из окружения приложения** (без `PYTHONUSERBASE`, как запускается
сервис):

```
выбран интерпретатор: .venv\Scripts\python.exe

  OK  index_status   OK  symbol      OK  search        OK  callers
  OK  class_usage    OK  policy_trace OK  task_stats   OK  freshness
  OK  catalog

НЕ РАБОТАЮТ: нет        ошибка про PyYAML: нет
```

## 7. Правила RAGRAF в системном промпте DevAgent

**Что было.** Присланный текст промпта (9753 символа, sha256 `54a89e13…`) уже
лежал и в `orchestrators/dev_agent/system_prompt.md`, и в базе приложения — но
**не содержал ни одного упоминания RAGRAF** (проверено поиском: 0 строк).

**Куда записано и почему именно так.** Промпт живёт в двух местах, и они должны
совпадать:

* `orchestrators/dev_agent/system_prompt.md` — копия для правки руками;
* `prompt_text` в базе — **отсюда** его берёт `build_assistant_dicts` при каждом
  запуске агента.

Поэтому запись выполнена штатной функцией проекта `save_orchestrator`, той же,
которой пользуется интерфейс: она обновляет базу и синхронизирует папку. Прямой
SQL был бы обходом конвенции и разошёлся бы с папкой. Оба хранилища проверены на
совпадение: 11619 символов, идентичны.

**Содержание раздела** — короткие правила вместо пересказа инструкции:
обязательный порядок «сначала индекс, потом файлы»; перечень операций по типу
вопроса; смысл полей `ok` / `found` / `stale` / `capability`; что делать после
правок кода (`freshness`); поведение при недоступном индексе. Полный справочник
оставлен в инструкции `ragraf_index` — она уже перечисляется в блоке
`## Available instructions`, который платформа дописывает автоматически.

**Проверка итогового промпта, который уходит модели** (33 575 символов):
заголовок `## 7.1 Working with the RAGRAF code index` на месте; блок
`## Available instructions` содержит `ragraf_index`; блок `## Available tools`
содержит инструмент `ragraf` (70 инструментов в каталоге).

**Отдельно проверено, что инструмент доступен агенту:**
`load_all_orchestrator_functions('dev_agent')` → `['ragraf']`, вызов
`operation=index_status` → `ok=True`. Отсутствие `ragraf` в поле `tools`
оркестратора доступность не блокирует — поле не является гейтом для каталога
инструментов, поэтому правка списка не требуется.

---

## 1. RAGRAF: операции `policy_trace` и `class_usage` не работали через CLI

**Симптом.** `python tools/ragraf.py query policy_trace --name X` завершался
кодом 2 «неверный аргумент», хотя операция есть в списке допустимых.

**Причина.** Обе операции объявлены в `OPS` (поэтому argparse их принимал),
реализованы в фасаде и доступны через MCP, но в диспетчере `dispatch_op` ветвей
для них не было — вызов доходил до конца функции и падал в
`raise SystemExit(EXIT_USAGE)`.

**Доказательство.** До правки: `query policy_trace --name can_act` → rc=2,
`query class_usage --mode missing` → rc=2. После правки: обе → rc=0.

**Правка.** `src/ragraf/cli.py:481` и `src/ragraf/cli.py:485` — добавлены ветви
диспетчера.

**Почему это не поймали раньше.** Существующий тест `tests/test_class_usage.py`
проверял наличие имени в исходнике (`"class_usage" in cli_src`) и доступность
через MCP — то есть декларацию, а не исполнение. Такая проверка проходит на
сломанном диспетчере.

## 2. RAGRAF: `--help` и русский вывод падали при не-UTF8 локали

**Симптом (из лога, дословно).**

```
UnicodeEncodeError: 'charmap' codec can't encode characters in position 174-188
  File "encodings\cp1252.py", line 19, in encode
```

**Причина.** RAGRAF выводит русский текст, но кодировку потоков не задавал.
Дочерний процесс, запущенный из Python без явной кодировки, получает на этой
машине cp1252, и справка не кодируется.

**Доказательство.** До правки: `PYTHONIOENCODING=cp1252` + `--help` → rc=1, та
же трассировка. После правки: rc=0, справка читается.

**Правка.** `src/ragraf/cli.py:988` — `_force_utf8_output()`, вызывается первым
действием `main` (до разбора аргументов, иначе справка всё равно падала бы).

## 3. RAGRAF: маркер интерпретатора «отравлялся» неудачным запуском

**Симптом (из лога).** Уже установленный PyYAML не находился: инструмент
постоянно отвечал «ошибка конфигурации: не установлен PyYAML».

**Причина.** `_remember_interpreter()` вызывался **первым действием** `main`, до
разбора аргументов и загрузки конфига. Любой запуск чужим интерпретатором
(например `.venv` SagaAI без PyYAML) записывал в `.interpreter` путь, который не
работает, и он оставался таким навсегда — а внешний инструмент SagaAI берёт
интерпретатор именно оттуда.

**Доказательство.** Воспроизведено: запуск `.venv` SagaAI → маркер изменён на
нерабочий путь. После правки: тот же запуск → маркер не изменился; успешный
запуск → маркер пишется.

**Правка.** `src/ragraf/cli.py:1012` — маркер пишется только после успешного
выполнения; лишняя запись не делается, если путь уже тот же.

## 4. SagaAI: `'NoneType' object is not subscriptable` в `run_code` / `run_test`

**Симптом (из лога).** Три вызова `run_code` вернули
`{"ok": false, "error": "'NoneType' object is not subscriptable"}`. Агент
прочитал это как поломку инструмента и ушёл на другой путь.

**Причина (найдена и воспроизведена точно).** В `subprocess.run(...)` указан
`text=True`, но не указан `encoding`. Вывод дочернего процесса декодировался
кодировкой, предпочитаемой **родителем**. Для piped-процесса на Windows это
ANSI-кодовая страница (cp1252), тогда как ребёнок мог писать UTF-8 или cp1251.
При несовпадении падал поток чтения, `proc.stdout` оставался `None`, и
следующая строка `proc.stdout[-4000:]` давала ровно это сообщение.

**Доказательство.** Воспроизведено дословно: дочерний процесс, печатающий
не-ASCII текст, возвращал `{'ok': False, 'error': "'NoneType' object is not
subscriptable"}`. После правки — структурированный результат.

**Правка.** `dev_agent/tool_executor.py`:
* `decode_child_output` (строка 469) — вывод читается **байтами** и
  декодируется цепочкой, уже принятой в проекте (`core.fs.decode_bytes`:
  utf-8 → cp1251 → latin-1; та же цепочка в `core/threads_devagent.py:456`).
  Одного `encoding="utf-8"` было мало: падение уходит, но вывод cp1251
  превращается в символы замены;
* `_run_child` (строка 496) — общий помощник для обоих инструментов;
* `run_test` (строка 1430) и `run_code` (строка 1527) переведены на него.

## 5. SagaAI: функция RAGRAF бросала исключение вместо `ok: false`

**Симптом.** `limit="abc"` приводил к `ValueError` **наружу**, что ломает
объявленный контракт функции: «при любой ошибке возвращается
`{"ok": False, ...}`».

**Правка.** `orchestrators/dev_agent/functions/ragraf.py`:
* `_BadParam` (строка 93) — тип исключения для неверного значения;
* защищённое приведение к int (строка 198);
* перехват в `invoke` (строка 260).

## Найдено дополнительно (в логе не проявилось)

| Дефект | Доказательство | Правка |
|---|---|---|
| `usage` падал с `no such column: task` на корпусе, где колонка ещё не добавлена: сводка при `telemetry=False` читалась «читателем» без подготовки схемы, а схема и миграция живут только в конструкторе `Telemetry` | Воспроизведено на копии журнала: без `_ensure_schema` → `OperationalError: no such column: task`; с ней → 144 вызова, 208 сессий | `src/ragraf/facade.py:424` |
| «Не найдено» отдавалось кодом 3, а функция SagaAI трактует ненулевой код как поломку инструмента — честное «в индексе этого нет» выглядело как сломанный RAGRAF | `query symbol --name <нет такого>` → rc=3 при `found: false` | `src/ragraf/cli.py:336` (`_exit_code`); код 3 остался только у `slice` при `degraded` |
| `--mode` имел один фиксированный набор значений (`hard`/`full`), хотя `table_usage` хочет `all/read/write`, а `class_usage` — `all/missing/unused`: команды из инструкции проекта отклонялись до запуска | `table_usage --mode write` → rc=2 | `src/ragraf/cli.py:50`, `resolve_mode` (строка 70) |
| `live_read` без пути превращался в чтение корня корпуса и давал `PermissionError: [Errno 13] ... 'C:\\!.Tihon\\sagaai_edit_github'` вместо «не указан путь» | Воспроизведено вызовом `live_read` без аргументов | `src/ragraf/facade.py:1185`, `1204` |

## Проверка

**Новые тесты (падают на исходном коде, проходят на исправленном):**

| Тест | Результат | Что ловит |
|---|---|---|
| `tests\test_cli_ops_dispatch.py` | 27 / 0 | каждая операция из `OPS` проходит диспетчер; коды выхода; `--mode` по операции; `--help` при cp1252; маркер не портится; сводка `usage` не зависит от флага телеметрии |
| `tests\test_sagaai_child_output_encoding.py` | 24 / 0 | не-ASCII вывод ребёнка не рождает `'NoneType' object is not subscriptable`; русский текст читается; ошибка/пустой/длинный вывод; путь-режим; `ok:false` вместо исключения |

Регрессионная проверка выполнена явно: при временном возврате исходного
`tool_executor.py` тест дал **4 ошибки** и дословно то самое сообщение
`{'ok': False, 'error': "'NoneType' object is not subscriptable"}`; после
возврата исправления — 24/0.

**Затронутые тесты без регрессий:**

* RAGRAF: `test_class_usage.py` 17/0, `test_rdpmon_ops.py` 22/0,
  `test_task_stats.py` 20/0, `test_lang_routing.py` 56/0, `test_init_project.py`
  23/0, `test_ragraf_interpreter.py` **18/0** (добавлена проверка раздела 6);
* SagaAI: `test_tool_executor_env.py` + `test_dangerous_list_form.py` +
  `test_phase1_agent_loop.py` — **122 passed**;
* тесты RAGRAF о SagaAI: `test_sagaai_child_output_encoding.py` 24/0,
  `test_sagaai_catalog.py` 6/0, `test_sagaai_function.py` 14/0,
  `test_task_stats_sagaai.py` 16/0, `test_slash_ragraf.py` 33/0.

**Проверка «в живом приложении»** — через штатный загрузчик проекта
(`core.orchestrator_folders.load_orchestrator_function_module`), то есть тем же
путём, каким работает агент и команда `/RAGRAF`:

```
symbol        ok=True   найдено, записей 2
class_usage   ok=True   не найдено
policy_trace  ok=True   не найдено
live_read     ok=True   найдено
search        ok=False  "параметр limit должен быть целым числом, получено 'abc'"
task_stats    ok=True   отчёт команды /RAGRAF сформирован
```

**Сервис.** Процесс на 8503 (PID 56660) завершился сам до начала работы;
работавший вместо него PID 42028 остановлен, порт освобождён, базы отпущены,
сервис запущен заново: `http://127.0.0.1:8503` → HTTP 200, в логе
`Uvicorn server started on 127.0.0.1:8503`, ошибок нет.

**Индекс.** Корпус `sagaai` переиндексирован после правок: ревизия `rev-0013`,
`changed_since_index: false`. Новые символы видны в индексе:
`dev_agent/tool_executor.py:decode_child_output` (строки 469-493) и
`dev_agent/tool_executor.py:_run_child` (496-512).

## Что осталось и требует решения владельца

1. **`slice` на корпусе SagaAI отдаёт код 3** — это правильное поведение, а не
   дефект: срез требует графа свойств кода (`capability=full`), а корпус
   `sagaai` в состоянии `degraded`, потому что Joern не смог экспортировать CPG
   (`ava.util.NoSuchElementException: next on empty iterator`). Экспорт CPG —
   отдельная задача.

2. **777 ошибок в полном прогоне `pytest` SagaAI** — это среда, не код: setup
   `tmp_path` падает с `PermissionError: [WinError 5]` при обходе каталога
   временных файлов. Проверено сравнением: с исходной и с исправленной версией
   `tool_executor.py` результат одинаковый. Рекомендация: задать pytest
   собственный `--basetemp`.

3. **Индекс корпуса `ragraf` (сам RAGRAF) не построен** — в его `.ragraf` нет
   каталога `index`, поэтому операции на этом корпусе отвечают «индекс не
   создан» (теперь кодом 3, как отказ слоя). Если нужен самоиндекс — отдельная
   задача: полная индексация каталога с JDK и GPU-библиотеками занимает время.

4. **«Мохнатый» русский текст в логе** (`R|# RAGRAF ? ????????????? ??????
   ????????????`) — артефакт самого диагностического скрипта SagaAI, который
   печатал результат через `encode('ascii', errors='replace')`
   (`Errors.html`, строки 561-590). Это не дефект продукта, поэтому не
   исправлялось.

## Изменённые файлы

**RAGRAF**

* `src/ragraf/cli.py` — код 3 только для отказа слоя, `policy_trace` и
  `class_usage` в диспетчере, `--mode` по операции, `--action`, UTF-8 вывод,
  безопасная запись маркера интерпретатора;
* `src/ragraf/facade.py` — подготовка схемы журнала при чтении сводки,
  понятные ответы `live_read` на пустой путь и на каталог;
* `.venv/` — собственное окружение со всеми зависимостями `requirements.txt`;
* `.interpreter` — переключён на `.venv`;
* `tests/test_cli_ops_dispatch.py` — новый тест (27 проверок);
* `tests/test_sagaai_child_output_encoding.py` — новый тест (24 проверки);
* `tests/test_ragraf_interpreter.py` — добавлена проверка «интерпретатор без
  зависимостей не выбирается» (18 проверок вместо 13).

**SagaAI** (`C:\!.Tihon\sagaai_edit_github`)

* `dev_agent/tool_executor.py` — вывод дочерних процессов читается байтами и
  декодируется цепочкой проекта;
* `orchestrators/dev_agent/functions/ragraf.py` — неверный числовой параметр
  возвращает `ok:false`, а не исключение; интерпретатор проверяется запуском;
* `orchestrators/dev_agent/system_prompt.md` и `prompt_text` в базе — раздел
  `## 7.1 Working with the RAGRAF code index`;
* резервные копии: `dev_agent/tool_executor.py.bak-ragraf-child-encoding`,
  `orchestrators/dev_agent/functions/ragraf.py.bak-ragraf-param`,
  `orchestrators/dev_agent/functions/ragraf.py.bak-ragraf-interp`,
  `orchestrators/dev_agent/system_prompt.md.bak-before-ragraf-section`.

Все файлы сохранены в UTF-8 без BOM с переводами строк LF; проверено
инструментом `Clean_BOM_Senior\clean-bom-senior.sh` (явные пути) —
после обработки `BOM=False`, `CRLF=0`.

**Не сделано намеренно:** коммиты и `git push` не выполнялись — репозиторий
SagaAI содержит 239 незакоммиченных файлов чужой незавершённой работы, и
решение о коммите принадлежит владельцу.
