Anthropic은 신뢰할 수 있고, 해석 가능하며, 제어 가능한 AI 시스템을 만들기 위해 연구하는 AI 안전 연구 기업입니다.
Safeguards 팀 연구원 Nicholas Carlini가 작성했습니다.
최근 저는 언어 모델을 감독하는 새로운 방식을 실험하고 있는데, 이를 "에이전트 팀(agent teams)"이라 부릅니다.
에이전트 팀에서는 여러 Claude 인스턴스가 사람의 개입 없이 하나의 코드베이스를 공유하며 병렬로 작업합니다. 이 접근법은 LLM 에이전트로 달성할 수 있는 범위를 획기적으로 넓혀줍니다.
한계를 시험하기 위해, 저는 16개 에이전트에게 Rust 기반 C 컴파일러를 밑바닥부터 작성해 리눅스 커널을 컴파일하라는 과제를 부여했습니다. 약 2,000회의 Claude Code 세션과 20,000달러의 API 비용을 거쳐, 에이전트 팀은 x86, ARM, RISC-V에서 Linux 6.9를 빌드할 수 있는 10만 줄 규모의 컴파일러를 만들어냈습니다.
이 컴파일러 자체도 흥미로운 결과물이지만, 이 글에서는 장시간 자율적으로 운영되는 에이전트 팀을 위한 하네스(harness) 설계에서 배운 점에 초점을 맞춥니다. 사람의 감독 없이도 에이전트가 올바른 방향을 유지하도록 테스트를 작성하는 법, 여러 에이전트가 동시에 진행할 수 있게 작업을 구조화하는 법, 그리고 이 접근법이 어디서 한계에 부딪히는지를 다룹니다.
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 구현은 매우 단순합니다. 빈 bare git 저장소를 생성하고, 각 에이전트별로 Docker 컨테이너를 띄워 해당 저장소를 /upstream에 마운트합니다. 각 에이전트는 /workspace에 로컬 복사본을 클론하고, 작업이 끝나면 자신의 로컬 컨테이너에서 upstream으로 푸시합니다.
두 에이전트가 동시에 같은 문제를 풀지 않도록, 하네스에서 간단한 동기화 알고리즘을 사용합니다:
이것은 아직 매우 초기 단계의 연구 프로토타입입니다. 에이전트 간 커뮤니케이션 수단은 아직 별도로 구현하지 않았고, 상위 목표를 관리하는 프로세스도 강제하지 않습니다. 오케스트레이션 에이전트도 사용하지 않습니다.
대신, 각 Claude 에이전트가 스스로 행동을 결정하도록 맡겨둡니다. 대부분의 경우 Claude는 "가장 명확한 다음 문제"를 알아서 선택합니다. 버그에 막히면 실패한 접근법과 남은 작업을 문서로 정리하기도 합니다. 프로젝트의 git 저장소에서 히스토리를 살펴보면 Claude가 다양한 작업에 락을 걸고 해제하는 과정을 확인할 수 있습니다.
스캐폴딩이 Claude를 루프 안에서 실행하지만, Claude가 스스로 진행 방향을 파악할 수 있어야 그 루프가 의미를 갖습니다. 저의 핵심 작업은 Claude 주변 환경—테스트, 실행 환경, 피드백—을 설계해서 제 도움 없이도 스스로 방향을 잡을 수 있게 만드는 것이었습니다. 여러 Claude 인스턴스를 오케스트레이션하면서 가장 효과적이었던 접근법들을 소개합니다.
Claude는 제가 부여한 문제를 자율적으로 해결합니다. 따라서 작업 검증기가 거의 완벽해야 합니다. 그렇지 않으면 Claude가 엉뚱한 문제를 풀게 됩니다. 테스트 하네스를 개선하려면 고품질 컴파일러 테스트 스위트를 찾고, 오픈소스 소프트웨어 패키지용 검증기와 빌드 스크립트를 작성하고, Claude가 저지르는 실수를 관찰한 뒤 해당 실패 패턴에 맞는 새 테스트를 설계해야 했습니다.
예를 들어, 프로젝트 후반부에 Claude는 새 기능을 구현할 때마다 기존 기능을 자주 망가뜨리기 시작했습니다. 이를 해결하기 위해 CI(지속적 통합) 파이프라인을 구축하고, 새로운 커밋이 기존 코드를 깨뜨리지 못하도록 더 엄격한 검증 체계를 도입했습니다.
이 테스트 하네스를 제가 아닌 Claude를 위해 만들고 있다는 사실을 끊임없이 상기해야 했습니다. 테스트가 결과를 전달하는 방식에 대한 기존 가정을 근본적으로 재고해야 했다는 뜻입니다.
예를 들어, 각 에이전트는 아무 컨텍스트 없이 새 컨테이너에 투입되므로 현재 상황을 파악하는 데 상당한 시간을 씁니다. 특히 대규모 프로젝트에서 더 그렇습니다. 테스트에 앞서, Claude가 스스로를 도울 수 있도록 상세한 README와 진행 상황 파일을 수시로 업데이트하라는 지시를 포함했습니다.
또한 언어 모델 고유의 한계를 염두에 두고, 이를 우회하도록 설계해야 했습니다. 구체적으로는 다음과 같습니다:
--fast 옵션을 포함합니다. 이 서브샘플은 에이전트별로는 결정적이지만 VM 간에는 랜덤이므로, 전체 파일을 커버하면서도 각 에이전트가 회귀를 정확히 식별할 수 있습니다.실패하는 테스트가 다양하게 많을 때 병렬화는 자명합니다. 각 에이전트가 서로 다른 실패 테스트를 맡으면 됩니다. 테스트 스위트 통과율이 99%에 도달한 뒤에는, 각 에이전트가 서로 다른 소규모 오픈소스 프로젝트(예: SQLite, Redis, libjpeg, QuickJS, Lua)의 컴파일을 담당했습니다.
하지만 리눅스 커널 컴파일에 들어가자 에이전트들이 막혔습니다. 수백 개의 독립적인 테스트로 구성된 테스트 스위트와 달리, 리눅스 커널 컴파일은 하나의 거대한 작업입니다. 모든 에이전트가 같은 버그에 부딪히고, 같은 버그를 수정한 뒤, 서로의 변경사항을 덮어쓰곤 했습니다. 16개 에이전트를 돌려도 모두 같은 작업에 매달리니 전혀 도움이 되지 않았습니다.
해결책은 GCC를 온라인 정답 오라클(known-good compiler oracle)로 활용해 비교하는 것이었습니다. 커널 대부분의 파일은 GCC로, 나머지 파일만 Claude의 C 컴파일러로 컴파일하는 새 테스트 하네스를 작성했습니다. 커널이 정상 동작하면 Claude가 담당한 파일에는 문제가 없다는 뜻이고, 실패하면 일부 파일을 GCC로 다시 컴파일해 범위를 좁혀갈 수 있습니다. 이를 통해 각 에이전트가 서로 다른 파일의 서로 다른 버그를 병렬로 수정해나갈 수 있었고, 결국 Claude의 컴파일러가 모든 파일을 컴파일할 수 있게 되었습니다. (이후에도 개별적으로는 정상이지만 함께 사용하면 실패하는 파일 쌍을 찾기 위해 델타 디버깅 기법을 적용해야 했습니다.)
병렬화는 역할 전문화도 가능하게 합니다. LLM이 작성한 코드는 기존 기능을 중복 구현하는 경향이 있어서, 한 에이전트에게 중복 코드를 찾아 통합하는 역할을 맡겼습니다. 다른 에이전트에게는 컴파일러 자체의 성능 개선을, 또 다른 에이전트에게는 효율적인 컴파일 결과물 생성을 담당시켰습니다. 한 에이전트에게는 Rust 개발자 관점에서 프로젝트 설계를 비평하고 전체 코드 품질을 높이기 위한 구조적 개선을 요청했고, 또 다른 에이전트는 문서화를 맡겼습니다.
이 프로젝트는 역량 벤치마크로 설계되었습니다. LLM이 현재 시점에서 간신히 달성할 수 있는 한계를 스트레스 테스트함으로써, 미래 모델이 안정적으로 달성할 수 있는 수준에 대비하는 것이 목적입니다.
저는 이 C 컴파일러 프로젝트를 Claude 4 모델 시리즈 전체에 걸친 벤치마크로 활용해왔습니다. 이전 프로젝트들과 마찬가지로, 먼저 원하는 결과물의 초안을 작성했습니다. 의존성 없이 밑바닥부터 만드는 최적화 컴파일러, GCC 호환, 리눅스 커널 컴파일 가능, 다중 백엔드 지원 등의 요건입니다. 설계의 일부 측면(예: 여러 최적화 패스를 지원하기 위한 SSA IR 사용)은 명시했지만, 구체적인 구현 방법은 지정하지 않았습니다.
이전 Opus 4 모델은 동작하는 컴파일러를 만들어내는 것조차 겨우 가능한 수준이었습니다. Opus 4.5가 대규모 테스트 스위트를 통과하는 기능적 컴파일러를 생산할 수 있는 문턱을 처음 넘었지만, 실제 대형 프로젝트를 컴파일하지는 못했습니다. Opus 4.6에서의 목표는 다시 한번 한계를 시험하는 것이었습니다.
2주에 걸쳐 약 2,000회의 Claude Code 세션을 수행하면서, Opus 4.6은 20억 개의 입력 토큰을 소비하고 1억 4천만 개의 출력 토큰을 생성했습니다. 총비용은 20,000달러 약간 미만입니다. 가장 비싼 Claude Max 요금제와 비교해도 매우 고비용 프로젝트였습니다. 하지만 제가 직접 이 작업을 수행하는 데 드는 비용은 물론, 팀 전체를 투입하는 비용에 비하면 극히 일부에 불과합니다.
이 컴파일러는 클린룸 구현입니다(개발 과정 전체에서 Claude는 인터넷에 접속하지 않았습니다). Rust 표준 라이브러리만을 의존성으로 사용합니다. 10만 줄 규모의 이 컴파일러는 x86, ARM, RISC-V에서 부팅 가능한 Linux 6.9를 빌드할 수 있습니다. 또한 QEMU, FFmpeg, SQLite, PostgreSQL, Redis를 컴파일할 수 있으며, GCC torture 테스트 스위트를 포함한 대부분의 컴파일러 테스트 스위트에서 99% 통과율을 기록합니다. 개발자의 궁극적 리트머스 테스트도 통과합니다—Doom을 컴파일하고 실행할 수 있습니다.
다만, 이 컴파일러에도 한계는 있습니다:
결과적으로 이 컴파일러는 Opus의 능력 한계에 거의 도달한 상태입니다. 위에 언급한 한계들을 해결하려고 상당히 노력했지만 완전히 성공하지는 못했습니다. 새로운 기능과 버그 수정이 기존 기능을 빈번히 망가뜨렸습니다.
특히 어려웠던 사례를 하나 들면, Opus는 16비트 리얼 모드로 부팅하는 데 필요한 16비트 x86 코드 생성기를 구현하지 못했습니다. 66/67 opcode 접두사를 통해 올바른 16비트 x86 코드를 출력할 수는 있었지만, 컴파일 결과물이 60KB를 넘겨 리눅스가 강제하는 32KB 코드 제한을 크게 초과했습니다. 결국 Claude는 이 단계에서 GCC를 호출하는 방식으로 우회합니다. (이는 x86에만 해당됩니다. ARM이나 RISC-V에서는 Claude의 컴파일러가 완전히 자체적으로 컴파일할 수 있습니다.)
컴파일러의 소스 코드를 공개합니다. 다운로드해서 코드를 읽어보고, 즐겨 사용하는 C 프로젝트에 시험해보세요. 언어 모델이 무엇을 할 수 있는지 이해하는 가장 좋은 방법은 한계까지 밀어붙인 뒤, 어디서부터 무너지기 시작하는지 관찰하는 것이라는 점을 일관되게 확인해왔습니다. 앞으로도 Claude가 이러한 한계를 해결하려는 시도를 계속 푸시할 예정이니, 관심 있으시면 따라와 주세요.
언어 모델 세대가 바뀔 때마다 새로운 활용 방식이 열려왔습니다. 초기 모델은 IDE에서 탭 자동완성에 유용했고, 머지않아 모델이 독스트링만으로 함수 본문을 완성할 수 있게 되었습니다. Claude Code의 출시는 에이전트를 주류로 끌어올려 개발자가 Claude와 페어 프로그래밍할 수 있게 만들었습니다. 하지만 이 모든 제품은 사용자가 작업을 정의하고, LLM이 몇 초에서 몇 분간 실행해 결과를 돌려주면, 사용자가 후속 지시를 내리는 구조를 전제합니다.
에이전트 팀은 복잡한 프로젝트 전체를 자율적으로 구현할 수 있는 가능성을 보여줍니다. 이로써 도구 사용자인 우리가 더 야심 찬 목표를 세울 수 있게 됩니다.
아직 초기 단계이며, 완전 자율 개발에는 실질적인 위험이 따릅니다. 사람이 Claude와 함께 개발할 때는 일관된 품질을 유지하고 실시간으로 오류를 잡을 수 있습니다. 하지만 자율 시스템에서는 테스트가 통과한다고 작업이 완료되었다고 착각하기 쉬운데, 실제로 그런 경우는 드뭅니다. 저는 과거에 모의 해킹(penetration testing) 분야에서 대기업 제품의 취약점을 공격하는 일을 했었는데, 프로그래머가 직접 검증하지 않은 소프트웨어를 배포하는 상황은 심각한 우려 사항입니다.
그래서 이 실험이 흥미진진하면서도 동시에 불안감을 느끼게 합니다. 이 컴파일러를 만드는 작업은 최근 가장 즐거운 경험이었지만, 2026년 이 시점에 이 정도가 가능할 줄은 전혀 예상하지 못했습니다. 언어 모델과 그 위에서 작동하는 스캐폴드 양쪽 모두의 빠른 발전은 방대한 양의 새 코드를 작성할 수 있는 길을 열고 있습니다. 긍정적 활용이 부정적 영향을 압도할 것으로 기대하지만, 안전하게 헤쳐나가기 위한 새로운 전략이 필요한 새로운 세계에 진입하고 있습니다.
Josef Bacik, Edwin Chen, Bernardo Meurer Costa, Jake Eaton, Dan Kelley, Felix Klock, Jannet Park, Steve Weis, 그리고 Anthropic 전반에서 도움과 기여를 해주신 많은 분들께 특별히 감사드립니다.