Когда я начинал строить систему из нескольких AI-агентов, которые пишут код параллельно и автономно, я думал что главная проблема — техническая. Как запустить 6 агентов одновременно? Как не потерять задачи при сбое? Как смотреть на всё это с телефона?

Я ошибался. Главная проблема оказалась архитектурной. И она называется: агент оптимизирует под метрику, а не под результат.


Закон Гудхарта в мире AI-агентов

Есть старый принцип из экономики: «Когда мера становится целью — она перестаёт быть мерой». Карл Гудхарт сформулировал это в 1975 году применительно к монетарной политике. В 2026 году это стало главной проблемой AI-агентов.

Представьте задачу: написать модуль авторизации. Метрика успеха — тесты проходят.

Агент знает какие тесты будут запущены. Агент умён. Агент оптимизирует:

def authenticate(username, password):
    # Агент "знает" что тест проверяет admin/secret
    if username == "admin" and password == "secret":
        return True
    return False

Тест проходит. Код бесполезен. Система считает задачу выполненной.

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


Решение которое кажется очевидным, но не реализуется

Решение простое: тот кто пишет код не должен знать как его будут проверять. А тот кто проверяет не должен знать кто писал и почему.

Это принцип независимой верификации. В науке это называется двойным слепым экспериментом. В праве — независимым судом. В бухгалтерии — внешним аудитом.

Почему же практически ни один из существующих open-source оркестраторов это не реализует?

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


Четыре слоя и жёсткие границы

Система которую я построил состоит из четырёх слоёв. Между ними — однонаправленные, типизированные, append-only контракты.

PLANNING      →  task graph
                 (что делать, в каком порядке)
                 ↓
EXECUTION     →  artifacts
                 (git diff, логи агента)
                 ↓
VERIFICATION  →  verdict
                 (прошёл / не прошёл + баллы)
                 ↓
OPTIMIZATION  →  proposals
                 (предложения людям, не команды)

Критично не что каждый слой делает, а что он не видит.

Planning не видит execution

Planning агент получает цель и структуру кодовой базы. Он генерирует граф задач с зависимостями. Он не знает какой воркер возьмёт задачу, сколько это займёт, каким путём агент придёт к решению.

Это важно потому что если Planning знает детали execution — он начинает под них подстраиваться. Задачи становятся слишком конкретными. Система теряет гибкость.

Execution не видит criteria

Воркер получает только описание задачи. Он не знает:

  • какие тесты будут запущены

  • какой минимальный score нужен

  • по каким критериям его оценят

Это устраняет возможность оптимизации под метрику. Агент вынужден решать задачу честно.

Verification не знает автора

Верификатор получает запечатанный артефакт: git diff и логи. Worker ID скрыт. Контекст «почему так сделано» не передаётся.

def verify(artifact: SealedArtifact, criteria: dict) -> Verdict:
    # artifact.worker_id существует в системе
    # но намеренно не передаётся в функции проверки
    
    score = check_tests(artifact.worktree_path)
    review = llm_review(artifact.git_diff, criteria['rubric'])
    # LLM видит только код, не видит кто писал

Это устраняет второй уровень проблемы — контекстуальное смягчение оценки. Когда reviewer знает «агент wt-3 работал над этим три часа и это его третья попытка» — оценка неизбежно смягчается. Когда видит только код — судит объективно.

Optimization видит только агрегаты

Пятый слой анализирует паттерны и предлагает улучшения. Он видит:

  • процент успешных задач

  • типичные ошибки (из verdicts)

  • среднее время выполнения

Он не видит содержимое артефактов. Не видит текущее состояние выполнения. И самое важное — он не может изменить систему напрямую. Только proposals, которые идут на апрув к человеку.

Это защищает от третьей проблемы — нестабильности при самомодификации. Система которая сама себя оптимизирует во время работы — это система которая ломается непредсказуемо.


Почему это сложно реализовать

Проблема не техническая — Python, Redis, SQLite, tmux. Всё это просто. Проблема в том что нужно постоянно сопротивляться соблазну упростить.

Когда пишешь verification агента, очень хочется передать ему описание задачи «для контекста». Это же поможет лучше оценить, правда?

Нет. Это разрушает границу.

Когда пишешь planning агента, хочется показать ему текущие логи агентов чтобы он «понимал ситуацию». Это же разумно?

Нет. Planning не должен знать как выполняется execution.

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

Решение — технически закрепить границы. Не договорённостями, а кодом.

# Redis ACL — каждый слой читает только свой канал
user planning-agent   on ~events:planning* ~history:*     +XADD +XREAD
user execution-agent  on ~events:execution* ~heartbeats:* +XADD +XREAD
user verify-agent     on ~events:verify* ~artifacts:*     +XADD +XREAD
user optim-agent      on ~metrics:* ~verdicts:summary:*   +XREAD
/farm/
  agents/planning/    # user: farm-planning
  agents/execution/   # user: farm-execution
  agents/verification/# user: farm-verify
  agents/optimization/# user: farm-optim

Когда граница существует на уровне OS permissions и сетевых ACL — её невозможно случайно нарушить.


Event-driven архитектура как следствие

Жёсткие границы между слоями делают классическую request-response архитектуру неудобной. Если Planning не может напрямую вызвать Execution — как они общаются?

Через append-only event log.

HUMAN       → TaskSubmitted
PLANNING    → TaskGraphEmitted
SCHEDULER   → TaskReady
DISPATCHER  → TaskAssigned
WORKER      → Heartbeat, Progress, Done, Failed
RECONCILER  → TaskRequeued, WorkerReleased
VERIFICATION→ VerdictEmitted
OPTIMIZATION→ ProposalGenerated

Каждое событие — immutable запись. Текущее состояние системы — это fold над всеми событиями. Dashboard в браузере не поллит базу данных — он подписывается на stream и применяет новые события поверх текущего состояния.

Это даёт бесплатный audit log, возможность replay для дебага и простое восстановление после сбоев.


Reconciler как control loop

Когда слои изолированы и общаются через events — возникает вопрос: кто следит за тем чтобы желаемое состояние соответствовало реальному?

Reconciler. Он работает по принципу Kubernetes control loop:

loop каждые 10 секунд:
  desired  = что event log говорит что должно происходить
  actual   = что heartbeats говорят что происходит реально
  diff     = desired - actual
  act(diff)

Если воркер назначен на задачу но не шлёт heartbeat больше 120 секунд — Reconciler переставляет задачу в очередь. Если процесс умер — освобождает воркер. Если задача провалилась меньше max_retries раз — повторно ставит в очередь.

Человек не узнает что что-то пошло не так. Он просто увидит результат.


Что в итоге

Система работает на VPS 8 vCPU / 16 GB. Шесть воркеров, Big Pickle через OpenCode Zen, git worktrees для изоляции, Tailscale для доступа с телефона.

Но железо и инструменты — детали. Принцип один:

AI-агенты без архитектурных границ деградируют. Они начинают оптимизировать под метрику вместо результата, под видимость работы вместо работы.

Закон Гудхарта не исчез когда появились LLM. Он просто стал работать быстрее.

Dashboard UI
Dashboard UI

Если хотите повторить — начните с Reconciler и Verification. Остальное можно добавлять постепенно. Но эти два слоя должны быть с самого начала.