Введение
Удобный, но хрупкий режим — реализовать фичу в одной чат-сессии: сначала обсудить, потом написать код, потом чинить CI в том же окне. Контекст быстро раздувается, а на очень длинном окне (100k токенов и выше) модель хуже помнит ранние договорённости и чаще ошибается.
Главный тезис этой статьи простой: агент справляется там, где решения уже приняты и записаны, контекст каждой итерации мал, а состояние работы живёт вне агента — в issue-трекере или файлах, а не в его памяти. Ниже — план, по которому можно провести фичу от идеи до PR с учётом этих ограничений.
Подход рассчитан на существенные фичи и идеи — там, где есть что обсуждать, зафиксировать и нарезать. Для простого багфикса, опечатки, мелкого рефакторинга или однострочной правки нет смысла запускать весь цикл, агенту и так понятно что делать.
Workflow
Мой workflow по большей части основан на подходе Matt Pocock и его наборе скиллов: последовательность шагов от идеи до финального PR там уже заложена, я опираюсь на неё как есть.
Разберу каждый шаг на примере небольшого пет-проекта geo-quiz. Стек у меня завязан на Claude Code и GitHub, но сами приёмы не про конкретный инструмент: их можно повторить с любым ИИ-агентом для работы с кодом (например, OpenCode) и с локальными файлами вместо GitHub issues.
Прежде чем разбирать каждый шаг — важная оговорка. Целиком я так работаю редко. Чаще беру отдельные скиллы и запускаю их вручную: так больше контроля, проще дебажить, видно, как всё устроено внутри, и легче адаптировать под себя. Если кусок неудобен — его не страшно заменить или выкинуть. Ниже — максимальная версия; каждая её часть работает и отдельно.
Пример реализуемой фичи
В приложении нужно добавить экран настроек (/settings): игрок задаёт количество вопросов в раунде — пресеты (10, 25, все страны каталога) или своё число; выбор сохраняется в localStorage и применяется при старте следующего раунда.

Фиксация требований
На этом этапе у Matt Pocock есть два связанных скилла:
/grill-me— агент опрашивает по плану, проходя по веткам дерева решений, пока каждая не разрешится./grill-with-docs— то же интервью, но дополнительно агент сверяет план с существующей доменной моделью, уточняет терминологию и по ходу обновляетCONTEXT.mdи ADR (Architectural Decision Record, запись об архитектурном решении).
Если речь про проект с кодовой базой — почти всегда стоит брать /grill-with-docs. Исторически первым был /grill-me — скилл, который сам по себе стал вирусным. Но в процессе работы Matt заметил, что ему регулярно не хватало общего языка с агентом (в DDD это ubiquitous language, UL): на грилл-сессиях всплывали хорошие термины, но они нигде не фиксировались, и в следующий раз их приходилось проговаривать заново. Сначала он запускал параллельно второй скилл /ubiquitous-language, который вытаскивал термины в отдельный глоссарий, а потом объединил оба в один — так появился /grill-with-docs.
В geo-quiz я запускаю именно его. Копия скилла лежит в репозитории — /grill-with-docs. Дальше всё стандартно: описываю агенту идею, вызываю скилл, и он начинает выяснять детали и фиксировать каждое значимое решение. Параллельно агент правит CONTEXT.md в корне репозитория.
CONTEXT.md — это глоссарий проекта: в нём живут определения ключевых терминов. Идея заимствована из DDD: один из его центральных концептов — ubiquitous language, единый язык, на котором говорят три стороны — код, разработчики и доменные эксперты. Когда все трое называют одни и те же вещи одинаково, исчезает класс ошибок: в разговоре сущность зовётся одним словом, в задаче — другим, в коде — третьим.
Для агента эффект ровно такой же, как для нового человека в команде: вместо того чтобы каждый раз заново выяснять, что такое, например, «раунд» или «пресет», он сверяется с CONTEXT.md и сразу использует готовый термин. Имена в коде и в PR совпадают с глоссарием, ответы агента становятся короче.
В больших репозиториях единый файл глоссария может перестать справляться: один и тот же термин в разных частях системы может обозначать разные сущности с разными правилами. На этот случай в DDD есть bounded context — явная граница, внутри которой язык согласован. У Matt для таких репозиториев в корне лежит CONTEXT-MAP.md: он не содержит определений сам, а только показывает, какие контексты есть в проекте и где у каждого свой CONTEXT.md.
/
├── CONTEXT-MAP.md
├── docs/adr/ # системные решения
└── src/
├── ordering/
│ ├── CONTEXT.md
│ └── docs/adr/ # решения внутри контекста
└── billing/
├── CONTEXT.md
└── docs/adr/
В небольшом проекте всё это избыточно — достаточно одного CONTEXT.md в корне. В большом продукте CONTEXT-MAP.md оправдан: он подсказывает агенту (и человеку), в какой глоссарий идти за термином, и не даёт смешать языки разных контекстов.
Кроме CONTEXT.md, /grill-with-docs иногда предлагает завести ADR — отдельный markdown-файл в docs/adr/. ADR заводится, если выполняются три условия: решение тяжело откатить, оно выглядит странно без контекста, имеет какой-то компромисс. Таких документов сознательно мало.
Создание PRD
Когда глоссарий устоялся и спорные решения зафиксированы в ADR, идея готова к переводу в формальный документ — PRD (Product Requirements Document).
PRD — это короткий документ, описывающий одну фичу: цель, сценарии использования, scope (что входит и что не входит), acceptance criteria. В отличие от CONTEXT.md, который описывает язык домена в целом, PRD всегда привязан к одной конкретной фиче.
PRD создаётся скиллом /to-prd — этот скилл берёт контекст чата и создаёт PRD по шаблону и далее публикует его на GitHub. Пример такого PRD: issue #13 — Configured round size (settings page).
Сам документ в репозитории не хранится — он живёт только как GitHub issue. После создания PRD я поверхностно просматриваю получившийся артефакт. Matt в своём видео говорит, что не делает этого: общий контекст с агентом, по его словам, уже достигнут на этапе /grill-with-docs, и PRD здесь нужен скорее как точка фиксации, чем как объект для согласования.
Нарезка задач
Скилл /to-issues разбивает большой PRD на небольшие задачи. Каждая из этих задач:
- Самодостаточная — можно взять, реализовать и смерджить.
- Это vertical slice (tracer bullet) — тонкий разрез через все слои (модель, сервис, UI, тесты), а не «весь backend сначала, потом весь frontend».
- Помечена как AFK (можно отдать агенту) или HITL (требует человека — например, дизайн-решение или архитектурный выбор).
Агент показывает предлагаемое разбиение, получает фидбек, итерирует и потом публикует дочерние issue на GitHub с лейблом ready-for-agent — по этому лейблу позже их забирает ralph loop (см. ниже).
На все issue, относящиеся к одному PRD, вешаются лейблы:
prd— этот лейбл получает только сам PRD-issue.prd-<N>, где<N>— номер PRD-issue. Этот лейбл получает и сам PRD, и все его дочерние issue.
Такая схема нужна, чтобы потом одной выборкой найти все дочерние issue, относящиеся к PRD.
Пример одного такого «ребёнка»: issue #14 — Configured round size: Quiz preferences store and round-start resolution.
После этого шага у меня в GitHub лежит: один PRD-issue, к нему привязаны несколько дочерних issue, у всех нужные лейблы.
ralph loop
ralph loop — это очень простой цикл:
while not done:
agent_step()
Название придумал Geoffrey Huntley — это отсылка к Ralph Wiggum из «Симпсонов». Персонаж туповатый, но упорный: делает одно и то же и не унывает от неудач. Цикл ведёт себя так же — один и тот же промпт, разный код вокруг, и так пока задача не будет закрыта.

У меня для этого есть скрипт .agents/ralph/loop.sh: он в цикле вызывает агента по одному и тому же PROMPT.md и смотрит на последнюю строку вывода — STATUS=done, STATUS=progress или STATUS=blocked. По статусу решает, выходить или звать агента снова.
Под капотом скрипт принимает номер PRD-issue, переходит в корень репозитория и в цикле до MAX_ITERS (по умолчанию 20) запускает агента с одним и тем же PROMPT.md. Вывод каждой итерации пишется в .agents/ralph/logs/<timestamp>/iter-NN.log — оттуда же скрипт грепает последнюю строку STATUS=… и решает, продолжать ли цикл.
Сам вызов агента:
claude \
--allowed-tools=Bash,Read,Edit,Write,MultiEdit,Grep,Glob \
-p "$PROMPT_BODY"
Флаг -p (он же --print) переводит Claude Code в headless-режим: агент печатает ответ в stdout и завершается, без интерактивного TUI:
~ ❯ claude -p "Hello, claude"
Hello! How can I help you today?
~ ❯
Без него вызов из скрипта не имел бы смысла — TUI заблокировал бы пайплайн.
Если вместо Claude Code использовать OpenCode, эту строку меняем на opencode run "$PROMPT_BODY" — у него ровно тот же неинтерактивный режим, который не ждёт ввода и сам автоапрувит инструменты. Остальная обвязка (логи, парсинг STATUS, цикл) остаётся как есть.
Рядом с loop.sh лежит loop-once.sh — почти тот же скрипт, но без внешнего цикла и без -p. Без -p агент запускается в обычном интерактивном TUI: я вижу, как он рассуждает, какие инструменты дёргает, где спотыкается. Это режим для наблюдения и тюнинга. На практике им я пользуюсь чаще, чем полным loop.sh — это и есть «части вместо целого» из оговорки в начале.
Принципиально важная деталь: между итерациями ralph ничего не помнит. Он берёт ровно один дочерний issue и реализует его. На следующем шаге всё начинается заново, с пустого контекста: агент перечитывает GitHub, видит, какие issue уже закрыты, какие заблокированы, и сам решает, что брать следующим.
Состояние я держу в GitHub: открытые/закрытые issue, лейблы, открытые/смердженные PR. Это единый источник истины. Если ralph упал посреди работы, то его можно просто перезапустить, он сам разберётся, на каком шаге продолжать.
Не обязательно использовать GitHub в качестве хранилища состояния. Точно так же можно хранить задачи в локальной директории tasks/ с markdown-файлами, где статус указывается во frontmatter, а агент сканирует эту папку вместо gh issue list. Ключевая идея — чтобы состояние сохранялось вне памяти агента, в устойчивом хранилище, которое переживает перезапуск.
Внутри каждой итерации ralph использует мой скилл implement-issue.
Этот скилл:
- Сверяется с
CONTEXT.mdи ADR’ами — чтобы названия в коде/коммитах/PR совпадали с доменом. - Создаёт ветку
ralph/<n>-<slug>от epic-ветки PRD (prd/<N>-<slug>), а не отmain. - Реализует issue через TDD — это отдельный скилл
tdd. Используется vertical slice: один тест → минимальная имплементация → следующий тест. Acceptance criteria из тела issue становятся списком тестов. - Прогоняет локально lint, prettier, build, vitest.
- Открывает PR в epic-ветку с
Closes #<n>. - Передаёт PR скиллу
babysit, который добивает его до зелёного CI и сквош-мержит в основную PRD-ветку.
Все дочерние PR сливаются в epic-ветку prd/<N>-<slug>. Финального слияния в main ralph никогда не делает — это моя зона ответственности.
Финальный PR в main
Когда ralph закрывает последнего ребёнка PRD, у меня в GitHub лежит один большой epic-PR prd/<N>-<slug> → main, состоящий из аккуратной серии сквош-коммитов «один коммит на issue».
Пример PR в epic-ветку: PR #18 — Configured round size: Settings page and home navigation.
Дальше:
- Прохожу функциональность глазами и руками в браузере.
- Читаю diff целиком: меня интересует, как фича выглядит как единое целое
- С помощью агента делаю правки там, где агент срезал угол или повернул не туда
- После QA — мержу epic в
main. GitHub автоматически закрывает все дочерние issue поCloses #<n>и сам PRD.
Если коротко
Полный путь от идеи до мержа в main:
/grill-with-docs— агент опрашивает по плану, фиксирует язык и спорные решения вCONTEXT.mdи ADR./to-prd— собирает контекст чата в PRD и публикует его как GitHub issue с лейбломprd./to-issues— режет PRD на дочерние vertical-slice issue и вешает routing-лейблы (prd-<N>,ready-for-agent).- ralph loop — берёт
ready-for-agentissue, черезimplement-issueдоводит её до PR в epic-ветку;babysitдобивает CI и сквош-мержит. - Финальный мерж — я руками прохожу epic-PR в браузере, читаю diff целиком, точечно правлю и мержу в
main.
К моменту, когда ralph берёт задачу, ему уже почти нечего «придумывать». Это не гарантирует хороший результат — он может быть и плохим, и не тем, что задумывалось. Но чем меньше у агента остаётся пространства для неопределённости, тем меньше он ошибается. Из всего, что я пробовал, этот подход работает лучше всего.