Сборка C-компилятора командой параллельных агентов Claude
Исследователь Anthropic запустил 16 параллельных агентов Claude для написания C-компилятора на Rust с нуля. За ~2000 сессий и $20 000 агенты создали 100 000 строк кода, способных собрать ядро Linux 6.9.
Сборка C-компилятора командой параллельных агентов Claude
Автор: Nicholas Carlini, исследователь команды Safeguards.
Я экспериментирую с новым подходом к управлению языковыми моделями, который мы называем «командами агентов» (agent teams).
В такой команде несколько экземпляров Claude работают параллельно над общей кодовой базой без активного участия человека. Этот подход существенно расширяет возможности LLM-агентов.
Для стресс-теста я поставил 16 агентам задачу написать C-компилятор на Rust с нуля — такой, чтобы он мог собирать ядро Linux. За почти 2 000 сессий Claude Code и $20 000 затрат на API команда агентов создала компилятор из 100 000 строк, способный собирать Linux 6.9 под x86, ARM и RISC-V.
Компилятор сам по себе — интересный артефакт, но здесь я сосредоточусь на том, что узнал о проектировании окружения для долгоживущих автономных команд агентов: как писать тесты, удерживающие агентов на верном пути без надзора человека, как структурировать работу так, чтобы несколько агентов могли двигаться вперёд параллельно, и где этот подход упирается в потолок.
Как запустить Claude на длительное время
Существующие агентные инструменты вроде Claude Code предполагают, что оператор онлайн и работает совместно с моделью. Если задать длинную и сложную задачу, модель решит её часть, но в какой-то момент остановится и будет ждать дальнейшего ввода — вопроса, обновления статуса или запроса на уточнение.
Чтобы добиться устойчивого автономного прогресса, я написал обёртку, которая запускает Claude в простом цикле (если вы видели Ralph-loop, это покажется знакомым). Завершив одну задачу, агент сразу берётся за следующую. (Запускайте это в контейнере, а не на своей машине.)
#!/bin/bash
while true; do
COMMIT=$(git rev-parse --short=6 HEAD)
LOGFILE="agent_logs/agent_${COMMIT}.log"
claude --dangerously-skip-permissions \
-p "$(cat AGENT_PROMPT.md)" \
--model claude-opus-X-Y &> "$LOGFILE"
done
В промпте агента я объясняю Claude, какую задачу нужно решить, и прошу разбивать её на небольшие части, отслеживать текущий прогресс, определять следующий шаг и фактически продолжать работу до достижения идеального результата. (В последнем пункте у Claude нет выбора — цикл работает бесконечно. Хотя в одном случае я видел, как Claude случайно выполнил pkill -9 bash, убив тем самым себя и завершив цикл. Бывает!)
Параллельный запуск Claude
Запуск нескольких экземпляров параллельно устраняет два слабых места одиночного агента:
-
Одна сессия Claude Code может делать только одно дело за раз. По мере роста проекта параллельная отладка нескольких проблем становится значительно эффективнее.
-
Несколько агентов позволяют специализироваться. Пока одни решают основную задачу, другие могут, например, поддерживать документацию, следить за качеством кода или заниматься специализированными подзадачами.
Моя реализация параллельного Claude минималистична. Создаётся чистый git-репозиторий, для каждого агента поднимается Docker-контейнер с репозиторием, смонтированным в /upstream. Каждый агент клонирует локальную копию в /workspace, а по завершении пушит изменения из своего контейнера в upstream.
Чтобы два агента не пытались решить одну задачу одновременно, обёртка использует простой алгоритм синхронизации:
-
Claude «захватывает» задачу, записывая текстовый файл в
current_tasks/(например, один агент может заблокироватьcurrent_tasks/parse_if_statement.txt, другой —current_tasks/codegen_function_definition.txt). Если два агента пытаются захватить одну задачу, механизм синхронизации git вынуждает второго выбрать другую. -
Claude работает над задачей, затем делает pull из upstream, мёрджит изменения других агентов, пушит свои и снимает блокировку. Конфликты слияния возникают часто, но Claude достаточно умён, чтобы с ними разобраться.
-
Бесконечный цикл порождает новую сессию Claude Code в свежем контейнере, и цикл повторяется.
Это очень ранний исследовательский прототип. Я пока не реализовал никаких других механизмов коммуникации между агентами и не ввёл никакого процесса управления высокоуровневыми целями. Оркестрирующего агента нет.
Вместо этого я предоставляю каждому агенту самостоятельно решать, как действовать. В большинстве случаев Claude берётся за «следующую очевидную» проблему. Застряв на баге, Claude нередко ведёт документ с перечнем неудачных подходов и оставшихся задач. В git-истории проекта можно проследить, как агенты захватывают блокировки на различные задачи.
Уроки работы с командами агентов Claude
Обёртка запускает Claude в цикле, но этот цикл полезен лишь тогда, когда Claude понимает, как двигаться вперёд. Большую часть усилий я потратил на проектирование окружения вокруг Claude — тестов, среды, обратной связи — чтобы агент мог ориентироваться без моего участия. Вот подходы, которые я нашёл наиболее полезными при оркестрации нескольких экземпляров Claude.
Пишите тесты исключительно высокого качества
Claude будет автономно решать ту задачу, которую я ему дам. Поэтому важно, чтобы верификатор задачи был практически идеальным — иначе Claude решит не ту проблему. Улучшение тестовой обёртки потребовало поиска качественных наборов тестов для компиляторов, написания верификаторов и скриптов сборки для пакетов с открытым исходным кодом, а также наблюдения за ошибками Claude с последующим проектированием новых тестов по мере выявления этих сбоев.
Например, ближе к концу проекта Claude начал регулярно ломать существующую функциональность при реализации новых возможностей. Чтобы решить эту проблему, я построил конвейер непрерывной интеграции и ввёл более строгие проверки, позволяющие Claude лучше тестировать свою работу: новые коммиты не должны ломать существующий код.
Смотрите на задачу глазами Claude
Мне постоянно приходилось напоминать себе, что я пишу эту тестовую обёртку для Claude, а не для себя, — а значит, нужно пересмотреть многие привычные представления о том, как тесты должны сообщать о результатах.
Например, каждый агент запускается в свежем контейнере без какого-либо контекста и тратит значительное время на ориентацию, особенно в крупных проектах. Ещё до запуска тестов, чтобы помочь Claude помочь себе самому, я включил инструкции по ведению подробных README и файлов прогресса, которые нужно часто обновлять с текущим статусом.
Я также учитывал присущие языковым моделям ограничения, которые в данном случае нужно было обходить:
-
Засорение контекстного окна: тестовая обёртка не должна выводить тысячи бесполезных байт. Максимум — несколько строк вывода, а вся важная информация должна логироваться в файл, чтобы Claude мог найти её при необходимости. Логи должны легко обрабатываться автоматически: при наличии ошибок Claude должен писать
ERRORи помещать причину на той же строке, чтобыgrepеё нашёл. Полезно заранее вычислять агрегированную статистику, чтобы Claude не пересчитывал её каждый раз. -
Слепота ко времени: Claude не чувствует времени и, предоставленный сам себе, с удовольствием потратит часы на прогон тестов вместо реального прогресса. Обёртка редко выводит промежуточный прогресс (чтобы не засорять контекст) и включает опцию
--fastпо умолчанию, запускающую случайную выборку в 1% или 10% тестов. Эта выборка детерминирована для каждого агента, но случайна между виртуальными машинами — так Claude охватывает все файлы, а каждый агент может точно выявлять регрессии.
Упрощайте параллелизм
Когда провальных тестов много и они независимы, распараллеливание тривиально: каждый агент берёт разный упавший тест. После того как тестовый набор достиг 99% прохождения, каждый агент занялся компиляцией отдельного небольшого проекта с открытым исходным кодом (например, SQlite, Redis, libjpeg, MQuickJS, Lua).
Но когда агенты приступили к сборке ядра Linux, они застряли. В отличие от набора из сотен независимых тестов, сборка ядра Linux — одна гигантская задача. Каждый агент натыкался на один и тот же баг, исправлял его и перезаписывал изменения других. Наличие 16 агентов не помогало, потому что все они решали одну и ту же задачу.
Решением стало использование GCC как онлайн-оракула с заведомо правильным результатом. Я написал новую тестовую обёртку, которая случайным образом компилировала большую часть ядра с помощью GCC и лишь оставшиеся файлы — компилятором Claude. Если ядро работало, значит, проблема была не в файлах, обрабатываемых компилятором Claude. Если ломалось — можно было сузить круг, перекомпилировав часть файлов через GCC. Это позволило каждому агенту работать параллельно, исправляя разные баги в разных файлах, пока компилятор Claude не смог собрать все файлы. (После этого всё равно пришлось применять техники дельта-отладки для поиска пар файлов, которые падали вместе, но работали по отдельности.)
Несколько ролей агентов
Параллелизм также открывает возможность специализации. Код, написанный LLM, нередко дублирует существующую функциональность, поэтому я поручил одному агенту объединять найденный дублирующийся код. Другой отвечал за повышение производительности самого компилятора, третий — за генерацию эффективного скомпилированного кода. Ещё один агент анализировал дизайн проекта с точки зрения Rust-разработчика и вносил структурные изменения для улучшения общего качества кода, а другой занимался документацией.
Стресс-тест пределов команд агентов
Этот проект задумывался как бенчмарк возможностей. Меня интересует стресс-тестирование пределов того, что LLM едва способны достичь сегодня, — чтобы подготовиться к тому, что модели будут надёжно достигать в будущем.
Я использую проект C-компилятора как бенчмарк для всей серии моделей Claude 4. Как и в предыдущих проектах, я начал с описания желаемого результата: оптимизирующий компилятор с нуля, без зависимостей, совместимый с GCC, способный компилировать ядро Linux и поддерживающий несколько бэкендов. Я указал некоторые аспекты дизайна (например, что компилятор должен использовать SSA IR для нескольких проходов оптимизации), но не вдавался в детали реализации.
Предыдущие версии Opus 4 едва справлялись с созданием работающего компилятора. Opus 4.5 стал первым, кто пересёк порог, позволяющий создать функциональный компилятор, проходящий большие тестовые наборы, — но он всё ещё не мог компилировать реальные крупные проекты. Цель с Opus 4.6 была та же — снова проверить пределы возможного.
Оценка результатов
За почти 2 000 сессий Claude Code на протяжении двух недель Opus 4.6 потребил 2 миллиарда входных токенов и сгенерировал 140 миллионов выходных токенов — общая стоимость чуть менее $20 000. По сравнению даже с самыми дорогими планами Claude Max это был крайне затратный проект. Но эта сумма — ничтожная доля от того, во что обошлось бы создание компилятора мне лично, не говоря уже о целой команде разработчиков.
Это была реализация в чистой комнате (Claude не имел доступа к интернету на протяжении всей разработки); компилятор зависит только от стандартной библиотеки Rust. Компилятор из 100 000 строк может собирать загрузочный Linux 6.9 под x86, ARM и RISC-V. Он также компилирует QEMU, FFmpeg, SQlite, postgres, redis и проходит 99% тестов большинства тестовых наборов для компиляторов, включая GCC torture test suite. Кроме того, он проходит главный тест разработчика: компилирует и запускает Doom.
Тем не менее компилятор не лишён ограничений:
-
В нём отсутствует 16-битный x86-компилятор, необходимый для загрузки Linux из реального режима. Для этого он обращается к GCC (компиляторы x86_32 и x86_64 — собственные).
-
У него нет собственного ассемблера и компоновщика; это последние компоненты, которые Claude начал автоматизировать, и они всё ещё содержат ошибки. Демонстрационное видео было записано с использованием ассемблера и компоновщика GCC.
-
Компилятор успешно собирает многие проекты, но не все. Он пока не является полноценной заменой настоящего компилятора.
-
Генерируемый код недостаточно эффективен. Даже при включённых оптимизациях он выдаёт менее эффективный код, чем GCC с отключёнными оптимизациями.
-
Качество кода на Rust приемлемое, но далеко от того, что мог бы написать опытный Rust-разработчик.
Полученный компилятор практически достиг пределов возможностей Opus. Я упорно пытался устранить несколько из перечисленных ограничений, но не добился полного успеха. Новые возможности и исправления багов регулярно ломали существующую функциональность.
Один особенно показательный пример: Opus не смог реализовать генератор 16-битного x86-кода, необходимый для загрузки в 16-битный реальный режим. Хотя компилятор может выводить корректный 16-битный x86-код через префиксы опкодов 66/67, результирующий скомпилированный вывод превышает 60 КБ — значительно больше лимита в 32 КБ, установленного Linux. Вместо этого Claude просто жульничает и вызывает GCC для этой фазы. (Это касается только x86. Для ARM и RISC-V компилятор Claude справляется полностью самостоятельно.)
Исходный код компилятора доступен публично. Скачайте его, изучите код и попробуйте на своих любимых C-проектах. Я неизменно убеждаюсь, что лучший способ понять возможности языковых моделей — довести их до предела, а затем изучить, где они начинают давать сбои. В ближайшие дни я продолжу наблюдать за тем, как Claude вносит новые изменения в попытках устранить эти ограничения.
Взгляд в будущее
Каждое поколение языковых моделей открывает новые способы работы с ними. Ранние модели были полезны для автодополнения в IDE. Вскоре модели научились дописывать тело функции по её докстрингу. Запуск Claude Code вывел агентов в мейнстрим и позволил разработчикам программировать в паре с Claude. Но все эти продукты строятся на предположении, что пользователь формулирует задачу, LLM работает несколько секунд или минут и возвращает ответ, а затем пользователь задаёт уточняющий вопрос.
Команды агентов демонстрируют возможность автономной реализации целых сложных проектов. Это позволяет нам, как пользователям этих инструментов, ставить перед собой более амбициозные цели.
Мы ещё в начале пути, и полностью автономная разработка несёт реальные риски. Когда человек работает рядом с Claude в процессе разработки, он может обеспечивать стабильное качество и выявлять ошибки в реальном времени. В автономных системах легко увидеть, что тесты проходят, и решить, что работа сделана, — хотя это редко соответствует действительности. Я работал в сфере пентестинга, эксплуатируя уязвимости в продуктах крупных компаний, и мысль о том, что программисты будут деплоить программное обеспечение, которое они лично никогда не проверяли, вызывает реальное беспокойство.
Поэтому, хотя этот эксперимент меня воодушевляет, он также оставляет ощущение тревоги. Создание этого компилятора было одним из самых увлекательных занятий за последнее время, но я не ожидал, что это окажется возможным так рано — в начале 2026 года. Стремительный прогресс как языковых моделей, так и инструментов для работы с ними открывает возможность написания огромного количества нового кода. Я ожидаю, что позитивные применения перевесят негативные, но мы входим в новый мир, для безопасной навигации в котором потребуются новые стратегии.
Благодарности
Отдельная благодарность Josef Bacik, Edwin Chen, Bernardo Meurer Costa, Jake Eaton, Dan Kelley, Felix Klock, Jannet Park, Steve Weis и многим другим сотрудникам Anthropic за помощь и вклад в проект.