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).