Когда в начале года индустрия заговорила об автономных фабриках программного обеспечения (AI Software Factory), идея казалась сказочной: скармливаешь нейросети подробный PRD, ложишься спать, а утром принимаешь готовый микросервис с тестами и чистой историей коммитов. Мы подробно исследовали этот концепт, когда разбирали архитектуру «темной фабрики» кода. Но реальность продакшена быстро сбила спесь даже с отчаянных энтузиастов.
Коул Медин (Cole Medin), создатель открытого оркестратора Archon, убедился на практике: полностью «темная» фабрика (Dark Factory) без участия человека неизбежно деградирует. Когда агенты штампуют десятки pull request в сутки вслепую, кодовая база страдает от архитектурного дрейфа. Модели уверенно подменяют контекст, ходят по кругу в попытках исправить нерелевантные тесты и застревают на неоднозначностях спецификации, сжигая сотни долларов на бесполезный инференс.
Ответом на этот кризис стал запуск открытого проекта factory-mission-control — командного пульта диспетчера, построенного поверх обновленного конвейера ai-software-factory. Я изучил эту связку и готов показать, как гибридный подход Human-in-the-Loop позволяет небольшой команде безопасно выпускать сотни проверенных PR в неделю.
Почему «темная фабрика» без человека заходит в тупик
Автономный CLI-агент вроде Claude Code или Aider чертовски хорош на коротких дистанциях. Ты ставишь локальную задачу, он правит функцию, прогоняет линтер и выдает аккуратный diff. Но стоит связать агентов в сквозной конвейер, как вылезает главная проблема: отсутствие детерминированного слоя управления.
Если агент сталкивается с противоречием в задании (например, выбрать между сессионными куками и JWT-токенами), у полностью автономной системы есть два одинаково тупиковых пути. Первый — сделать случайный выбор и переписать под него половину базы данных. Второй — бесконечно зациклиться на генерации тестов, пытаясь совместить несовместимое. В итоге утром разработчик обнаруживает не рабочий сервис, а сотни конфликтующих веток и гигантский счет за токены.
Поэтому концепция живого контроля субагентов стала фундаментом надежности. Фабрика берет на себя 90% рутины: создание веток, бойлерплейт, типовую логику и независимый аудит. Но в ключевых развилках конвейер принудительно останавливается и эскалирует вопрос человеку.
Архитектура Mission Control: от вебхуков к реактивной базе
Чтобы не утонуть в сотнях параллельных задач, фабрике нужен прозрачный слой состояния (Control Plane). Медин отказался от хранения статусов в файлах репозитория или комментариях GitHub. Фундаментом диспетчерской стала связка Neon Postgres и механизма живых подписок Neon Realtime.
Сквозная цепочка событий:
- Шина событий: любое действие в GitHub (создание issue, коммит, открытие PR) генерирует Webhook.
- Бессерверный инжектор: легковесные функции Neon Functions перехватывают вебхук и записывают события в таблицы (
laps,decisions,audit_logs). - Реактивный интерфейс: дашборд Mission Control подписывается на живые SQL-запросы через WebSocket. Любое изменение в базе мгновенно обновляет карточку задачи на экране без перезагрузки страницы.
- Ветвление данных (Neon Branching): мгновенное клонирование состояния базы позволяет проводить чистое A/B-тестирование разных версий самой фабрики и промптов.
Сам дашборд Mission Control разрабатывается мета-способом: фабрика сама пишет и развивает свой интерфейс под надзором автора.
Как устроен рабочий цикл: Git worktrees и узел Human Gate
Сердцем фабрики остается оркестратор Archon, разворачивающий задачу в направленный граф (DAG) на YAML. Чтобы параллельные запуски (laps) не конфликтовали, Archon изолирует каждый запуск через git worktree в отдельную директорию.
Вот фрагмент сценария Archon с точкой эскалации human_decision_gate:
version: "1.0"
name: "feature-pipeline"
nodes:
- id: create_worktree
type: shell
action: |
WORKTREE="/tmp/worktrees/lap-${ARCHON_LAP_ID}"
git worktree add -b "feature/${ARCHON_ISSUE_KEY}" "${WORKTREE}" origin/main
- id: analyze_spec
type: agent
depends_on: [create_worktree]
model: "claude-3-7-sonnet"
harness:
cwd: "${create_worktree.WORKTREE}"
prompt: |
Проанализируй тикет ${ARCHON_ISSUE_KEY}. При архитектурных развилках
вызови инструмент escalate_to_owner.
- id: human_decision_gate
type: gate
depends_on: [analyze_spec]
condition: "${analyze_spec.needs_owner_decision == true}"
on_wait:
notify_webhook: "https://my-neon-func.neon.tech/api/escalate"
payload:
lap_id: "${ARCHON_LAP_ID}"
issue: "${ARCHON_ISSUE_KEY}"
- id: code_generation
type: agent
depends_on: [human_decision_gate]
model: "claude-3-7-sonnet"
harness:
cwd: "${create_worktree.WORKTREE}"
tools: ["fs_read", "fs_write", "bash", "test_runner"]
prompt: "Реализуй требования и добейся прохождения тестов."
- id: independent_review
type: agent
depends_on: [code_generation]
model: "claude-3-7-sonnet"
prompt: "Проведи независимый аудит безопасности git diff."
Узел independent_review принципиален: проверяющий агент изолирован от кодера, видит только итоговый git diff и не подвержен чужим галлюцинациям.
Бессерверная реактивность и живой пульт управления
Преобразование событий GitHub в реактивный интерфейс диспетчера решено через серверлесс-функции и клиентские хуки.
Обработчик вебхуков на стороне Neon Serverless Functions:
import { neon } from '@neondatabase/serverless';
export default async function handler(req: Request) {
const sql = neon(process.env.DATABASE_URL!);
const { action, pull_request, repository } = await req.json();
if (req.headers.get('x-github-event') === 'pull_request') {
await sql`
INSERT INTO factory_events (event_type, repo_name, pr_number, status, title)
VALUES (${action}, ${repository.full_name}, ${pull_request.number}, ${pull_request.state}, ${pull_request.title})
ON CONFLICT (pr_number, repo_name) DO UPDATE SET status = ${pull_request.state};
`;
return new Response(JSON.stringify({ ok: true }));
}
return new Response(JSON.stringify({ ignored: true }));
}
Клиентский компонент Mission Control с подпиской Neon Realtime:
import { useRealtimeQuery } from '@neondatabase/react-realtime';
export function ActiveLapsWidget({ factoryId }: { factoryId: string }) {
const { data: laps, loading } = useRealtimeQuery(
`SELECT id, issue_key, status, current_step, owner_blocked
FROM factory_laps WHERE factory_id = $1 AND status != 'completed'`,
[factoryId]
);
if (loading) return <div>Синхронизация с пультом фабрики...</div>;
return (
<div className="laps-grid">
{laps.map(lap => (
<div key={lap.id} className={`lap-card ${lap.owner_blocked ? 'blocked' : 'active'}`}>
<h4>#{lap.id}: {lap.issue_key} ({lap.current_step})</h4>
{lap.owner_blocked && <span className="warning">Требуется решение</span>}
</div>
))}
</div>
);
}
Все задачи с флагом owner_blocked попадают во вкладку Owner Tab. Инженер раз в несколько часов изучает развилки, делает пару кликов, и агенты продолжают сборку.
Экономика масштабирования и границы применимости
Практические метрики Коула Медина впечатляют: система отгружает сотни PR в неделю. Анализ отказов (Failure Analysis) в дашборде показал:
- Из сотен прогонов лишь 2 сбоя произошли из-за лимитов провайдеров LLM.
- 21 сбой потребовал подстройки шагов и условий в самих графах Archon.
- Ни один дефектный PR не попал в основную ветку благодаря автоматическому ревьюеру и тестам.
Постоянный спаун изолированных веток через git worktree быстро забивает диск. Настройте в финальном узле каждого сценария автоматическую очистку рабочей директории через git worktree remove --force после слияния ветки.
Но иллюзий питать не стоит: если инженер начнет бездумно одобрять запросы в Owner Tab, фабрика мгновенно утопит проект в техдолге. Инженер перестает писать рутинный синтаксис и становится диспетчером сборочного завода.
