Пять или шесть лет мой GitHub не показывает почти ничего, кроме оплачиваемой работы. Последние личные проекты в этом блоге — из 2017 года: фракталы, L-системы, небольшие эксперименты с творческим кодом. А потом — пауза.

Эту паузу закрыла агентная разработка — но не по той причине, которую обычно называют. Агенты не просто набирают код за меня. Изменилось то, что я перестал тратить бóльшую часть времени на написание кода вручную и начал тратить его на планы и архитектуру. Именно этот сдвиг позволил держать высокое качество, быстро итерируя, и именно он вернул смысл в разработку ради неё самой.

Этот пост описывает рабочий процесс за всем этим, отточенный на одном реальном проекте — десктопном приложении для анализа торгов. Само приложение здесь не важно — если коротко, это Python-сайдкар (FastAPI на loopback), управляемый агентом по MCP, и Electron-просмотрщик, который только отрисовывает, — важен процесс вокруг него. Этот процесс уже дал 109 завершённых планов, 107 записей об архитектурных решениях, 56 MCP-инструментов и 10 навыков, созданных в одиночку за несколько месяцев. Дальше — то, что стоит перенять.

Это началось как дефект, а не как замысел

Я не собирался строить команду агентов. Я решал одну конкретную проблему — потерю контекста.

За долгую сессию модель «плывёт». Она забывает решения, принятые час назад, неверно выводит их заново и противоречит собственным прежним ответам. Исправление — перестать держать контекст внутри разговора и записывать его вне модели, в долговременных документах. Заметка Мартина Фаулера про привязку к контексту описывает тот же принцип: опирать модель на устойчивый внешний контекст, а не полагаться на её память. Побочная польза в том, что внешний контекст читаем человеком — его можно прочитать и рассмотреть диаграммы, чего нельзя сказать о токенах в окне контекста.

Однако написание хороших планов требует сессии, единственная задача которой — писать планы, держа в контексте соответствующие шаблоны и соглашения и ничего больше. Из этого ограничения родился навык architect. Реализация этих планов требует отдельной сессии, держащей соглашения по коду и контроль качества. Так появился dev. Разделение труда не насаждалось сверху — оно вытекло из решения выносить контекст в планы.

Роли

Сейчас есть десять навыков, и каждый подчиняется одному правилу: один навык, одна ответственность, одна ограниченная область кодовой базы. Карта владения задана явно:

flowchart TB
human["я за клавиатурой"]
architect["architect: планы, ADR, диаграммы, ревью"]
dev["dev: Python-сайдкар, хранение, CI"]
sa["strategy-author: strategies/"]
bt["backtester: backtest/, runs/"]
ui["ui-builder: desktop/ (Electron + React)"]
human --> architect
architect -->|назначает фазу плана| dev
architect --> sa
architect --> bt
architect --> ui
dev --> architect
sa --> architect
bt --> architect
ui --> architect

Два свойства этих навыков важнее самих границ.

Во-первых, при «пустом» вызове навык ждёт. Если я обращаюсь к architect, не сформулировав задачу, он не читает файлы и не сканирует дерево документации, пытаясь угадать, чего я хочу. Он сообщает, чем владеет, и останавливается. Сканирование репозитория ради определения намерения — как раз то поведение, которое расходует контекст без отдачи, поэтому оно запрещено; чтения привязаны к задаче и выполняются только когда появляется конкретная задача.

Во-вторых, границы кодируют доменные правила, а не только владение файлами. Навыки-аналитики сообщают об условиях, и им запрещено рекомендовать действие — условия суть факты, решения принадлежат пользователю. Ровно одному навыку, advisor, позволено пересекать эту черту, и только на основании отдельной записи о решении (ADR-0029): его вывод всегда помечен как рекомендательный, всегда несёт обоснование и основу — на бэктесте или прогнозе, — и он не хранит ключей и не выставляет ордеров. Пересечение существует в одном слое, и его не дают просочиться обратно в аналитиков. Мысль обобщается: граница навыка — это место, где закрепляется правило, а не просто разделяются файлы.

Планы говорят что; ADR говорят почему

Это два разных документа, и их разделение — половина ценности.

План описывает, что строится и как: упорядоченный список фаз, каждая выходит отдельным коммитом, каждая помечена владеющим навыком и конкретным критерием «готово, когда». План одноразовый — как только его финальная фаза внедрена и прошла ревью, файл перемещается в plans/done/. Показательная фаза, процитированная дословно из плана, добавившего слой advisor:

### Phase 1 — advisor/ package: Recommendation model + fusion engine
- Owner skill: dev
- Files touched: advisor/models.py, advisor/fusion.py, tests/advisor/*
- Done when: constructing a Recommendation with an empty/absent basis RAISES a
  validation error (a test asserts this). The fusion is deterministic: identical
  inputs produce an identical recommendation. The package imports only analyst
  OUTPUTS — an import-lint walks the package AST and asserts no import of
  analyst-internal modules.

Пункт «готово, когда» — это механизм. Это не «реализовать advisor»; это набор проверяемых поведенческих утверждений — ошибка валидации при отсутствии основы, побайтово идентичный вывод при идентичном вводе, проверка импортов на уровне AST. План, сформулированный так, можно проверить; расплывчатый — нельзя, и исполнитель его тихо игнорирует. Каждый план также явно указывает, чего он не делает, что ограничивает объём так же твёрдо, как и список фаз.

ADR фиксирует, почему решение было принято вместо альтернатив, и он только дополняется. Принятый ADR никогда не редактируется; его замещает более поздний, который на него ссылается. Так получается читаемая история решений: ADR-0001 выбрал Tauri для оболочки, ADR-0005 заместил его на Electron; ADR-0003 задал политику вендоринга, ADR-0009 заместил её собственным слоем данных; ADR-0014 описал MCP как вторичный протокол, ADR-0015 уточнил его до основного управляющего интерфейса днём позже. Раздел, который окупает себя, — список отклонённых вариантов с причинами; например, о том, почему графики не рисуются в терминале:

## Alternatives considered
### Alternative A — Remove Electron entirely; render charts in the terminal
Rejected because the required visualization fidelity — candlesticks with overlays,
zoomable equity curves, marker hover-text — does not exist in terminal chart
libraries. The alternative reads as "consistent" but trades product value for
ideological purity.

Спустя месяцы, когда та же идея всплывает снова, ADR уже зафиксировал контраргумент. Нумерованные планы и ADR — первое, что я порекомендовал бы внедрить; все остальные практики здесь опираются на них. Номера планов и ADR — независимые последовательности, назначаемые из отслеживаемого «следующего свободного» номера, чтобы параллельные сессии на них не сталкивались.

Проектирование, реализация и ревью — три отдельные сессии

Наблюдение, перестроившее весь процесс, таково: внимание агента ограничено не только бюджетом токенов, но и его собственной историей реализации. Сессия, написавшая кусок кода, — плохой его рецензент, потому что читает код, который намеревалась написать, а не тот, что есть на самом деле.

Поэтому ревью — это свежая сессия architect без памяти о реализации, оценивающая весь план относительно согласованного проекта. Переход к такой схеме существенно снизил число дефектов.

У свежего ревью есть собственный режим отказа, с которым я столкнулся напрямую. Ранние финальные ревью почти всегда возвращались чистыми — «изменений не требуется», — и тем не менее приложение иногда не запускалось. Ревью, которое всегда проходит, — это не ревью. Исправлением стало дать ревью конкретные обязанности, а не общую инструкцию «проверь работу». Теперь рецензент открывает названные файлы тестов и читает тела проверок; зелёный прогон CI не принимается как доказательство. Это выросло из конкретных случаев в самом первом плане:

  • sidecar-supervisor.spec.ts проверял expect(pid).toBeGreaterThan(0) для произвольного процесса — тавтология, которая проходила, ничего не проверяя. «3 из 4 спеков проходят» скрывало, что один из этих трёх был заглушкой.
  • security.spec.ts обещал в своей docstring проверку «cross-origin fetch блокируется CSP», но в файле такого теста не было. Утверждение жило только в комментарии.
  • Исправление пути загрузки в более поздней фазе разблокировало ранее пропускавшийся спек ohlcv-view.spec.ts, который затем упал и обнажил реальный, до того скрытый баг с пустым состоянием.

Каждый из этих случаев проходит наивную проверку «тесты зелёные». Чтение проверок их ловит. Теперь это явная обязанность рецензента.

Жизненный цикл, от начала до конца:

flowchart LR
plan["architect: план + ADR"] -->|явное «go»| impl["dev: реализует все фазы, одна сессия, коммит на фазу"]
impl -->|финальная фаза внедрена| rev["architect: свежая сессия, читает проверки, прогоняет done-when"]
rev -->|церемония закрытия| done["plans/done/, ADR приняты, обновление индекса, бамп версии"]

Исполнитель делает по одному коммиту на фазу и никогда не пушит; исправления вносятся движением вперёд, а не переписыванием истории. План advisor, например, вышел двумя коммитами-фазами (47e7ac9, 339885e), за которыми последовали два коммита-исправления вперёд (97099a5, 6455786), когда ревью обнаружило, что пакет импортирует модуль инструмента вместо доменной поверхности. Церемония закрытия — фиксированная последовательность: переключить статус плана на done и перенести его через git mv в done/, перевести парные ADR из proposed в accepted, обновить индексы планов и ADR, согласовать затронутые «живые» спецификации, влить ветку реализации, если она есть, и один раз поднять версию на весь план (feat — минорную, fix — патч). Исполнитель не делает ничего из этого; разделение авторства и закрытия — в этом и смысл.

Передачи — это определённый протокол

Поскольку каждая фаза несёт метку владеющего навыка — из фиксированного словаря dev, ui-builder, strategy-author, backtester и human, — план, охватывающий несколько доменов, передаётся детерминированно. Активный навык реализует непрерывный набор своих фаз, делает коммит и передаёт управление на границе. Для пары devui-builder передача автоматизирована внутри сессии; для остальных — ручная вставка в свежую сессию. В любом случае принимающий навык переформулирует оставшуюся работу и ждёт явного «go» перед тем, как писать код. Автоматизация убирает шаг копирования-вставки, но не воротца одобрения.

Ограничения закреплены в коде, а не выражены прозой

Как только над одним репозиторием работает больше одного агента, сотрудничество не может зависеть от того, что модель «решит вести себя хорошо». Две сессии, делящие рабочее дерево, будут индексировать незаконченные файлы друг друга; так и было, пока это не предотвратили. Предотвращение — механическое, а не рекомендательное:

  • Pre-commit-хук напрочь отвергает широкую индексацию — git add -A, git add ., --all и pathspec :/ запрещены. Индексация — по явному пути, или её нет вовсе.
  • Разрушительные и переписывающие историю операции — push, reset, rebase, commit --amend, commit --no-verify — запрещены правилом. Агенты коммитят; пушу я.
  • Даже путь с сообщением коммита укреплён после реального сбоя: коммит db7f17b прервался с первой попытки, потому что длинное тире и внутренние кавычки в теле сообщения были разобраны оболочкой как отдельные pathspec’и. Именно поэтому сообщения коммитов теперь ограничены обычным ASCII.

Доменные незыблемые правила закреплены так же — в коде, а не в напоминаниях. Никакого заглядывания вперёд: решение на баре i может видеть только бары до i включительно, что обеспечивается стражем as_of на единственном слое поставщика данных, общем и для «живого», и для бэктест-путей. Детерминизм: золотой тест движка бэктеста проверяет побайтово идентичный вывод между процессами при model_dump(exclude={"run_id", "started_at", "finished_at"}), так что любой недетерминизм на финансово значимом пути валит тест. Зависимости зафиксированы точно (==X.Y.Z, без диапазонов) и отклоняются, пока им не исполнится хотя бы четырнадцать дней, что тоже проверяется при разрешении, а не принимается на веру.

Цель всего этого — не недоверие ради недоверия. Дело в том, что агента можно оставить работать без присмотра, только если его режимы отказа огорожены. Именно ограничения позволяют отвести взгляд.

Параллелизм: то, что не оправдало ожиданий

Задуманным финалом был fan-out: несколько планов в работе одновременно, каждый агент — в своём git-worktree, чтобы никакие два не делили рабочее дерево, и всё внедряется параллельно. Механизм для этого есть — по одному worktree и ветке на план, уникальный каталог данных на агента и каждый сайдкар привязан к порту, назначенному ОС.

flowchart TB
main["ветка main"]
b1["worktree plan-0090: своя ветка, каталог данных A, порт 0"]
b2["worktree plan-0091: своя ветка, каталог данных B, порт 0"]
main --> b1
main --> b2
b1 -->|merge no-ff при закрытии| main
b2 -->|merge no-ff при закрытии| main

Однако изоляция файлов убирает не все коллизии — три её переживают. Миграции Alembic образуют одну линейную цепочку, поэтому два плана, каждый из которых добавляет миграцию, дают при слиянии две «головы» без чистого решения во время выполнения — такие планы нельзя запускать параллельно. Общие индексные файлы и «следующий свободный» номер — это точки дозаписи, которые состязаются, поэтому все номера планов и ADR назначаются заранее. А общее состояние на диске разделяется только каталогом данных на агента.

На практике выигрыш в пропускной способности оказался меньше ожидаемого. Время, сэкономленное на параллельном написании кода, во многом уходит обратно на оркестрацию и слияние. Более фундаментально: ограничением была не пропускная способность агентов, а моя. Рецензировать и направлять несколько одновременных сессий, не теряя качества, превышает внимание, которым я реально располагаю. Поэтому я гораздо чаще веду один хорошо управляемый цикл, чем несколько параллельных, и отношусь к механизму параллелизма как к доступному, а не как к варианту по умолчанию. Это ограничение человека в цикле, и о нём стоит сказать прямо, а не приукрашивать.

Навыки разрабатываются эмпирически

Ещё один момент: сами навыки рассматриваются как программное обеспечение под тестами. Отдельный skill-creator прогоняет навык на наборе промптов — с навыком и без него, — оценивает выводы по написанным проверкам и сообщает долю прохождения, стоимость в токенах и задержку. Он также оптимизирует поле description — текст, определяющий, сработает ли навык вообще, — измеряя точность срабатывания на отложенных запросах. Тем самым создание промптов и навыков становится эмпирическим циклом с измеренными дельтами, а не делом интуиции.

Что из этого взять

Если внедрять только одну практику, то вот эту: выносите контекст в нумерованные планы и держите почему (ADR) отдельно от что (планы). Вынести контекст из разговора в долговременные, читаемые документы — это фундамент; специализированные навыки, ревью со свежим контекстом, механические ограничения и параллелизм на worktree — всё это способы защитить этот фундамент.

Выигрыш для меня был не столько в скорости. Он в том, что личные проекты снова стали возможны. Годами я писал код только для работы. Теперь я трачу это время на написание планов и поддержание честной архитектуры, а не на ручное производство шаблонного кода, — и в результате разработка ради неё самой снова стала тем, на что у меня хватает сил.

Исходный код (проект, на котором отточен этот процесс)