My Architect, частина 9: Recursive context — як ми навчили агента чесно казати, чого він не прочитав
Це дев'ята стаття серії про My Architect. Коротко для тих, хто приєднався щойно: My Architect — система, де проєкт живе між сесіями AI-агента; агент веде його через MCP, людина бачить прогрес у візуальному інтерфейсі. Ця стаття — історія однієї фічі: від дослідження MIT до скіла recursive-context у плагіні my-architect v1.13.0. Усі числа наших прогонів — із реальних запусків 2026-07-03; числа дослідження RLM — зі статті arXiv:2512.24601. Нічого не додумано.
Пролог. Проблема, яку великий контекст не розв'язує
У сучасних агентів величезне контекстне вікно, і це створює небезпечну ілюзію: «влізе — отже впорається». На практиці робота з великими даними ламається трьома способами, і жоден із них не лікується розміром вікна.
Перший: вікно — розхідник. Агент, який «просто прочитав» лог на 200 МБ, або не прочитав його (вікно скінчилося), або спалив бюджет сесії на один файл — і далі працює недогризком контексту.
Другий: правдоподібність замість фактів. Попроси агента «збери, що вже працює в коді» — і отримаєш гладкий текст, у якому факти з коду, переказ документації та здогадки не відрізниш одне від одного. Великий контекст не дає гарантій роботи з даними: модель уміє звучати впевнено і без опори на джерело.
Третій: тихе звуження покриття. Агент обробив 40 файлів із 60, щось упало дорогою — а у відповіді все одно звучить «я проаналізував репозиторій». Ніхто не збрехав навмисне; просто ніхто й не обіцяв рахувати, що пропущено.
До цієї фічі путньої дисципліни на такі випадки в нас не було: поведінка агента — як пощастить конкретній сесії, покриття ніхто не обіцяв рахувати, а факти у відповідях не відрізниш від переказу документації.
Розділ 1. Ідея: стаття про RLM і чому нам не знадобилася їхня бібліотека
У грудні 2025 група з MIT (OASYS lab) опублікувала роботу Recursive Language Models з open-source репозиторієм alexzhang13/rlm. Ідея проста і красива: замість того щоб запихати гігантський вхід у промпт, rlm.completion() кладе його змінною в REPL — і модель пише код, який цей вхід розглядає, нарізає і рекурсивно викликає суб-модель (rlm_query, з батчингом і лімітом паралельних викликів) над шматками. Контекст перестає бути текстом промпта і стає зовнішнім середовищем.
Числа зі статті: обробка входів до 100× довших за рідне вікно; на long-context бенчмарках медіанний відрив від vanilla GPT-5 — +26–130% залежно від baseline-стратегії (і ~+13% проти baseline самого Claude Code), за порівнянної вартості. Важливе застереження авторів: рекурсивна декомпозиція виграє на природно розкладних задачах (аналіз коду, обробка документів) і шкодить цілісним послідовним міркуванням. Репозиторій пропонує REPL-оточення від локального процесу до Docker і хмарних пісочниць і відзначає застосування в DSPy, Symbolica та Google Cloud.
І тут ми помітили головне: у Claude Code всі примітиви RLM уже є, нативно. Bash — це REPL для детермінованого попереднього нарізання. Workflow-скрипт — звичайний JavaScript-сценарій, з якого агент оркеструє суб-агентів: agent() — буквально «викликати суб-LLM як функцію», ізольовано від історії сесії; pipeline() і parallel() — батчинг з автоматичним лімітом паралелізму; budget — стеля витрат. Тягнути python-залежність із другим шаром рекурсії всередину харнеса, який сам це вміє, — надлишково. Отже, потрібен не пакет, а скіл: документ, який закріплює дисципліну.
Розділ 2. Що будували
Скіл recursive-context у плагіні my-architect: тонкий роутер + три рецепти + один канонічний workflow-скрипт. Задум ми одразу заклали ширше, ніж «один величезний файл»: ті самі принципи покривають аудит кодової бази і видобуток фактів із репозиторію для вимог — аудит репо це рівно та сама big-corpus задача, яку стаття називає ідеальною для рекурсії.
Ядро дисципліни в одному абзаці: розмір-гейт до читання (ls/wc першою дією; ≤256 КБ і ≤5000 рядків — читай нормально, скіл мовчить) → index, don't ingest (межі шматків ріже код у scratchpad, контент не тече через вікно; для «голкових» задач — спершу грубий grep) → fan-out ізольованих суб-агентів, кожному лише його шматок + вузьке питання, повернення строго за схемою, не прозою → рекурсія як цикл (згортання агрегату до малого всередині одного скрипта) → синтез із чесним покриттям (скільки шматків оброблено, які впали). Для фактів із коду — окремий контракт: {claim, evidence_path, confidence}, і жорстке правило — твердження без шляху в код фактом не є: воно йде в документ плейсхолдером [факт: <питання>], а не правдоподібною заглушкою.
Як це виглядає наживо
Перші секунди роботи — не читання, а розвідка кодом. Реальні команди з живого прогону на 48.7-мегабайтному лозі; весь «контакт» основного агента зі вмістом файлу — шість рядків проб:
wc -lc fixture.log # 600000 рядків, 48 735 680 байт → гейт спрацював
head -4 fixture.log # якої форми записи? (4 рядки)
tail -2 fixture.log # (ще 2)
split -l 10000 -a 3 fixture.log chunks/chunk- # 60 шматків по ~793 КБ: ріже код, не читання
Далі кожен суб-агент отримує ізольоване завдання — лише свій шматок і вузьке питання. Фрагмент реального промпта з того прогону:
Проаналізуй ЛИШЕ файл …/chunk-aat — це шматок production-лога (~10000 рядків). Працюй grep/awk/Read строго всередині цього файлу, нікуди більше не ходи. Фоновий шум лога має вигляд: «\<timestamp\> DEBUG|INFO|WARN [auth|billing|catalog|gateway|search] request rid=N handled in Xms status=200». Питання: знайди ВСІ рядки, які НЕ вписуються в цей шаблон шуму […] Якщо аномалій немає — поверни порожній findings.
Відповідати суб-агент зобов'язаний не прозою, а структурою. Ось знахідка з того прогону — дослівно, як вона повернулася (схема змушує додати точну цитату-доказ):
{
"claim": "ERROR-level log line from an unexpected component (payment-reconciler)
reporting a panic/ledger drift, not matching the standard noise template",
"evidence": "2026-06-25T00:00:00 ERROR [payment-reconciler] PANIC: ledger drift detected txn=TXN-2222",
"chunk": "…/chunks/chunk-aat"
}
Оркестрація — звичайний JavaScript, який агент передає Workflow-тулу. Серце скрипта (скорочено; повний канон лежить у скілі):
const rawPartials = await pipeline(groups, (g, _o, i) =>
agent(`Read ONLY these files: ${g.join(', ')}. Question: ${A.question}. …`,
{ label: `map:${i}`, schema: FINDING }))
const partials = rawPartials.filter(Boolean) // null = група, що впала
const failedGroups = rawPartials.map((r, i) => (r ? -1 : i)).filter(i => i >= 0)
let prevSize = Infinity // рекурсія = цикл зі страховкою
while (JSON.stringify(working).length > 30_000) {
const size = JSON.stringify(working).length
if (size >= prevSize) { log(`no progress — stop`); break } // гарантія завершення
prevSize = size
/* …reduce-агенти згортають знахідки партіями… */
}
return { findings: working, groupsSucceeded: partials.length, failedGroups } // покриття чесне
А так виглядає «факт» у задачі про вимоги — дослівний елемент із живого прогону по нашому репозиторію (зверни увагу: шлях і рядки, не «десь у коді»):
{
"claim": "updateNode(...) gates: if updates.status === 'done' && existing.status !== 'done',
it computes getBlockers(...) and throws HierarchyValidationError(..., 'BLOCKED_BY_OPEN_ITEMS')",
"evidence_path": "src/server/domain/hierarchy.ts:189-215",
"confidence": "verified"
}
Розділ 3. Чому тести саме такі
Ми йшли за «залізним законом» розробки скілів: eval-датасети пишуться до файлів скіла, baseline знімається до авторингу. Якщо ти не бачив, як агент помиляється без скіла, — ти не знаєш, чого скіл учить. Тести вишикувалися в три шари, кожен відповідає на своє питання.
Тригер-тести: «чи завантажиться скіл сам?» Я від самого початку поставив вимогу: агент має автоматично розуміти, коли взяти скіл, — без підказки від людини. У Claude Code рішення про завантаження ухвалюється за іменем та описом — тому судді в тесті бачать лише name+description і запит користувача, нічого більше. Три незалежні судді на кейс; у датасеті не лише позитиви (лог 200 МБ, дамп, транскрипт, аудит репо — російською та англійською), а й near-miss негативи: YAML на 300 рядків, правка трьох відомих файлів, 40 однотипних тікетів із CSV. Плюс крос-регресія: 10 позитивів сусіднього скіла myarchitect — новий скіл не повинен красти чужі тригери. Скіл, який фаєриться на все підряд, шкідливіший за скіл, якого немає.
Поведінкові тести: «чи дотримано дисципліну?» Шість dry-run кейсів, кожен б'є в конкретний спосіб зламатися: гейт до читання (проти «спершу просто прочитаю»); мовчання під порогом (проти карго-культу — 180-рядковий лог не заслуговує на конвеєр); grep до fan-out (ефективність); рекурсія циклом, а не вкладеним workflow() (жорстке обмеження платформи — вкладення глибше одного рівня неможливе); контракт фактів; і фолбек, коли Workflow-тула взагалі немає. Виконавці пишуть план дій, незалежний грейдер порядково звіряє з assertions.
Live-прогони: «чи працює машинерія по-справжньому?» Dry-run перевіряє план, але не виконання. L1 — синтетичний лог на 600 000 рядків / 48.7 МБ із 12 засіяними «голками», позиції яких відомі детермінованому генератору: це дає вимірні recall і precision замість вражень. Голки трьох типів не випадкові: 4 рідкісні PANIC (пошук аномалій), 3 підозрілі config-перевизначення — і 5 кроків однієї сесії (login → elevate-privileges → export-full-dump → wipe-audit-trail → logout), розмазаних по різних кінцях файлу: цей тип перевіряє синтез — чи зможе конвеєр зібрати зв'язний сюжет зі шматків, оброблених різними агентами. L2 — не синтетика: реальний репозиторій і реальне питання — «що вже працює в коді і не повинно бути зачеплене?» — з вибірковою ручною перевіркою шляхів-доказів.
Розділ 4. Труднощі. Три кейси, які зробили фічу кращою
Кейс 1. Baseline виявився занадто добрим
Неприємний (і найкорисніший) сюрприз RED-заміру — прогону тих самих задач без скіла, знятого до того, як скіл був написаний. Ми дали агентові той самий 48.7-мегабайтний лог — очікуючи побачити спробу читання в лоб. Натомість Sonnet холоднокровно зробив wc -l, подивився 20 рядків голови і 21 хвоста, пройшовся grep/awk-статистикою за рівнями і компонентами — і знайшов усі 12 голок за 7 викликів інструментів і ~37 тисяч токенів. Baseline із видобутку фактів зі знайомого репо теж був сильним.
Чесний висновок довелося вбудувати і у звіт, і в сам скіл: на greppable-голках в однорідному машиночитному лозі grep-first уже «в крові» у моделей — і скіл не повинен із цим боротися, він повинен це узаконити (рецепт giant-file, крок 2: кандидати влізли → відповідай напряму, fan-out не потрібен, так і скажи). Справжня дельта скіла не «порятунок від наївного читання», а три інші речі: контракт (схеми, confidence-розмітка, плейсхолдери, покриття — baseline повернув сильну прозу, де факти з коду і переказ доків нерозрізненні), масштабованість на задачі розуміння без grep-якорів і корпуси більші за вікно, і консистентність — дисципліна закріплена регресійною сіткою, а не залежить від настрою конкретної сесії. Якби ми не зняли baseline, ми б продавали скіл не за те, що він робить.
Кейс 2. Цикл, який міг не скінчитися, і покриття, яке брехало
Зведене рев'ю знайшло в канонічному скрипті два дефекти, прикрі саме своєю тонкістю. Перший: reduce-цикл («згортай знахідки, поки агрегат не стане маленьким») не мав гарантії завершення — reduce-агент, якому веліли «дедуплікуй і відкинь нерелевантне», має повне право повернути стільки ж знахідок, скільки отримав, якщо всі унікальні й релевантні. Агрегат перестає зменшуватися — цикл продовжує плодити агентів. Фікс — no-progress guard: раунд не зменшив агрегат → виходимо з логом; після фікса термінація доказова (відстежуваний розмір строго спадає).
Другий дефект був гіршим, бо порушував власне правило скіла. Скрипт повертав chunksProcessed: <усі чанки> безумовно — навіть якщо частина map-агентів упала і їхні шматки ніхто не читав. Те саме «тихе звуження покриття» з прологу, тільки тепер усередині інструмента, який обіцяв із ним боротися. Фікс: невідфільтрований масив результатів зберігає null на місці кожної групи, що впала, — це єдиний спосіб дізнатися, що саме не покрито; повернення стало чесним: {groupsSucceeded, failedGroups, …}, і правило «failedGroups зобов'язаний потрапити у фінальну відповідь користувачеві» вписано в текст скіла.
Туди ж — урок про рев'ю тестової обв'язки: рев'юер виконував генератор фікстури на межових входах, а не читав його. Так знайшлися нескінченний цикл на нечисловому аргументі (POSIX awk порівнює число з рядком посимвольно — умова циклу істинна вічно; 76 МБ сміття за 3 секунди) і тиха втрата голки на малих розмірах (позиція всікалася в неіснуючий рядок 0).
Кейс 3. Чотирнадцять мілісекунд
Перший live-запуск L1 завершився за 14 мс із результатом chunksTotal: 0. Жоден агент не стартував. Мінімальний воркфлоу-зонд показав причину: args приходить у скрипт JSON-рядком, а не об'єктом — навіть якщо передати об'єкт. args.count був undefined, цикл побудови списку чанків не виконався жодного разу, скрипт чесно повернув порожнечу.
Ані dry-run, ані рев'ю коду цього не спіймали б — дефект живе на межі між інструментом і скриптом і проявляється лише за справжнього виклику. Фікс — один рядок захисного парсу (const A = typeof args === 'string' ? JSON.parse(args) : args) — пішов у канонічний скрипт скіла і в план, з поміткою «перевірено живим прогоном». Це найкращий аргумент за те, чому приймання було live: його задача — не підтвердити, що все добре, а знайти те, що може знайти лише реальність.
Бонус того ж роду: у субагентів-виконавців поведінкових тестів Workflow-тула фізично немає — двоє з них самі виявили це через пошук інструментів і коректно перейшли на задокументований фолбек (паралельні звичайні агенти, та сама дисципліна). Фолбек-гілка скіла пройшла живу перевірку випадково — але по-справжньому.
Розділ 5. Було → стало
| Було (RED, без скіла) | Стало (GREEN + LIVE, зі скілом) | |
|---|---|---|
| Величезний файл | немає дисципліни: як пощастить конкретній сесії; покриття не заявляється | розмір-гейт до читання; grep-first узаконено; fan-out 60 агентів за потреби; recall 12/12, precision 12/12 — baseline теж знайшов 12/12, дельта не в точності пошуку, а в явно заявленому покритті 60/60 груп і консистентності (див. Кейс 1) |
| Факти з коду | сильна проза: факти з коду і переказ доків нерозрізненні, дір не видно | 40 фактів, 100% зі шляхом і рядками, verified/inferred розділені, 2 діри — явні [факт: …]; вибіркова перевірка 5/5 підтвердила дослівно |
| Автовибір скіла | — | 60/60 суддівських голосів (позитиви, near-miss негативи, крос-регресія сусіда — 0 хибних спрацьовувань) |
| Гарантії конвеєра | — (без скіла конвеєра не існувало); у чернетці скіла до рев'ю: цикл міг не завершитися, покриття могло брехати | термінація доказова; failedGroups у поверненні; args-рядок знешкоджено |
| Вікно main-агента | ~37k токенів проб і grep-статистик проходять через вікно (так пройшов baseline) | L1: через вікно пройшло 6 рядків проб — сам 48.7-мегабайтний файл через вікно не проходив узагалі |
Побічний продукт L2, якого ніхто не замовляв: видобуток фактів розкрив реальне питання до продукту — UI пише залежність dependsOn напряму в локальний store, оминаючи HTTP-роут із валідацією. Ми завели на це окрему перевірочну задачу. Хороший конвеєр фактів знаходить не лише те, про що питали.
Розділ 6. А ефективність?
Знаходити складне — пів справи; важливо не платити конвеєром за все підряд. Ефективність тут спроєктована, і тести перевіряють саме її.
Скіл уміє мовчати. Під порогом (тест «лог на 180 рядків») — звичайне читання, нуль машинерії. Негативні тригер-кейси і крос-регресія підтверджують на всіх перевірених сценаріях (42/42 голоси «не фаєрити»), що конвеєр не запускається на задачах, де він — оверхед.
Grep до fan-out. Голкова задача з відомим якорем розв'язується вирізанням регіонів без жодного суб-агента — і агент зобов'язаний явно сказати, що fan-out не знадобився (тест 3).
Малий корпус — без Workflow. Рецепт requirements-mining прямо забороняє гармату по горобцях: ≤30 релевантних файлів → один агент зі списком. Живий L2 так і пройшов: 1 агент, ~74k токенів, 36 викликів — на виході 40 перевірюваних фактів.
Ціна повного fan-out відома і сплачена свідомо. L1 з 60 агентами коштував ~1.22M суб-токенів і ~2.5 хвилини — на цій задачі grep був би в ~33 рази дешевшим (~37k токенів, Кейс 1). Ми це знаємо і не ховаємо: на greppable-задачах скіл приписує grep і конвеєр не вмикає, а дорогий прогін був разовим прийманням машинерії — і окупився першим же знайденим багом (Кейс 3). Fan-out — інструмент для випадків, коли grep не працює в принципі (проза, транскрипти, питання розуміння), а не податок на кожен великий файл. При цьому суб-токени — витрата ізольованих вікон; вікно основної сесії залишилося чистим, а це часто найдефіцитніша валюта.
Бюджет і глибина. Канонічний скрипт поважає budget як обмежувач глибини згортання і логує ранні зупинки. Заради чесності: ця гілка перевірена лише dry-run кейсом про згортання — живого прогону з реальним budget-стопом не було.
Епілог. Що лишилося чесно недоведеним
Поріг 256 КБ / 5000 рядків — стартовий дефолт, не вистраждане число: його тюнити за досвідом. Два сценарії не перевірені наживо — це найкращі кандидати наступного eval-раунду: repo-audit цілком (multi-modal sweep + «копай, поки два раунди поспіль не сухо») на великому незнайомому репозиторії і прозовий транскрипт без grep-якорів: саме там, за статтею RLM, очікується максимальний відрив від baseline, і саме там чесний baseline показав би справжній провал без скіла. І знайдене питання про подвійний шлях запису dependsOn теж чекає на свою перевірку.
Як спробувати
Скіл їде в плагіні my-architect починаючи з v1.13.0 і вмикається сам, коли задача пахне великим корпусом — руками його кликати не потрібно (у цьому й був сенс тригер-тестів).
- Заведи акаунт на 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 ``
- Уже стоїть плагін?
/plugin marketplace update my-architect-marketplace→/plugin update my-architect.
---
Факти і посилання
- Скіл:
plugins/my-architect/skills/recursive-context/у відкритому репозиторії github.com/d7561985/my-architect-marketplace, plugin v1.13.0, тегmy-architect--v1.13.0, релізний комітb39cfc1. - Повні дані прогонів:
skills/recursive-context/evals/RESULTS.md(RED / GREEN / LIVE); датасетиtrigger-evals.json,behavior-evals.json; генератор фікстуриfixtures/gen-fixture.sh(детермінований, 12 голок). - Дослідження: Recursive Language Models, arXiv:2512.24601 (MIT OASYS, грудень 2025); репозиторій github.com/alexzhang13/rlm.
- Ключові фікс-коміти за підсумками рев'ю і live:
be0b99d(guard генератора),ab505f5(термінація reduce + чесне покриття),c512e64(захисний парсargs).