My Architect, часть 10: память о репозитории — код-граф, которому запрещено верить на слово
Это десятая статья серии про My Architect и продолжение девятой — истории скила recursive-context, который учит агента дисциплине работы с данными больше контекстного окна. На этот раз: четыре релиза за один день (v1.14.0 → v1.16.1), которые дали агенту персистентный граф кода, и контролируемый эксперимент, который измерил его реальную пользу вместо пересказа чужого маркетинга. Все числа наших прогонов — из телеметрии реальных запусков 2026-07-03; факты о Graphify — из его README и живой установки; оценки помечены явно. Ничего не додумано.
Пролог. Агент с амнезией
У LLM-агента нет памяти между сессиями. Каждый новый разговор о твоём репозитории начинается с чистого листа: агент снова grep'ает, снова читает те же файлы, снова строит в голове ту же карту — и выбрасывает её в конце сессии. Мы это измерили на собственном репозитории: сбор фактов о хорошо знакомой кодовой базе стоил ~77 тысяч токенов и 20 вызовов инструментов — и так каждую сессию, по кругу.
Отдельная беда — вопросы вида «что сломается, если я поменяю сигнатуру этой функции?». Ответ требует знать всех потребителей символа. Grep находит их, но за цену полного прохода; а главное — у агента нет места, где это знание могло бы пережить сессию.
Вокруг этой боли уже вырос рынок инструментов «код-как-граф» — с лозунгами уровня «90% экономии токенов» и «агент в 500 раз умнее». Нам нужны были не лозунги, а ответ на два вопроса: какие классы ошибок это убирает и где экономия настоящая, а где популизм. Спойлер: в конце статьи — таблица с измеренными числами.
Глава 1. Не строить своё: Graphify
Инструмент для персистентной памяти о коде уже существует — Graphify (open source, MIT, ~77k звёзд): парсит код tree-sitter'ом полностью локально (36 языков, без API-вызовов и телеметрии), складывает граф символов и связей в graphify-out/graph.json, рядом кладёт интерактивную HTML-карту и готовый текстовый отчёт о репозитории. Свежесть индекса поддерживают git-хуки; для командной работы есть merge-driver, чтобы граф можно было коммитить без конфликтов.
Мы сознательно не стали строить свой граф-движок. Вместо этого научили наш скил decomposition-дисциплины распознавать лежащий в репозитории индекс и правильно им пользоваться. Вся фича — это markdown-справочник с правилами плюс несколько строк-указателей: у плагина не появилось ни одной жёсткой зависимости. Нет графа — всё работает как раньше; нет графа, а задача тяжёлая — агент один раз предложит владельцу поставить и собрать (и сделает это сам после «да»; молчаливые установки запрещены).
Насколько это дёшево на практике: на нашем репозитории (351 файл) сборка графа заняла секунды и дала 2655 узлов, 4948 рёбер и 153 автоматически найденных кластера — 6.1 МБ на диске, ноль API-затрат. Один запрос после этого выглядит так:
$ graphify explain "getBlockers"
Node: getBlockers() Source: src/hierarchy/model.ts L419 Degree: 9
<-- NodePropertiesPanel.tsx [imports]
<-- validator.ts [imports]
<-- validateHierarchy() [calls]
...
Секунда — и у агента все потребители функции с путями. То, что раньше требовало полного прохода по репозиторию, стало запросом. Важная деталь для всего дальнейшего разговора об экономике: сам граф токенов не тратит вообще — и сборка, и запросы это локальные утилиты, а не вызовы модели.
Глава 2. Правило, ради которого всё затевалось
Индекс — это кэш, а кэш умеет врать: он отстаёт от кода между пересборками. Поэтому в центре фичи не команды, а три правила:
- Граф — навигация и кандидаты, НЕ источник фактов. Ответ графа говорит, КУДА смотреть; фактом становится только то, что подтверждено живым файлом. Контракт фактов (
{утверждение, путь-доказательство, уверенность}) не изменился ни на йоту. - Freshness-check обязателен. Первым делом агент сравнивает время сборки индекса с временем последнего коммита. Протух — сказать вслух, предложить владельцу пересборку (а если git-хук не стоит — один раз предложить и хук, чтобы протухание не повторялось каждую сессию), кандидатов помечать «по устаревшему индексу».
- Verify — экономно, регионами. Кандидат приходит с
путь:строка— проверка читает ±30 строк вокруг, а не файл целиком. Без этого правила проверка съедает всё, что граф сэкономил на поиске.
Глава 3. Как мы это тестировали
Скил — это текст, который должен менять поведение модели. Текст не скомпилируешь и юнит-тестом не покроешь, поэтому проверка — слоёная, и каждый слой ловит свой класс ошибок. Железное правило всех релизов: тесты пишутся до правок, и сначала фиксируется «как агент ошибается без них».
Слой 1 — триггеры: загрузится ли скил сам. Судьи видят только имя и описание скила (ровно то, что видит Claude Code при выборе) плюс запрос; три независимых судьи на кейс; в наборе — позитивы на двух языках, коварные почти-попадания («переименуй переменную» — похоже на impact-вопрос, но скил должен молчать) и кросс-проверка, что новый скил не ворует триггеры соседнего. Финальный прогон: 30/30 голосов ровно по ключу.
Слой 2 — поведение: соблюдается ли дисциплина. Исполнитель читает скил с диска и пишет план по сценарию; независимый грейдер построчно сверяет с заранее записанными утверждениями. Каждый сценарий бьёт в конкретный способ сломаться: свежий граф → запросы до grep, но факты по файлам; протухший → заметить и не доверять; графа нет → предложить один раз и не ставить молча.
Слой 3 — злое ревью самого текста. Отдельный ревьюер не читает скил вежливо — он его исполняет: запускает шелл-сниппеты на граничных входах, поднимает docker, сверяет каждое имя команды с README апстрима.
Слой 4 — live-прогоны: настоящая установка (по согласию), настоящий граф, настоящий агент на настоящем репозитории.
Слой 5 — контролируемый эксперимент (глава 5): когда качественные проверки пройдены, остаётся вопрос «а сколько это стоит?» — и на него отвечает только A/B с телеметрией.
Глава 4. Что поймали до эксперимента. Четыре истории
Ловушка условного роутинга. Мы добавили ветку «графа нет → предложи владельцу собрать» — а ревью показало, что она недостижима: все указатели, ведущие агента в справочник графа, начинались со слов «если граф ЕСТЬ». Агент без графа просто не открыл бы файл с предложением. Классическая ошибка: новая ветка есть, но все дороги к ней гейтятся на состояние, при котором она не нужна.
Баг, который жил бы только на Linux. Сниппет freshness-проверки использовал macOS-синтаксис stat с фолбэком на GNU-вариант. Ревьюер поднял docker и показал: у GNU stat тот же флаг значит другое — команда «успешно» печатает мусор, и гейт вечно отвечает STALE. На любой Linux-машине graph-first не включился бы никогда, тихо.
Апстрим знает лучше. Мы советовали прятать graphify-out/ в .gitignore — а README Graphify рекомендует командам граф коммитить (общая карта; конфликты решает их merge-driver). Наш совет молча выбрасывал командный сценарий; теперь предложение честно даёт выбор. Заодно выяснилось, что PyPI-пакет называется graphifyy — с двумя «y».
Исполнитель, который не поверил условию задачи. Сценарий поведенческого теста утверждал «граф свежий» — агент проверил mtime по-настоящему, обнаружил, что граф его реального окружения протух (наши же коммиты успели его обогнать), и обработал расхождение по рецепту. Дисциплина «не доверяй, проверяй» сработала даже против текста задания.
Отдельным заходом мы провели аудит «до чего агент не додумается сам» — и закрыли три пробела: impact-вопросы получили собственный вход в скил (раньше «что сломается, если поменяю X?» вне трекаемой задачи не вело к графу вообще), готовый отчёт GRAPH_REPORT.md теперь читается первым при аудите (сводка уже посчитана — глупо собирать её запросами), а при протухшем графе без хука агент предлагает хук.
Глава 5. Эксперимент: меряем сами, а не цитируем маркетинг
Дизайн. Незнакомый обеим группам крупный репозиторий — NestJS, 2125 файлов (на порядок больше нашего). Два идентичных клона: плечо A — с графом (12309 узлов, 22895 рёбер, 738 кластеров; собран tree-sitter'ом за секунды, $0), плечо B — без. Три класса вопросов, промпты идентичны с точностью до пути, про граф — ни слова (плечо A обязано найти его само по дисциплине скила), одна модель (Sonnet), один контракт вывода (≥8 фактов с путями + честное покрытие + журнал всех вызовов). Метрики — токены и вызовы из телеметрии, качество — количество фактов и выборочная сверка путей.
Результаты:
| Класс вопроса | A (граф) | B (без графа) | Дельта |
|---|---|---|---|
| Impact: «потребители ModuleRef, что сломается при смене get()» | 71.4k токенов · 26 вызовов · соло · 12 фактов | 71.9k · 31 вызов · соло · 14 фактов | паритет (A: −16% вызовов и времени) |
| Понимание: «HTTP-request lifecycle end-to-end» | 75.9k · соло · 13 фактов | 56.1k сверху + 205.8k в скрытых суб-агентах (измерено) ≈ 262k · 21 факт | граф дешевле в ~3.5× (−71%) |
| Карта: «packages/core: модули, API, риски» | ≈445k (8 суб-агентов, оценка*) · 24 факта | ≈500k (9 суб-агентов, оценка*) · 25 фактов | ~паритет |
\* Оценка вложенной стоимости откалибрована на единственной точно измеренной ячейке (Q2-B: 205 759 токенов на 5 агентов) и помечена как оценка; направление погрешности неизвестно.
Вывод 1: экономия настоящая, но селективная. На классе «понять подсистему» — измеренные −71%: не потому что запросы графа дешевле grep, а потому что граф сразу сузил корпус, и один агент справился там, где плечо B было вынуждено молча развернуть пятерых. На impact-классе экономия — ноль (grep по буквальному имени символа дёшев даже на 2125 файлах; выигрыш графа там — полнота и скорость, не токены). На полном аудите — ноль (оркестрация нужна обоим; граф даёт готовое партиционирование и кандидатов, не скидку). Универсального множителя «90%» не существует — существует класс задач.
Вывод 2, методологический — скрытая оркестрация. По верхним цифрам плечо B на «понимании» выглядело дешевле (56k против 76k) — пока мы не вскрыли в его транскриптах 205.8k токенов вложенных агентов. Красивые сравнения «агент с инструментом против агента без» систематически недооценивают плечо, которое тихо делегирует. Честный замер обязан считать всё дерево агентов — подозреваем, что немалая часть популярных цифр этим не занимается.
Дисциплина недоверия к графу отработала и внутри эксперимента: плечо A на impact-вопросе отсеяло ложноположительных кандидатов графа (barrel-импорты) живым grep'ом, а на аудите верифицировало циклы из графа и два ложных отбросило. Граф давал наводки — фактами становилось только подтверждённое.
Бонус, которого не заказывали: аудит-агенты плеча B нашли в ядре NestJS два похожих на настоящие бага (присваивание в Map через индекс, которое никогда не читается; return вместо continue, молча обрезающий регистрацию middleware) — незапланированное подтверждение, что глубина аудита у дисциплины настоящая.
Ограничения — честно: n=1 на ячейку (без дисперсии), один репозиторий, один тир модели; вложенная стоимость двух ячеек оценена по калибровке на единственной точно измеренной (и помечена как оценка); плечи сами выбирали стратегию — это фича дизайна (измеряем систему целиком), но она смешивает «граф» с «решением не оркестрировать»; долгосрочная амортизация повторных сессий этим экспериментом не измерялась — она остаётся гипотезой с сильным априори (~77k «налога на переизучение» за сессию против одноразовой бесплатной сборки).
Глава 6. Было → стало
| Было | Стало | |
|---|---|---|
| Знание репо между сессиями | не переживает сессию; ~77k токенов на переизучение | граф собирается за секунды один раз; кандидаты — мгновенные бесплатные запросы |
| «Кто зовёт X / что сломается?» | полный grep-проход; вне трекаемой задачи агент про граф не знал | самостоятельный вход: graphify affected "X" → verify регионами ±30 строк — включая indirect-связи, которых grep не видит |
| Понимание подсистемы на большом репо | скрытый fan-out суб-агентов, ~262k токенов | один агент с графом, ~76k — −71%, измерено |
| Аудит репозитория | разведка с нуля | GRAPH_REPORT.md (готовые хабы, кластеры, неожиданные связи) читается первым |
| Доверие к индексу | — | freshness-гейт; протух → вслух + предложение пересборки и хука; факты — только по живым файлам |
| Установка | — | по явному «да» владельца: агент предлагает один раз, ставит и собирает сам; отказ уважается всю сессию |
Эпилог
Формула фичи: существующий инструмент (Graphify) + дисциплина обращения с ним (наш скил) + слоёные тесты, которые не верят никому — ни автору текста, ни красивому сниппету, ни условию собственного сценария, ни, как выяснилось в главе 5, верхней строчке чужого бенчмарка. Из всех существенных находок четырёх релизов ни одна не была найдена «вычиткой» — все принесло исполнение: судьи, грейдеры, docker, живые прогоны и телеметрия эксперимента.
Осознанно отложено: триаж pull request'ов через граф (у нас нет PR-процесса — инструмент без потребителя не делаем), петля памяти запросов save-result/reflect (отдельный разговор) и повторение эксперимента с n>1 и вторым репозиторием — если селективная экономия подтвердится и там, у «90% хайпа» появится честная замена: «−70% на классе „понимание“, ноль на остальных».
Как попробовать
Код-граф едет в плагине my-architect начиная с v1.14.0 (актуальная — v1.16.1). Руками звать не нужно: скил сам распознаёт граф в репозитории, а на тяжёлой задаче без графа — сам предложит поставить.
- Заведи аккаунт на my-architect.app, возьми токен на странице API Keys и экспортируй его в той же сессии, где запускаешь Claude Code:
``bash export MCP_API_KEY=mcp_ВАШ_ТОКЕН ``
- Добавь маркетплейс и поставь плагин:
`` /plugin marketplace add d7561985/my-architect-marketplace /plugin install my-architect@my-architect-marketplace ``
- Граф можно собрать и руками, не дожидаясь предложения агента:
``bash pip install graphifyy && graphify build . ``
- Уже стоит плагин?
/plugin marketplace update my-architect-marketplace→/plugin update my-architect.
---
Факты и ссылки
- Скил-справочник:
plugins/my-architect/skills/recursive-context/references/code-graph.mdв открытом репозитории github.com/d7561985/my-architect-marketplace; релизы v1.14.0 (f2ca07d), v1.15.0 (1257064), v1.16.0 (ee0a711), v1.16.1 (6f35623), все — 2026-07-03. - Полные данные тестов и эксперимента:
skills/recursive-context/evals/RESULTS.md(RED/GREEN/LIVE/A/B); датасетыtrigger-evals.json(13 кейсов),behavior-evals.json(10 кейсов). - Graphify: github.com/safishamsi/graphify (MIT; PyPI-пакет
graphifyy); наша установка: 0.9.5. - Предыдущая часть цикла: My Architect, часть 9 — про сам скил decomposition-дисциплины.