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

Эта статья — про то, что я понял и что в итоге сработало: почему LLM деградирует на плохой архитектуре точно так же, как команда людей; почему классика (SOLID, DDD, чистая архитектура) не только не устарела, но стала важнее; и как превратить архитектурные договорённости из «пожеланий в CLAUDE.md» в контракт, который машина не может нарушить. С конкретикой: два конфига deptrac для Symfony-бэкенда и пайплайн разработки с субагентом-ревьювером.

Медовый месяц и похмелье

Проект — сервис AI-обработки изображений: пользователь загружает фото, выбирает инструмент, платит кредитами, получает результат. Symfony на бэкенде, Next.js на фронте, ничего экзотического.

Первый месяц vibe coding выглядел как реклама будущего. «Добавь оплату кредитами» — готово. «Сделай очередь обработки с ретраями» — готово. Я почти не читал код, только проверял, что работает.

К третьему месяцу картина изменилась:

  • Агент добавляет проверку баланса — и она оказывается четвёртой реализацией этой проверки в проекте, чуть-чуть отличающейся от трёх предыдущих.

  • Правка в модуле обработки фото ломает админку, потому что они, оказывается, делят одну общую «помойку» — сервис на полторы тысячи строк.

  • Каждый диалог с агентом начинается с десяти минут «изучения кодовой базы», и всё равно он регулярно промахивается.

Знакомо? Это же классическая кривая легаси-проекта. Скорость внедрения фич падает по той же траектории, что и у команды людей — просто у LLM весь цикл «от стартапа до болота» занимает не три года, а три месяца.

И это логично, если вспомнить, на чём модель училась. LLM обучен на человеческом коде с GitHub — включая весь говнокод, которого там статистически больше, чем хорошего кода. Модель не знает «как правильно», она знает «как обычно». А «как обычно» — это god-объекты, копипаста и логика, размазанная по слоям.

Контекстное окно — это рабочая память джуна

Ключевая механика деградации простая.

LLM видит только то, что влезло в контекст. Чтобы безопасно изменить код, агент должен «загрузить в голову» всё, что этим кодом затронуто. И тут вступает связность:

  • Низкий coupling: правка локальна, агенту хватает одного-двух файлов, он делает изменение уверенно и правильно.

  • Высокий coupling: чтобы понять последствия правки, нужно прочитать 20 файлов. Агент читает 5, про остальные не догадывается — и правит на ощупь. Что-то ломается в месте, которое в контекст не попало.

Отсюда главный тезис статьи:

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

Хорошая архитектура делает изменения локальными. Локальные изменения влезают в контекстное окно целиком. Значит, LLM-friendly код — это ровно тот же код, который friendly для новичка в команде: понятный за пять минут, а не за две недели онбординга. Всё, что мы двадцать лет рассказывали друг другу про качество кода «для будущих поколений разработчиков», внезапно стало иметь мгновенный, измеримый эффект: либо агент справляется с фичей за итерацию, либо вы платите токенами и своим временем за его блуждания.

SOLID, DRY, KISS, YAGNI — теперь с LLM-оптикой

Классические принципы никуда не делись, но у каждого появилось новое прочтение.

SRP. Класс с одной ответственностью — маленький. Маленький класс целиком влезает в контекст вместе со своими тестами. Правка не задевает чужую ответственность, потому что чужой ответственности в файле нет. God-объект же гарантированно не влезет в голову агента вместе со всеми своими использованиями.

OCP + DIP. Если новая фича — это новый класс, реализующий существующий интерфейс, агенту вообще не нужно трогать старый код. Он смотрит на интерфейс и одну соседнюю реализацию как на образец — и пишет новую. Риск регрессии около нуля. Если же новая фича — это if в десяти местах, агент найдёт восемь из них.

DRY. У LLM патологическая тяга к копипасте. Ему всегда «дешевле» сгенерировать проверку заново, чем найти существующую: генерация — это его естественный режим, а поиск по кодовой базе — дорогая операция, съедающая контекст. Без внешнего контроля вы получите пять версий одной валидации, и при изменении бизнес-правила агент обновит три из них.

KISS + YAGNI. А вот тут обратная беда. LLM обожает «на всякий случай»: фабрику для класса с одной реализацией, конфигурируемость, которую никто не просил, слой абстракции над слоем абстракции. Выглядит солидно, ревью проходит легко («ну, аккуратно же написано»), а через месяц эта преждевременная гибкость становится тем самым лишним контекстом, который мешает следующим правкам.

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

DDD: бэкенду — да, фронтенду — нет

Прежде чем перейти к инструментам — два слова про доменно-ориентированное проектирование, потому что именно оно даёт LLM самое ценное: карту.

Ограниченные контексты и единый язык — это, по сути, способ разбить систему на куски, каждый из которых можно понять изолированно. Для агента неймспейс Core\Billing\Domain\Entity\CreditBalance — это уже документация: понятно, что это, где живёт и кого касается, ещё до чтения кода. Ubiquitous language работает с LLM даже лучше, чем с людьми: модель отлично оперирует словами, и если код называет вещи так же, как их называет постановка задачи, полдела сделано.

Но выстраданное уточнение: DDD — это про бэкенд. Именно там живёт то, ради чего DDD придуман: инварианты, транзакции, бизнес-правила, согласованность данных.

Я честно пробовал тащить ту же структуру на фронтенд — entities, репозитории, use cases в React-приложении. Получился оверинжиниринг в чистом виде: слои ради слоёв. Фронтенд — это отображение и UX-состояние. Его домен — «что сейчас на экране и что произойдёт по клику», а не «может ли пользователь потратить кредиты». Бизнес-инварианты фронту доверять всё равно нельзя — их проверит бэкенд. Фронту хватает компонентного подхода или feature-sliced design, а агенту на фронте нужна не доменная модель, а типизированный API-клиент, сгенерированный из контрактов бэкенда.

(Дисклеймер для комментариев: да, существуют rich-client приложения с настоящей доменной логикой на клиенте — графические редакторы, офисные пакеты. Речь про типичные продуктовые SPA и мобильные приложения.)

Пример: модульный монолит на Symfony

Дальше конкретика. Бэкенд проекта устроен так:

apps/backend/
├── core/                  # framework-agnostic бизнес-логика
│   ├── Auth/
│   ├── Billing/           # кредиты, тарифы, платежи
│   ├── Media/             # хранение файлов
│   ├── Task/              # оркестрация AI-пайплайна
│   ├── Tool/              # каталог инструментов обработки
│   ├── Monitoring/        # здоровье AI-провайдеров
│   ├── Shared/            # примитивы: Money, Uuid, Clock...
│   └── Port/              # ICommandBus, IQueryBus, IJobDispatcher
└── src/                   # Symfony-адаптеры
    ├── Api/               # тонкие HTTP-контроллеры
    └── Infrastructure/    # реализации портов: Doctrine, S3, шины

Каждый контекст внутри повторяет одну и ту же структуру:

core/Billing/
├── Domain/        # Entity, ValueObject, Enum, Exception, Repository (интерфейсы)
├── Application/   # Command + Handler, Query + Handler, Job, View (DTO ответов)
└── Port/          # интерфейсы наружу: IPaymentGateway...

Правила игры:

  • core/ не знает про Symfony, HTTP и Doctrine. Вообще.

  • Контроллеры тонкие: валидируют вход, диспатчат Command/Query через порт шины, возвращают View. Вся логика — в хендлерах.

  • Зависимости между контекстами направленные: Task оркестрирует пайплайн, поэтому ему можно видеть Billing (списать кредиты), Media (взять файл) и Tool (взять рецепт обработки). Monitoring видит только Task. Листовые контексты (Auth, Billing, Media, Tool) не знают ни о ком, кроме Shared.

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

Проблема в одном: сами по себе эти правила — джентльменское соглашение. И LLM — не джентльмен.

Deptrac: архитектура, которую нельзя нарушить

Сначала я описал правила слоёв в CLAUDE.md. Агент их читал. И всё равно нарушал — не назло, а потому что в обучающей выборке инжектить репозиторий куда попало «нормально». Пара реальных примеров из моих диффов:

// core/Task/Domain/Entity/ProcessingTask.php
use App\Infrastructure\Task\DoctrineTaskRepository; // инфраструктура в домене

// core/Monitoring/Application/GetProviderHealthHandler.php
use Core\Billing\Domain\Entity\CreditBalance;       // чужой контекст напрямую

Работает? Работает. Тесты проходят? Проходят. PHPStan молчит? Молчит. А архитектура уже потекла, и следующие правки поедут по этой колее.

Вывод, к которому я пришёл: правила, которые не проверяются автоматически, для LLM не существуют. Нужен enforcement — на pre-commit и в CI. Для PHP это deptrac: статический анализатор, который строит граф зависимостей между классами и проверяет его против декларативных правил. Слои описываются коллекторами (по неймспейсу, регулярке, атрибуту), ruleset говорит, кому кого можно видеть. Любая зависимость вне правил — violation, ненулевой код выхода, красный CI.

Конфиг №1: вертикальные слои

Первый конфиг режет систему горизонтальными пластами — классическая луковица чистой архитектуры. Упрощённая версия:

# deptrac.yaml — вертикальное разделение слоёв
deptrac:
  paths: [./src/, ./core/]
  layers:
    - name: Api
      collectors:
        - { type: classNameRegex, value: '#^App\\Api\\#' }
    - name: Infrastructure
      collectors:
        - { type: classNameRegex, value: '#^App\\Infrastructure\\#' }
    - name: Domain
      collectors:
        - { type: classNameRegex, value: '#^Core\\.*\\Domain\\#' }
    - name: Application
      collectors:
        - type: bool
          must: [{ type: classNameRegex, value: '#^Core\\.*\\Application\\#' }]
          must_not:
            # контракты выносим в отдельный слой
            - { type: classNameRegex, value: '#(Command|Query|View|Job)$#' }
    - name: Contracts        # CQRS-DTO и порты — публичная поверхность core
      collectors:
        - { type: classNameRegex, value: '#^Core\\.*\\Application\\.*(Command|Query|View|Job)$#' }
        - { type: classNameRegex, value: '#^Core\\.*\\Port\\#' }
        - { type: classNameRegex, value: '#^Core\\Port\\#' }
    - name: Shared
      collectors:
        - { type: classNameRegex, value: '#^Core\\Shared\\#' }
    - name: Symfony
      collectors:
        - { type: classNameRegex, value: '#^Symfony\\#' }
    - name: Doctrine
      collectors:
        - { type: classNameRegex, value: '#^Doctrine\\#' }

  ruleset:
    Api: [Contracts, Shared, Symfony]        # контроллер видит только DTO и порты
    Infrastructure: [Contracts, Application, Domain, Shared, Symfony, Doctrine]
    Application: [Domain, Contracts, Shared]
    Contracts: [Domain, Shared]
    Domain: [Shared]                          # домен не знает ни о чём
    Shared: ~
    Symfony: ~
    Doctrine: ~

Обратите внимание на слой Contracts. Контроллеру из Api нельзя видеть ни домен, ни хендлеры — только команды, запросы, view-DTO и интерфейсы шин. Это заставляет держать контроллеры тонкими автоматически: захотел агент «по-быстрому» дёрнуть доменную сущность из контроллера — deptrac не пустит.

Вендорные слои (Symfony, Doctrine) объявлены явно — так намерение видно в ruleset: Domain не имеет их в списке, значит попытка заюзать Doctrine-аннотацию в доменной сущности — это violation, а не «ну, все так делают».

Конфиг №2: горизонтальная изоляция контекстов

Первый конфиг ничего не говорит о том, может ли Billing залезть в Media. Это второй, отдельный конфиг:

# deptrac-contexts.yaml — изоляция ограниченных контекстов
deptrac:
  paths: [./src/, ./core/]
  layers:
    - name: Api
      collectors: [{ type: classNameRegex, value: '#^App\\Api\\#' }]
    - name: Infrastructure
      collectors: [{ type: classNameRegex, value: '#^App\\Infrastructure\\#' }]
    - name: Auth
      collectors: [{ type: classNameRegex, value: '#^Core\\Auth\\#' }]
    - name: Billing
      collectors: [{ type: classNameRegex, value: '#^Core\\Billing\\#' }]
    - name: Media
      collectors: [{ type: classNameRegex, value: '#^Core\\Media\\#' }]
    - name: Task
      collectors: [{ type: classNameRegex, value: '#^Core\\Task\\#' }]
    - name: Tool
      collectors: [{ type: classNameRegex, value: '#^Core\\Tool\\#' }]
    - name: Monitoring
      collectors: [{ type: classNameRegex, value: '#^Core\\Monitoring\\#' }]
    - name: Shared
      collectors:
        - { type: classNameRegex, value: '#^Core\\Shared\\#' }
        - { type: classNameRegex, value: '#^Core\\Port\\#' }

  ruleset:
    # адаптеры обслуживают все контексты
    Api: [Auth, Billing, Media, Task, Tool, Monitoring, Shared]
    Infrastructure: [Auth, Billing, Media, Task, Tool, Monitoring, Shared]
    # листовые контексты не знают друг о друге
    Auth: [Shared]
    Billing: [Shared]
    Media: [Shared]
    Tool: [Shared]
    # Task оркестрирует пайплайн: кредиты, файлы, рецепты инструментов
    Task: [Billing, Media, Tool, Shared]
    # Monitoring смотрит на здоровье Task и не лезет никуда больше
    Monitoring: [Task, Shared]
    Shared: ~

Ruleset здесь — это, по сути, карта контекстов (context map) из DDD, записанная исполняемым кодом. Она читается как документация: видно, кто оркестратор, кто листья, и почему Monitoring не может дотянуться до Billing.

Почему два конфига, а не один

Первый порыв — засунуть всё в один файл: и слои, и контексты. Я попробовал, и это те грабли, ради которых стоило писать статью.

Deptrac позволяет классу принадлежать нескольким слоям и проверяет каждую пару «слой зависимого — слой зависимости». Класс Core\Billing\Domain\Entity\Invoice в объединённом конфиге принадлежит одновременно слою Domain и слою Billing. Его зависимость от Core\Billing\Domain\ValueObject\Money (тоже Domain + Billing) порождает четыре проверки: Domain→Domain, Domain→Billing, Billing→Domain, Billing→Billing. И если вертикальная матрица разрешает не всё то же самое, что горизонтальная, вы получаете ложные violations на абсолютно легальном коде. Начинаешь давить их разрешениями — и матрица правил превращается в решето, где уже непонятно, что вообще запрещено.

Решение: два независимых конфига с взаимоисключающими наборами слоёв. Один отвечает на вопрос «правильный ли это слой», второй — «правильный ли это контекст». Каждый файл маленький, читаемый, у каждого violation — однозначная причина. Бонус: правила эволюционируют с разной скоростью. Вертикаль застыла ещё в начале проекта, а вот контекстная карта живёт — новый контекст появляется, связь добавляется осознанным коммитом в deptrac-contexts.yaml, и этот коммит виден на ревью как архитектурное решение.

Запускается всё в pre-commit хуке и в CI:

vendor/bin/deptrac analyse --config-file=deptrac.yaml
vendor/bin/deptrac analyse --config-file=deptrac-contexts.yaml

Теперь тот самый импорт чужого контекста заканчивается не молчаливой эрозией, а так:

 ----------- ------------------------------------------------------------
  Reason      Monitoring
 ----------- ------------------------------------------------------------
  Violation   Core\Monitoring\Application\GetProviderHealthHandler must
              not depend on Core\Billing\Domain\Entity\CreditBalance
 ----------- ------------------------------------------------------------

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

CLAUDE.md: намерение против факта

Deptrac проверяет факт. Но агенту всё ещё нужно знать намерение — иначе он будет писать код методом «нагенерил → отхватил от линтера → переделал». Работает, но дорого и медленно.

Для намерений есть файл правил проекта (CLAUDE.md, AGENTS.md — у кого что). Что туда стоит класть, по опыту:

  • Карту репозитория — та схема каталогов, что выше, с одной строкой о назначении каждого куска.

  • Команды проверки. У меня это одна команда make validate, которая гоняет cs-fixer, phpstan, оба deptrac-конфига и тесты. Агенту прописано запускать её после любых изменений бэкенда.

  • Архитектурные инварианты словами: «core/ не зависит от Symfony и Doctrine», «новая фича = новый Command/Query + Handler», «контроллер: валидация → диспатч → View, ничего больше».

  • Запрещённые приёмы, на которых агент попадался: «никогда не редактируй файлы миграций руками — только генерация», «никакого PDO/SQL напрямую в обход репозиториев», «не добавляй абстракцию, пока нет второй реализации» (это против YAGNI-графомании).

Формулировки — короткие и императивные, в идеале с парой «плохо/хорошо». LLM следует правилам тем лучше, чем меньше они похожи на эссе.

Принцип, который я вывел для себя:

Правила описывают намерение. Deptrac проверяет факт. Одно без другого не работает.

Правила без enforcement агент нарушает. Enforcement без правил превращает разработку в игру «угадай, что запрещено, по сообщениям об ошибках». Вместе они дают агенту то же, что хорошему новому сотруднику: понятный онбординг плюс CI, который не пустит халтуру.

Субагент-ревьювер: второй взгляд до PR

Остался последний слой защиты — от того, что статика не ловит в принципе: соответствие реализации задаче, лишние абстракции, дубли логики, пропущенные edge cases.

Сначала я просто просил того же агента «проверь свой код». Это почти бесполезно: агент, только что написавший код, смотрит на него теми же глазами, тем же контекстом и с той же уверенностью, что всё правильно. Он «заражён» собственными решениями — как и человек, перечитывающий свой код через минуту после написания.

Работает другое: отдельный субагент-ревьювер с чистым контекстом. Он не видел рассуждений кодера, не привязан к его решениям и читает диф так, как читал бы чужой PR. В Claude Code это отдельный агент в .claude/agents/, а весь процесс оформлен скилом — тем самым «пайплайном разработки».

Скил: пайплайн разработки

---
name: development-pipeline
description: Используй для нетривиальных задач разработки: новые фичи,
  изменения домена/API/схемы БД, многофайловые правки, рискованные
  рефакторинги. Цикл план → developer → reviewer → фиксы до принятия.
---

# Пайплайн разработки

Обязательный процесс для нетривиальных задач:

1. Главный агент составляет конкретный план.
2. Реализацию делает субагент `developer`.
3. Диф проверяет субагент `reviewer` (чистый контекст, диф + план,
   без истории рассуждений разработчика).
4. Блокирующие замечания → сфокусированный fix-план → снова `developer`.
5. Цикл повторяется, пока `reviewer` не примет диф.
6. Главный агент запускает `make validate` и отчитывается пользователю.

## Требования к плану для developer

- цель и границы (что в скоупе, что нет);
- файлы и документы, которые нужно посмотреть;
- шаги реализации;
- ограничения (слои, контексты, запрещённые приёмы);
- обязательные проверки (`make validate`);
- формат ответа: изменённые файлы + результаты проверок.

## Правило приёмки

Задача не считается выполненной, пока у reviewer остаются блокирующие
замечания или `make validate` красный. Замечания уровня «вкусовщина»
reviewer помечает как non-blocking — они не крутят цикл.

Субагент-разработчик

---
name: developer
description: Реализует переданный план. Не принимает решений о скоупе.
model: sonnet
---

Ты — разработчик. Тебе дают готовый план — выполни его точно.

- Не выходи за скоуп плана. Заметил проблему рядом — упомяни в отчёте,
  но не чини.
- Следуй паттернам соседнего кода: новая фича = Command/Query + Handler
  по образцу существующих.
- После изменений запусти `make validate` и приложи результат.
- Верни: список изменённых файлов, что сделано, результаты проверок.

Субагент-ревьювер

---
name: reviewer
description: Ревьюит диф после developer. Чистый контекст, только диф и план.
model: opus
tools: Read, Grep, Glob, Bash
---

Ты — архитектурный ревьювер. Тебе дают план задачи и диф. Кода ты не
писал, решений разработчика не видел — оценивай только результат.

Проверь по порядку:

1. **Соответствие плану.** Всё ли из скоупа сделано? Нет ли изменений
   вне скоупа?
2. **Архитектура.** Слои и контексты (сверх deptrac — семантика:
   доменная логика не утекла в контроллер или инфраструктуру?).
3. **DRY.** Поищи по кодовой базе: нет ли уже существующей реализации
   того, что написано заново?
4. **YAGNI.** Есть ли абстракции без второй реализации, параметры,
   которые никто не передаёт, «задел на будущее»?
5. **Корректность.** Edge cases, обработка ошибок, конкурентность,
   безопасность (авторизация, инъекции, доступ к чужим данным).
6. **Тесты.** Покрывают ли новое поведение? Не подогнаны ли под
   багованную реализацию вместо ожидаемого поведения?

Формат ответа: список замечаний, каждое с пометкой BLOCKING или
NON-BLOCKING, с файлом, строкой и предложением, как исправить.
Если блокирующих нет — напиши APPROVED первой строкой.

Пара наблюдений после нескольких месяцев работы этой схемы.

Разделение окупается экономически. Кодеру уже не нужно быть самой умной моделью — план составлен, паттерны в кодовой базе чистые, за спиной линтеры. Реализацию тянет модель подешевле. А вот ревьювер и планировщик — та точка, где ум стоит своих денег: одна пойманная до PR проблема экономит целый цикл «человек посмотрел → написал замечание → агент переделал».

Чистый контекст ревьювера — не деталь, а суть. Пункты 3 и 4 его чек-листа (DRY и YAGNI) — это ровно те болезни LLM, о которых была первая половина статьи. Ловить их может только тот, кто не участвовал в написании: кодер всегда считает свою абстракцию нужной, а свою копипасту — уникальным случаем.

Человек смещается в конец конвейера. Итоговый пайплайн: план → генерация → pre-commit (cs-fixer, phpstan, deptrac ×2) → субагент-ревью → и только потом мои глаза. До меня доезжают споры и продуктовые решения, а не «тут забыли проверить null» и не «зачем здесь фабрика».

Выводы

Если сжать полгода набивания шишек в несколько строк:

  1. LLM — усилитель, а не замена инженерной культуры. Хорошую архитектуру он масштабирует, плохую превращает в болото быстрее, чем любая команда людей.

  2. Качество кода теперь измеряется в токенах. Чем меньше контекста нужно для изменения, тем лучше архитектура. SRP, низкий coupling, ограниченные контексты — это буквально оптимизация под контекстное окно.

  3. Правила, которые не проверяются автоматически, для LLM не существуют. Намерение — в правилах проекта, факт — в deptrac/phpstan/тестах. Сообщение об ошибке — лучший способ доставить знание в контекст агента точно в момент, когда оно нужно.

  4. DDD — на бэкенд, фронту — feature-sliced. Не тащите слои туда, где нет инвариантов.

  5. Ревьювер должен быть отдельным агентом с чистым контекстом. Self-review — это плацебо.

Чек-лист внедрения на существующем PHP-проекте:

  • [ ] Зафиксировать целевые слои и контексты (хотя бы на салфетке — это будущий ruleset).

  • [ ] Поставить deptrac, описать вертикальные слои; текущие нарушения — в baseline, новые — в запрет.

  • [ ] Вторым конфигом описать карту контекстов. Два файла, не один.

  • [ ] Собрать одну команду make validate (стиль + статика + deptrac + тесты) и включить её в pre-commit и CI.

  • [ ] Написать правила проекта: карта репо, инварианты, запрещённые приёмы, команда проверки.

  • [ ] Завести субагентов developer и reviewer и оформить пайплайн скилом.

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

Комментарии (41)


  1. ken48
    11.07.2026 04:33

    Недавно думал как раз об этом. Архитектурные практики, как способы борьбы со сложностью, были придуманы, чтобы дать возможность человеку понимать системы, выходящие по сложности за пределы его контекстного окна - кошелька Миллера - 3-5 (иногда 7) сущностей в кэше внимания. У ИИ контекстное сильно больше, но тоже ограниченное. Такая логика приводит к выводу: важность архитектуры есть функция отношения сложности проекта к размеру контекстного окна. Проще говоря, сложность проекта мы в любом случае должны компенсировать: там, где заканчивается контекстное окно, начинается архитектура. Иначе будет плохо.


    1. kotafey Автор
      11.07.2026 04:33

      Да, это ровно тот вывод, к которому я шёл всю статью, только сформулированный компактнее — «важность архитектуры есть функция отношения сложности проекта к размеру контекстного окна» я бы вынес в эпиграф. Добавлю два уточнения из практики. Первое: у LLM деградация начинается сильно раньше формальной границы окна — заявленные 200k токенов и окно, в котором модель реально удерживает все связи, это очень разные числа, так что «архитектура начинается» гораздо раньше, чем кажется по спецификации модели. Второе: даже гипотетическое бесконечное окно не отменяет архитектуру — пять чуть разных проверок баланса плохи не тем, что не влезают в контекст, а тем, что при правке нужно не забыть ни одну. Это уже не про объём внимания, а про количество мест, которые обязаны меняться согласованно.


    1. Dhwtj
      11.07.2026 04:33

      У ИИ контекстное сильно больше, но тоже ограниченное

      Против n² зависимостей между n компонентами никакое окно не поможет.

      3-5 (иногда 7) сущностей в кэше внимания

      Если это правильные абстракции, то их хватает.


  1. al-chemist
    11.07.2026 04:33

    Обезьянам раздали по гранате и теперь тут и там слышны взрывы, а форумы пухнут от историй «я не умею пользоваться инструментом, и поэтому он не работает».

    Проблему узкого контекстного окна решили больше года назад, решение называется RAG, но все продолжают пытаться её решить никчемными текстами под разными названиями (скилл, субагент, и прочий мусор), которые только раздувают этот самый контекст.


    1. johnnyBoy1984
      11.07.2026 04:33

      а вы вообще поняли о чем статья? rag работает с семантическим слоем и отвечает на вопрос какие в базе есть релевантные по смыслу запроса чанки. в коде почти нет семантики и чанки не годятся для генерации кода потому что это не весь контекст, который нужен агенту.


      1. al-chemist
        11.07.2026 04:33

        Статья, my ass. Монография.

        Этот, с позволения сказать, текстик — о том, что недостаток образования способен сотворить чудеса бессмысленной изобретательности.

        rag работает с семантическим слоем и отвечает на вопрос какие в базе есть релевантные по смыслу запроса чанки

        Да-да, конечно, жалко, что пацаны не в курсе, и давно уже используют RAG в связке с MCP для построения AST и отдачи релевантного контекста агенту, а не продолжают долбиться в набор буковок, который не влезает в контекстное окно.


        1. johnnyBoy1984
          11.07.2026 04:33

          ммм... срач в комментах, ну как тут устоять))) спасибо за прогрев

          там помимо вашего rag еще куча тейков в статье, автору респект за труды! мега полезный материал


        1. is_cake
          11.07.2026 04:33

          А можно подробности? Статьи, примеры. Я как раз недавно задумался о возможности применения семантического поиска непосредственно к коду. И хотелось бы увидеть существующие наработки.


          1. al-chemist
            11.07.2026 04:33

            Было бы можно, но напыщенные невежи отрезали мне возможность делиться опытом, как это всегда и бывает на хабре; напишите в личку — дам и ссылки и примеры.


        1. kotafey Автор
          11.07.2026 04:33

          RAG поверх AST — это ответ на вопрос «как быстрее найти код». А статья — про то, что находить должно быть нечего.

          Если проверка баланса живёт в пяти местах, идеальный retrieval честно притащит агенту все пять копий — и что дальше? Он всё так же должен удержать их в контексте и не забыть ни одну при правке. Вы просто построили скоростную магистраль к болоту. Архитектура — это когда проверка одна, лежит в предсказуемом месте, и никакой векторный индекс для этого не нужен: хватает неймспейса.

          И про «никчемные тексты» — вторая половина статьи вообще-то про обратное: тексты без enforcement не работают, поэтому deptrac и ненулевой код выхода. RAG, кстати, тут бессилен: он подскажет агенту, где лежит Doctrine-репозиторий, но не помешает заинжектить его в доменную сущность. Semantic search не заменяет запрет — он просто помогает нарушать быстрее.

          Так что нет, не альтернатива. Retrieval — про доступ к контексту, архитектура — про то, чтобы контекста нужно было меньше, enforcement — про то, чтобы правила нельзя было обойти. Первое без второго и третьего — автоматизация раскопок в легаси.


          1. al-chemist
            11.07.2026 04:33

            Если проверка баланса живёт в пяти местах […]

            Ессесно, эта проблема же появилась вчера, и люди не научились искать Code Duplication статанализом 25 примерно лет назад.

            Архитектура — это когда […]

            Архитектура — существительное, поэтому отвечать на вопрос «когда» не может.

            где лежит Doctrine-репозиторий, но не помешает заинжектить его в доменную сущность […]

            Да, я помню, что вы про статический анализ слыхом не слыхивали, ведь гораздо проще взять через раз работающие промпты.

            Так что нет, не альтернатива.

            Да ладно! А я что, где-то говорил, что альтернатива? Архитектура — штука важная, полезная и нужная, вот только она к LLM вообще никакого отношения в принципе не имеет.

            Retrieval — про доступ к контексту […]

            Это хорошо, что вы аббревиатуру уже на ⅓ расшифровали. Продолжайте в том же духе, и под конец (даже прямо на второй букве) вам откроется много нового и интересного.


            1. Dhwtj
              11.07.2026 04:33

              Code Duplication статанализом 25 примерно лет назад.

              Онанизм наш ваш лучший друг

              Укрепляет мышцы рук

              Стат анализ не отловит что ответственность дублирована и размазана

              З. Ы. Не я токсичить начал


              1. al-chemist
                11.07.2026 04:33

                Стат анализ не отловит что ответственность дублирована и размазана

                Да-да, конечно. Господи, когда ж вы все научитесь спрашивать: «как это сделать?» вместо огульного: «это сделать невозможно» на потеху публике, которая это делает уже сто лет в обед.


                1. Dhwtj
                  11.07.2026 04:33

                  Умение говорить ещё не признак ума©


            1. kotafey Автор
              11.07.2026 04:33

              Про «слыхом не слыхивали про статический анализ» — половина статьи посвящена deptrac, это статический анализатор, конфиги приложены. Copy-paste-детекторы ловят текстуальные дубли; четыре чуть разные реализации одной проверки они как раз не ловят — это ровно то, о чём уже написал Dhwtj. А с тезисом «архитектура — штука важная, но к LLM отношения не имеет» спорит сама статья, аргументы там. На этом закончу, по кругу ходить не буду.


            1. d3d14
              11.07.2026 04:33

              Архитектура — это когда […]

              Тот комментарий писала нейросеть. Не нужно отвечать на него.


      1. Imidinti
        11.07.2026 04:33

        Скорее всего имеется ввиду не классический RAG только с векторной базой. А более комплексное понятие, где уже несколько слоев поиска по разным индексам. Это AST, LSP сервер, графы вызовов, графы зависимостей и т.д. и рядом уже векторная база.

        В этой статье описывается многоуровневый поиск https://yage.ai/share/why-coding-agents-still-use-grep-en-20260327.html


        1. johnnyBoy1984
          11.07.2026 04:33

          вот это содержательный коммент, спасибо за статью


        1. kotafey Автор
          11.07.2026 04:33

          Спасибо, отличная ссылка — она, по-моему, ставит точку в споре выше. Индустриальный консенсус ровно обратный тезису «профессионалы используют RAG»: все шесть разобранных агентов (включая Cursor и Claude Code) базируются на grep, а эмбеддинги в этой иерархии — самый верхний и самый дорогой слой для редких чисто семантических запросов. И заметьте: чем чище архитектура, тем ниже по этим слоям можно оставаться — в кодовой базе с предсказуемыми неймспейсами grep по Core\Billing находит всё за один запрос, и никакой векторный индекс не нужен. Дешёвый поиск — это тоже дивиденд архитектуры.


          1. al-chemist
            11.07.2026 04:33

            Индустриальный консенсус […]

            Рекламный текст ≠ индустриальный консенсус. Конечно, вам будут втюхивать греп, который токены жрет, как не в себя, — вместо инструментов, которые сокращают расходы пользователей в разы, а иногда — в десятки раз.

            в кодовой базе с предсказуемыми неймспейсами

            А куда делись размазанные по неправильным неймспейсам дубли из прошлого комментария?

            ставит точку в споре

            Никакого спора выше не было, мне недосуг спорить, особенно с твердолобыми. Я просто указал, как эту проблему решают профессионалы, если вам не надо — мне тем более.


          1. maxim_ge
            11.07.2026 04:33

            Точнее было бы сказать: индустриальный консенсус не “против RAG”, а “против embedding-based retrieval как retrieval-механизма по умолчанию”. Большинство инструментов используют текстовый (grep/regex) retrieval вместо векторного, оставляя эмбеддинги для узкого класса семантических запросов. Grep – это тоже форма retrieval, так что RAG как класс тут никуда не исключается. Меняется лишь то, чем наполнено R.


            1. Eg0r00f
              11.07.2026 04:33

              R - retard


  1. Yevgeny_VS
    11.07.2026 04:33

    Плюсую. Но на одного, кто эти вещи понимает и использует приходится 10, которые этим не заморачивается, и 100, кто даже не знает и гордится этим.


    1. vagon333
      11.07.2026 04:33

      Но на одного, кто эти вещи понимает и использует приходится 10, которые этим не заморачивается, и 100, кто даже не знает и гордится этим.


      Разгребать после сотни и будет наш хлеб.


  1. Dhwtj
    11.07.2026 04:33

    Раз в 50 промптов или 20 коммитов остановка, выдох*, мини архитектурное ревью на свежую голову.

    *Смотрим на часы и принудительно идём спать

    По необходимости расширенное ревью и рефакторинг


  1. digrobot
    11.07.2026 04:33

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

    Ценная мысль. Я всегда подсознательно стремился к простоте в коде, и вот теперь этому есть обоснование.


  1. dkfbm
    11.07.2026 04:33

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

    Это правда, если ревьювить каждую строчку, то наверное, было бы быстрее самому написать. Но выход есть: надо разбивать работу на этапы. Я все изменения (за исключением самых мелких) пускаю через superpowers: сначала сам пишу описание фичи или изменения в отдельном .md, потом он пишет полную спецификацию, я просматриваю и корректирую по необходимости, и далее он на основе этой спецификации пишет план реализации. Код в нём тоже есть, но на него я особо не смотрю даже, а обращаю внимание в первую очередь именно на соответствие предложенного плана архитектурным решениям. Ну и на заключительном этапе, конечно, обязательное ревью другим агентом. Пока результат (на проекте в ~30K строк кода) меня устраивает.

    Что любопытно, при таком подходе количество строк документации в .md (49K) превышает количество строк собственно кода.


    1. kotafey Автор
      11.07.2026 04:33

      То, что вы описываете, — это по сути SDD, Spec-Driven Development: спецификация становится первичным артефактом, а код — её производной. Spec Kit у GitHub, Kiro у Amazon — та же идея. И ваши 49K строк .md против 30K кода — характерный признак подхода: центр тяжести проекта переехал из кода в спеки.

      Это рассуждение в другую сторону от моей статьи, и мне кажется, они не конкурируют, а дополняют друг друга. Общий диагноз один: ревьюить глазами каждую строчку невозможно, контроль надо куда-то смещать. SDD смещает его вверх — в намерение: вы ревьюите спеку и план, что в разы дешевле чтения дифа. Статья — про смещение вниз, в факт: что бы агент ни задумал, deptrac и ревьювер с чистым контекстом проверят результат.

      Одно другое не отменяет, потому что план агент нарушает так же непринуждённо, как и CLAUDE.md: на этапе «пишу код по спеке» его тянет в ту же копипасту и те же дырки между слоями. Согласованный план — не гарантия согласованного кода. Поэтому в идеале это два конца одного конвейера: спека/план (намерение) → генерация → машинная проверка факта. Судя по тому, что у вас в конце стоит обязательное ревью другим агентом — вы к этой же схеме и пришли, просто с другого конца.

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


      1. dkfbm
        11.07.2026 04:33

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

        Именно так. ИИ генерирует слишком много кода в единицу времени, чтобы человек мог его хотя бы оценивать в том же темпе. Да и честно говоря, претензию именно к качеству кода свежим моделям предъявить трудно – чисто технически они пишут хорошо. При условии, что задача, ограничения и критерии успеха сформулированы точно и недвусмысленно. Со знаменитыми галлюцинациями практически не сталкиваюсь – ну может, пару раз что-то похожее было. Вероятно, в силу именно того, что каждый шаг проекта документирован, причины принятых решений понятны и особого простора для фантазий не оставляют.


  1. Bardakan
    11.07.2026 04:33

    KISS + YAGNI. А вот тут обратная беда. LLM обожает «на всякий случай»: фабрику для класса с одной реализацией, конфигурируемость, которую никто не просил, слой абстракции над слоем абстракции. Выглядит солидно, ревью проходит легко («ну, аккуратно же написано»), а через месяц эта преждевременная гибкость становится тем самым лишним контекстом, который мешает следующим правкам.

    не понял. Какая еще фабрика в KISS, если принцип предполагает простоту кода - в том числе меньше кода там, где он не требуется?

    а поиск по кодовой базе — дорогая операция, съедающая контекст

    тоже ерунда - когда вместо spec-driven-development описываю только общие правила, а cursor без отдельного по стилю ориентируется на уже существующие классы как шаблоны, то получается неплохая экономия токенов (а значит и контекст засоряется меньше). Вплоть до того, что на работе удивлялись, как это мне хватает 20$ подписки. Но это конечно, если вы используете агентов именно как помощников, а не пытаетесь генерить вообще весь проект автоматически.