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-дисципліни.