Проблематика: почему агенту нельзя доверять память о коде
Три измеренных симптома слепого агента, данные VulnGym и LLMxCPG, и пять анти-требований, из которых выросла архитектура RAGRAF.
Три симптома, которые знает каждый #
Кто поручал LLM-агенту правку в существующем проекте, видел это своими глазами.
Симптом 1 — агент не находит место #
Он уверенно отвечает про функцию, но проверка показывает: функция в другом файле, а описанная — одноимённая. В репозитории на 392 файла grep по имени даёт десятки совпадений, и нужное — не первое.
Измерение на пилотном корпусе: rg находил файлы, но не локализовал — 34 кандидата на SuperAdmin, 44 на z_e(, 64 на namespace BatteryService. Найти файл легко; найти место в файле — нет.
Симптом 2 — агент не знает схему БД #
Колонку назвали «как переменную в соседнем модуле» — AuthorUserID. Такой колонки нет. Это выясняется в рантайме, а не при чтении:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'AuthorUserID' in 'field list'В журналах колонка называлась InitiatorID, а UpdatedBy существовала ровно в одной таблице из пяти. Код был уже написан, когда ошибка проявилась. Именно этот случай породил операцию write_check — сверку плана записи со схемой до запуска.
Симптом 3 — агент не знает, что устарел #
Самый неприятный. Индекс построили, код потом правили — агент получает факты о состоянии, которого уже нет. Отличить «такой функции нет» от «функция появилась после индексации» по ответу невозможно. Решение принимается по этому ответу, и оно неверно.
Что говорят измерения, а не мнения #
Навигация — доминирующая причина провалов #
Исследование VulnGym (цитируется в статье Positive Technologies о графах в аудите кода): в задачах, где цепочка от источника до стока длиннее девяти шагов, лучший агент находил 22,58% уязвимостей, а в 56,5% провалов он либо вообще не находил нужный файл (26,8%), либо находил файл, но не строку (29,7%).
Больше половины отказов — не про анализ. Про навигацию. Именно её RAGRAF выносит из модели во внешний индекс.
Свободные запросы к графу — генератор ошибок #
LLMxCPG (USENIX Security '25) измерил, как базовые модели генерируют CPGQL-запросы к графу кода:
| Модель | Валидных запросов |
|---|---|
| Базовая, конфигурация A | 19 из 1278 |
| Базовая, конфигурация B | 132 из 1278 |
| Дообученная генерировать запросы | 1278 из 1278 |
Дообученная модель идеальна — потому что её учили именно этому. Произвольного агента никто не учил. Отсюда архитектурное решение P4: языка запросов нет вовсе, только фиксированные операции. Подробнее — в архитектуре.
Смежная литература, на которую опирается дизайн #
- ALIBI — инъекции в комментариях подавляют обнаружение в >90% случаев → свободный текст хранится отдельно от структурных фактов (
text_chunkпротивnode/edge). - «Words Speak…» — формулировка сдвигает вердикты на 33,2% → нейтральность формулировок ответов — требование (ФТ-23).
- Repoformer — право не искать даёт до 70% ускорения → честное «не найдено» лучше приблизительного ответа (принцип P8).
- LCIP (
arXiv:1512.07435) — измеренный потолок impact-анализа: precision 69% / recall 79% / F1 55% (Java, мутационная симуляция). Используется только как ориентир порядка величины для метрики M2 — никогда как «цель для отчёта». - AdaRepair-Mem — «больше памяти ≠ лучше» → журнал расхождений архивируется и дедуплицируется, а не копится вечно.
Почему существующие инструменты не закрывают задачу #
| Альтернатива | Что даёт | Чего не хватает |
|---|---|---|
rg/grep + чтение файлов | универсальность, ноль настройки | локализация (34–64 кандидата); нет структуры; нет понятия свежести; каждое чтение жжёт контекст |
| LSP / индексы IDE | точные определения и ссылки | сервер на каждый язык; нет связей код↔схема БД↔маршруты; не рассчитан на агента; нет контракта provenance/staleness |
| Обычный RAG (чанки+эмбеддинги) | семантический поиск | нет «кто кого вызывает»; нет фактов схемы; молча устаревает после правок; сходство ≠ структура |
| Графовая БД + Cypher/CPGQL | полная выразительность | модель не пишет валидных запросов (19–132/1278); сервер СУБД; факты без контракта доказательств |
| Joern/CodeQL напрямую | настоящий CPG, taint-анализ | вес JVM/CodeQL; снова язык запросов; нет журнала свежести; нет фиксированного контракта для агента |
Позиция RAGRAF: взять структурную точность графа и семантический охват векторов, обернуть в фиксированный, проверяемый, знающий свою свежесть контракт из 22 операций и хранить всё в файлах SQLite — ноль сервисов. Опциональный слой Joern добавляет межпроцедурную достижимость, а его отсутствие сообщается, а не прячется.
Пять анти-требований: проблему, которую индекс не имеет права создать #
Индекс — это кэш. Кэш, который врёт, хуже отсутствия кэша:
- Молчаливое устаревание — факты прошлой недели выдаются за текущие. → Иммутабельная ревизия + append-only журнал расхождений +
stale_warningsв каждом конверте + авто-реиндекс по контрольной сумме. Механика — в статье свежесть и дрейф. - Галлюцинированные связи внутри индекса — правдоподобное ребро «потому что из базы». → Каждое ребро несёт
evidence path:line,provenance(stated/derived) иconfidence; эвристические рёбра — отдельный вид и отдельный блок ответа. - Непроверяемые ответы — агент не может проверить полученное. → 100% фактов о коде с
path:line; метрика M6 подтверждает ссылки автоматически. - Маскарада возможностей — «slice вернул пусто» против «slice в этом режиме невозможен». →
capability: full|degradedв каждом конверте; отказ с кодом 3 и причиной; молчаливая деградация запрещена конструктивно. - Утечка секретов в индекс — значения из конфигов и
.envв векторах. →secrets_exclude— исключение, а не маскировка: файлы не открываются вовсе; телеметрия хранит хеши аргументов и 60-символьный дайджест.
Эти пять анти-требований сформировали архитектуру сильнее, чем любая фича. Каждое отображено в проверяемый механизм — см. архитектуру и протоколы приёмки (метрики M5/M6, тест M3-T2).
Что это дало на практике #
Одна реальная доработка (раздел документооборота /agreements/: журналы платежей, договоров, отпусков), 46 обращений к операциям, 0 ошибок выполнения:
| Результат | Число |
|---|---|
| Дефектов найдено/предотвращено в разрабатываемой подсистеме | 9 (3 из них — обрывы маршрута согласования и неработающая защита полей) |
| Дефектов самого RAGRAF найдено той же работой | 13 (5 невидимы синтетическим тестам: «код работал правильно, а измерял себя неправильно») |
| Предотвращено до написания кода | 4 (один — операцией schema, остальные — write_check/policy_trace) |
| Доминирующий класс дефектов | «работает, но не так» — 8 из 16: страница открывается, исключений нет, а отклонённый документ — тупик, и любой сотрудник может отметить платёж оплаченным |
Ни компилятор, ни тест «страница открывается» такой класс не ловят. Ловят — точная схема в индексе, статическая трассировка прав и проверки под реальной ролью. Полные разборы — в статьях замеры и границы и перспективы.
Границы: чем RAGRAF не является #
- Не переписыватель кода. RAGRAF отвечает; правит агент. Автономного патчинга нет.
- Не сканер уязвимостей. Taint-анализ не цель релиза (слой CPG делает его возможным в будущем — см. перспективы).
- Не сервис. Ни демонов, ни сетевых эндпоинтов, кроме loopback-панели. Три файла SQLite в
<project>/.ragraf/. - Не оракул. Каждый ответ перечисляет свои
limits(«статический анализ; динамика не покрыта»), а честная общая проверяемость по всем операциям — 24,4%, потому что у фактов схемы не существует номеров строк. Мы не дорисовываем ссылки для красоты отчёта.
Дальше: архитектура — три файла SQLite и ноль инфраструктуры →