Код-граф: как my_architect осведомлён об архитектуре, но не верит индексу на слово
Агент с амнезией
У LLM-агента нет памяти между сессиями. Каждый новый разговор о репозитории начинается с чистого листа: агент снова grep'ает, снова читает те же файлы, снова строит в голове ту же карту — и выбрасывает её в конце сессии. Мы это измерили на собственном репозитории: сбор фактов о хорошо знакомой кодовой базе стоил примерно 77 тысяч токенов и 20 вызовов инструментов — и так каждую сессию, по кругу.
Отдельная боль — вопросы вида «что сломается, если поменять сигнатуру этой функции?». Ответ требует знать всех потребителей символа. Grep находит их, но за цену полного прохода по репозиторию, и главное — этому знанию негде пережить сессию.
Вокруг этой боли вырос рынок инструментов «код-как-граф» с лозунгами уровня «90% экономии токенов». Нам нужны были не лозунги, а ответ на два вопроса: какие классы ошибок это убирает и где экономия настоящая, а где популизм.
Не строим своё: Graphify
Инструмент для персистентной памяти о коде уже существует — Graphify (open source, MIT). Он парсит код через tree-sitter полностью локально, без API-вызовов и телеметрии, складывает граф символов и связей в graphify-out/graph.json, рядом кладёт интерактивную HTML-карту и готовый текстовый отчёт о репозитории. Свежесть индекса поддерживают git-хуки; для команд есть merge-driver, чтобы граф можно было коммитить без конфликтов.
Мы сознательно не стали строить свой граф-движок. Вместо этого научили наш скил decomposition-дисциплины recursive-context распознавать лежащий в репозитории индекс и правильно им пользоваться. Вся фича — это markdown-справочник с правилами плюс несколько строк-указателей; у плагина не появилось ни одной жёсткой зависимости. Нет графа — всё работает как раньше; нет графа, а задача тяжёлая — агент один раз предложит владельцу поставить и собрать (и сделает это сам после «да»; молчаливые установки запрещены).
Насколько это дёшево на практике: на нашем репозитории (порядка 339–351 файла) сборка графа заняла секунды и дала 2655 узлов, 4948 рёбер и 153 автоматически найденных кластера — около 6 МБ на диске, ноль API-затрат. Отчёт сразу показывает «god nodes» (у нас это HierarchyNode с 84 рёбрами), циклы импортов и «неожиданные связи», о которых мы сами забыли. Один запрос после этого возвращает всех потребителей функции с путями за секунду — то, что раньше требовало полного прохода. Важная деталь для всей дальнейшей экономики: сам граф токенов не тратит вообще, и сборка, и запросы — локальные утилиты, а не вызовы модели.
Правило, ради которого всё затевалось
Индекс — это кэш, а кэш умеет врать: он отстаёт от кода между пересборками. Поэтому в центре фичи не команды, а три правила.
Первое: граф — навигация и кандидаты, НЕ источник фактов. Ответ графа говорит, КУДА смотреть; фактом становится только то, что подтверждено живым файлом. Контракт факта {утверждение, путь-доказательство, уверенность} не изменился ни на йоту.
Второе: freshness-check обязателен. Первым делом агент сравнивает время сборки индекса с временем последнего коммита. Протух — сказать вслух, предложить владельцу пересборку (а если git-хук не стоит — предложить и хук, чтобы протухание не повторялось каждую сессию), кандидатов помечать «по устаревшему индексу».
Третье: verify — экономно, регионами. Кандидат приходит с координатой путь:строка, и проверка читает ±30 строк вокруг, а не файл целиком. Без этого правила верификация съедала бы всё, что граф сэкономил на поиске.
Что поймали тесты
Скил — это текст, который должен менять поведение модели. Текст не скомпилируешь, поэтому проверка слоёная: триггеры (загрузится ли скил сам по имени и описанию), поведение (соблюдается ли дисциплина в плане), злое ревью самого текста (ревьюер не читает сниппеты вежливо — он их исполняет) и живые прогоны на настоящем графе. Железное правило: сначала фиксируется «как агент ошибается без правил», и только потом пишутся правки.
Самые полезные находки принесло именно исполнение, а не вычитка. Ветка «графа нет → предложи собрать» оказалась недостижимой: все указатели в справочник начинались со слов «если граф ЕСТЬ». Сниппет freshness-проверки использовал macOS-синтаксис stat, а на GNU тот же флаг значит другое — гейт вечно отвечал бы STALE, и на любой Linux-машине graph-first не включился бы тихо. Мы советовали прятать graphify-out/ в .gitignore — а README апстрима рекомендует командам граф коммитить; наш совет молча выбрасывал командный сценарий. А исполнитель поведенческого теста не поверил условию «граф свежий», проверил mtime по-настоящему, обнаружил, что граф его окружения уже протух, и обработал расхождение по рецепту — дисциплина «не доверяй, проверяй» сработала против собственного задания.
Экономия настоящая, но селективная
Качественные проверки не отвечают на вопрос «сколько это стоит». На него отвечает только контролируемый A/B с телеметрией. Мы взяли незнакомый обеим группам крупный репозиторий (NestJS, 2125 файлов — на порядок больше нашего), сделали два идентичных клона: с графом (12309 узлов, 738 кластеров) и без. Промпты идентичны с точностью до пути, про граф — ни слова: плечо с графом обязано найти его само.
Результат отрезвляет. На impact-вопросах («кто потребители символа, что сломается») — паритет по токенам: grep по буквальному имени символа дёшев даже на 2125 файлах, выигрыш графа там в полноте и скорости, не в токенах. На полном аудите подсистемы — тоже паритет: оркестрация нужна обоим. А вот на классе «понять подсистему end-to-end» граф оказался дешевле примерно в 3.5 раза — измеренные −71%. Не потому что запросы дешевле grep, а потому что граф сразу сузил корпус, и один агент справился там, где плечо без графа было вынуждено молча развернуть пятерых суб-агентов.
И здесь методологический сюрприз. По верхним цифрам плечо без графа на «понимании» выглядело дешевле — пока мы не вскрыли в его транскриптах 205.8k токенов вложенных агентов. Честный замер обязан считать всё дерево агентов; подозреваем, что немалая часть красивых бенчмарков этим не занимается. Универсального множителя «90%» не существует — существует класс задач. Полный разбор с замерами экономии токенов и дисциплиной проверки индекса мы вынесли в отдельную статью, вместе с ограничениями (n=1 на ячейку, один репозиторий, один тир модели).
Итог
Формула фичи проста: существующий инструмент плюс дисциплина обращения с ним плюс слоёные тесты, которые не верят никому — ни автору текста, ни красивому сниппету, ни условию собственного сценария, ни верхней строчке чужого бенчмарка. Мы дали агенту постоянную память об архитектуре — и одновременно запретили ей верить на слово. Индекс говорит, куда смотреть; фактом становится только код.