수천 개의 에이전트를 오케스트레이션해 장기간 자율 코딩을 확장하며 얻은 시스템 설계, 인프라, 프롬프트 최적화의 교훈을 공유한다.
우리는 장기간 자율 코딩(long-running autonomous coding)을 확장하는 연구에 대한 반응에 큰 고무를 받았습니다.
이 작업은 현재 모델들의 한계를 밀어붙이기 위한 내부 연구로 시작했습니다. 연구의 일환으로, 우리는 수천 개의 에이전트를 오케스트레이션하고 그들의 행동을 관찰할 수 있는 새로운 에이전트 하네스를 만들었습니다. 지난달까지 우리 시스템은 1주일 동안 연속 실행할 수 있을 만큼 안정화되었고, 연구 프로젝트(웹 브라우저)에 대한 커밋의 대다수를 만들어 냈습니다. 이 브라우저는 외부에서 사용되도록 의도된 것이 아니었고, 우리는 코드에 결함이 있을 것이라고 예상했습니다.
하지만 약간의 특이점이 있더라도, 수천 개의 에이전트가 협력해 사람의 개입 없이도 거의 전부 실행 가능한 결과물을 만들어낼 수 있었다는 사실은 공유할 만한 이정표처럼 느껴졌습니다. 그 이후로도 연구를 계속해 왔고, 하네스를 어떻게 만들었는지 더 깊이 다뤄보고자 합니다.
또한 이 연구의 일부를 일부 사용자들이 직접 시험해 볼 수 있도록 제공하고 있습니다.
우리 연구 프로젝트는 제 개인 사이드 프로젝트로 시작했습니다.
브라우저는 흥미로운 벤치마크처럼 보였습니다. 최전선 모델들의 한계를 드러낼 만큼 충분히 복잡했고, 함께 동작해야 하는 서로 다른 하위 시스템도 매우 많았기 때문입니다.
처음 계획은 JavaScript 지원 없이 웹 페이지 렌더링을 지원하는 것이었습니다. 저는 Opus 4.5에 프롬프트를 넣어 브라우저 엔진을 만들기 위한 자세한 계획을 쓰게 했고, 계획을 얼마나 멀리까지 밀고 나갈 수 있는지 보기 위해 반복해서 “계속해(keep going)”라고 재촉했습니다.
이는 금방 실패했습니다. 모델은 자신이 무엇을 하고 있는지 추적을 잃었고, 아직 한참 멀었는데도 자주 성공을 선언하며 멈췄고, 복잡한 구현 디테일에 갇혀 버렸습니다. 하지만 깊은 지식과 지능의 징후는 분명했습니다. 작은 조각으로는 좋은 코드를 작성할 수 있었습니다.
핵심 문제는 브라우저가 너무 압도적인 작업이라는 점이었고, 더 작은 하위 과제로 쪼개야 했습니다. 다음으로 저는 에이전트가 병렬로 맡을 수 있는 주요 작업들의 의존성 그래프를 계획하게 했습니다. 작업마다 에이전트를 수동으로 띄우고, 멈추면 다시 재촉했습니다. 처리량은 늘었지만 결과는 크게 낫지 않았습니다. 에이전트들은 서로 소통할 수 없었고, 프로젝트 전체에 대한 피드백도 제공할 수 없었습니다. 시스템은 더 동적이어야 했습니다.
한편 GPT-5.1(이후 GPT-5.2)이 지시사항을 정확히 따르는 능력에서 더 나은 결과를 보이기 시작했습니다. 이는 장기간 실행 에이전트에 잘 맞는 듯했기 때문에, 우리는 이 실험을 바탕으로 OpenAI 모델을 사용하도록 하네스를 업데이트했습니다.
이 시점에서 하네스는 JavaScript 없는 간단한 웹 브라우저 버전을 만들 수 있었지만, 하나의 에이전트로 완전한 브라우저 엔진을 만드는 것은 지나치게 느려 현실적이지 않았습니다.
여기서 다음 연구가 시작됐습니다. 연산(compute)에 10배 더 쓰면 의미 있는 처리량도 10배 더 얻을 수 있을까요?
우리는 간단한 Rust 기반 하네스를 가진 새 리포지토리를 시작했습니다.
분산 시스템의 복잡성과 씨름하는 대신, 우리는 자원이 풍부한 단일 대형 Linux VM(Virtual Machine)에서 하네스를 실행했습니다. 하네스를 제어하기 위해 VM에 SSH로 접속해 간단한 터미널 인터페이스를 사용했습니다.
우리는 시스템을 제대로 관측할 수 있도록 초기에 더 많은 시간을 투자했습니다. 모든 에이전트 메시지, 시스템 액션, 커맨드 출력들을 타임스탬프와 함께 로깅해 세션을 분석하고 재생할 수 있게 했습니다. 이는 우리가 수동으로 리뷰하는 데도 유용했지만, Cursor로 다시 흘려보내 방대한 데이터를 훑고 빠르게 패턴을 찾는 데도 도움이 됐습니다.
첫 번째 멀티 에이전트 아이디어는 가장 단순한 형태였습니다. 동일한 역할을 가진 에이전트들이 공유 상태 파일을 통해 다른 에이전트가 무엇을 하는지 보고, 자신이 할 일을 결정하고, 파일을 업데이트하게 하는 방식입니다.


우리는 무엇을 해야 하는지 최소한으로만 규정하고, 에이전트들이 스스로 어떻게 자기 조율(self-coordinate)할지 알아내도록 두었습니다. 이는 빠르게 실패했습니다.
조율 파일은 금세 더 많은 문제를 만들었습니다. 에이전트들은 락(lock)을 너무 오래 잡아두거나, 해제를 잊거나, 불법인 상황에서 락/언락을 시도했고, 전반적으로 조율 파일에 락을 잡고 있는 것의 의미를 이해하지 못했습니다. 락은 잘못 쓰기 쉽고, “간신히 맞는” 상태가 되기 쉬운데, 프롬프트를 더 준다고 해결되지 않았습니다.
락킹은 경합도 너무 많이 유발했습니다. 20개의 에이전트가 1~3개 에이전트 수준의 처리량으로 느려졌고, 대부분의 시간이 락을 기다리는 데 쓰였습니다. 우리는 에이전트에게 다른 에이전트의 작업을 명시적으로 기다릴 수 있는 툴을 주기도 했지만, 거의 사용하지 않았습니다. 락 없는 낙관적 동시성 제어(optimistic concurrency control)도 시도했는데, 오버헤드는 줄었지만 혼란을 없애지는 못했습니다.
에이전트 간 구조가 부족하니 어떤 단일 에이전트도 크고 복잡한 작업을 맡지 않았습니다. 그들은 경합과 충돌을 피하면서, 프로젝트 전체에 책임을 지기보다는 더 작고 안전한 변경을 선택했습니다.
다음으로 우리는 역할을 분리해 에이전트들에게 오너십(ownership)과 책임(accountability)을 부여했습니다:


플래너(planner)가 먼저 사용자의 지시사항을 향해 진전을 내기 위한 정확한 접근 방식과 산출물(deliverables)을 제시합니다. 이는 실행자(executor)에게 전달되고, 실행자는 계획이 완전히 달성되도록 보장하는 유일한 리드 에이전트가 됩니다. 실행자는 워커(workers)에게 작업을 생성(spawn)할 수 있었고, 이는 선형적인 확장과 처리량을 제공했습니다.
지속적인 전진과 책임을 위해, 독립적인 저지(judge)가 실행자 종료 후 실행되어 완료 여부와 다음 반복(iteration)이 필요한지 판단했습니다. 이는 많은 조율 문제를 해소했습니다. 실행을 소유하고 감독하는 단일 역할이 있으면 워커들은 자기 작업에만 좁게 집중할 수 있고, 전체 시스템은 여전히 결과를 낼 수 있었습니다.
이 설계에 도달하려면 시스템을 면밀히 관찰해야 했습니다.
큰 문제가 있으면 반복적으로, 그리고 많은 에이전트와 툴 호출에 걸쳐 나타나는 경향이 있었습니다. 예를 들어, 많은 에이전트가 동시에 git restore를 실행하면서 경합이 너무 커진다는 점을 알아냈습니다. 우리는 Cursor를 사용해 로그를 분석하고 프롬프트와 비교하여 왜 행동이 기대와 달랐는지 이해했습니다.
결국 이 시스템은 가장 느린 워커에 의해 병목이 생긴다는 것을 발견했습니다. 너무 경직되어 있었습니다.
모든 계획을 upfront로 세우면, 새 이슈가 발견될 때 동적으로 재조정하기도 어려웠습니다. 일부 에이전트는 역효과 나는 방향으로 가버렸고, 루프의 다음 반복까지는 스스로 교정할 수 없었습니다.
다음 버전에서는 독립적인 플래너를 제거했습니다.
이제 실행자는 목표를 전달하기 위한 계획도 세우고, 동시에 작업을 생성할 수 있었습니다. 실행자가 단 하나의 에이전트였기 때문에, 계획을 어딘가에 적어두거나, 정적인 단일 계획에 고수하거나, 모든 워커를 딱딱하게 기다릴 필요가 없었습니다.
모든 역할에 걸친 에이전트가 장기간에 걸쳐 드리프트(drift)하지 않도록, 우리는 신선도(freshness) 메커니즘을 도입했습니다:
scratchpad.md는 누적해서 덧붙이기보다는 자주 다시 써야 한다.시스템은 이제 매우 동적이고 유연해졌습니다. 코드 탐색을 선제적으로 수행하고, 결정을 재고하고, 워커를 관리하고, 작업을 섞어 수행(interleave)하고, 최신 정보를 지속적으로 반영할 수 있었습니다. 우리는 에이전트들이 지시사항을 끝까지 따르는 데 reasonably good하다는 것을 발견했기 때문에, 시스템을 단순하게 유지하려고 저지를 제거했습니다.


이런 개선에도 불구하고, 연속 실행자(continuous executor)는 병적 행동(pathological behaviors)을 보이기 시작했습니다. 랜덤하게 잠들거나, 에이전트를 더 이상 실행하지 않거나, 작업을 스스로 해버리거나, 계획을 거부하고 아주 적은 수의 좁은 작업만 생성하거나, 워커 변경을 제대로 병합하지 않거나, 너무 이른 완료를 주장했습니다.
우리는 실행자에게 너무 많은 역할과 목표가 동시에 주어졌다는 것을 발견했습니다. 예컨대 계획, 탐색, 리서치, 작업 생성, 워커 상태 확인, 코드 리뷰, 편집 수행, 출력 병합, 루프 종료 판단 등이었습니다. 돌이켜보면 압도된 것이 당연했습니다.
최종 설계는 우리가 배운 모든 것을 반영합니다:
흥미롭게도, 이는 오늘날 일부 소프트웨어 팀이 운영되는 방식과 닮아 있습니다.


서브플래너는 워커를 빠르게 확산(fan out)시키면서도, 전체 시스템이 여전히 어떤 에이전트에 의해 완전히 소유되고 책임지는 상태를 유지하게 해 처리량을 높입니다. 이는 단일 플래너가 압도되어 터널 비전(tunnel vision)에 빠지기 쉬운 대규모 프로젝트와 작업에도 도움이 됐습니다.
핸드오프에는 무엇을 했는지뿐 아니라 중요한 노트, 우려사항, 일탈(deviations), 발견사항, 생각, 피드백이 들어갑니다. 플래너는 이를 후속 메시지로 받습니다. 이는 시스템을 지속적으로 움직이게 합니다. 플래너가 “끝났다”고 느끼더라도 계속 업데이트를 받고, 최신 리포를 가져오고, 계속 계획을 세우고 다음 의사결정을 할 수 있습니다.
모든 에이전트가 이 메커니즘을 갖고 있기 때문에, 전역 동기화나 크로스톡(cross-talk)의 오버헤드 없이도, 더 전역적인 관점을 가진 오너들에게 체인 상단으로 정보가 전파되며, 시스템은 놀라울 정도로 동적이고 자기 수렴(self-converging)할 수 있습니다.
우리는 원래 전역적으로 상황을 아는( globally-aware) 중앙 품질 관리와, 너무 많은 워커가 동시에 push, rebase, 충돌 해결, 병합을 시도하며 생기는 경합을 줄이기 위해 인티그레이터(integrator)를 추가했습니다.
하지만 이는 곧 명백한 병목이 되었습니다. 수백 명의 워커와 하나의 관문(즉 “레드 테이프”)이 존재해 모든 작업이 그곳을 통과해야 했습니다. 프롬프트 변경도 시도했지만, 결국 불필요하다고 판단했고, 시스템을 단순화하기 위해 제거했습니다.
이 시스템은 1주일 동안 10M 툴 호출에 걸쳐 시간당 약 1,000개의 커밋으로 정점을 찍었습니다. 시스템이 시작된 뒤에는 우리 쪽에서 어떤 개입도 필요하지 않았습니다.
이 처리량을 달성하기 위해 의도적인 트레이드오프가 있었습니다.
모든 단일 커밋 전에 100% 정확성을 요구했을 때, 이는 중대한 직렬화(serialization)와 유효 처리량의 급감으로 이어졌습니다. API 변경이나 오타 같은 작은 오류 하나만 있어도 전체 시스템이 멈춰 서게 됐습니다. 워커는 자신의 범위를 벗어나 관련 없는 것들을 고치기 시작했습니다. 많은 에이전트가 같은 이슈를 고치려고 몰려 서로를 짓밟는 상황도 발생했습니다.
이런 행동은 도움이 되지도, 필요하지도 않았습니다. 어느 정도의 여유를 허용하면 에이전트들은 다른 이슈도 곧 동료 에이전트에 의해 고쳐질 것이라고 믿을 수 있고, 실제로도 시스템 전체 코드베이스에 대해 효과적인 오너십과 위임이 이루어지기 때문에 그 믿음이 맞습니다. 오류는 생기지만 곧바로 고쳐집니다. 오류율은 작고 일정하게 유지되며, 완전히 깨끗해지는 경우는 드물 수 있어도 안정적이고 관리 가능하며, 폭증하거나 악화되지 않습니다.
이는 이상적이고 효율적인 시스템이 어느 정도의 오류율을 받아들이되, 릴리스 전에는 에이전트가 정기적으로 스냅샷을 만들고 빠른 수정(fixup) 패스를 수행하는 최종 “green” 브랜치가 필요하다는 점을 시사할 수 있습니다.
때로는 여러 에이전트가 같은 파일을 건드리거나 같은 코드를 리팩터링합니다. 이를 완전히 근절하려 들거나 과도하게 공학적으로(overengineer) 해결책을 만들기보다는, 우리는 어느 정도의 난류(turbulence)를 받아들이고 시스템이 짧은 시간 내 자연스럽게 수렴하고 안정화되도록 둡니다.
이는 추가 토큰을 쓰고 국소 경합을 만들지만, 시스템을 전반적으로 더 단순하게 유지해 줍니다. 모델을 정렬(alignment)시키고 압도시키지 않기가 더 쉽고, 관리·관측이 더 쉽고, 마찰이 적고, 전역 생산성이 더 좋습니다. 또한 지나치게 복잡한 접근을 피하게 해 줍니다.
각 멀티 에이전트 실행은 분산 시스템에 대한 이른 복잡성을 피하기 위해, 시스템 자원이 충분한 단일 대형 머신에서 수행되었습니다. 이는 대부분의 실행이 수백 개 에이전트에서 정점을 찍었고, 보통 머신을 포화(saturated)시키긴 했지만 과도하게 자원을 요구하지는 않았기 때문에 잘 맞았습니다. 이 아키텍처는 시스템 메트릭을 관찰하고, 필요할 때 상태를 공유·복사하는 일을 쉽게 해줬습니다.
에이전트의 RAM 사용량을 제한한 뒤에는 디스크가 핫스팟이 되었습니다. 특히 모놀리식 프로젝트에서는 수백 개의 에이전트가 동시에 컴파일하면서 빌드 산출물의 읽기/쓰기에서 초당 수 GB의 I/O가 발생했습니다. 이는 하네스의 전체 처리량에 큰 영향을 줬고, 흥미로운 교훈이었습니다. 프로젝트 구조, 아키텍처 결정, 개발자 경험이 토큰/커밋 처리량에 영향을 줄 수 있는데, 이는 이상적으로는 “생각하고 코딩”하는 시간이 지배적이어야 하지만 실제로는 코드베이스를 다루는 작업(예: 컴파일)이 시간을 지배하기 때문입니다.
일반적인 개발 환경에도 제약과 비효율이 있었습니다. 단일 사용자 워크스페이스에서는 합리적이거나 중요하지 않은 것들이, 한 머신에서 수백 에이전트가 같은 일을 반복하면 두드러지게 나타납니다. 이를 해결하는 사소한 방법은 각 에이전트에게 자기 머신을 주는 것입니다. 하지만 이런 프리미티브와 툴을 다시 생각하고 재설계하는 것만으로도 큰 효율 향상을 얻을 수 있는 흥미로운 로우-행잉(low-hanging) 기회들이 있습니다.
예를 들어 Git과 Cargo 같은 많은 툴은 단순한 동시성 제어 메커니즘으로 공유 락(shared locks)을 사용합니다. 데이터베이스 같은 동시성 시스템에서 잘 확립된 메커니즘을 가져오면 멀티 에이전트 시스템에서도 똑같이 잘 동작하게 만들 수 있을까요? 모든 에이전트는 자기 리포 사본을 갖고 있지만 대부분의 파일과 산출물은 동일합니다. 더 정교한 프로덕션 스토리지 시스템에 있는 간단한 copy-on-write와 deduplication 기능을 추가하면, 별도의 인프라를 만들지 않고도 전형적인 “단일 사용자” 시스템에서 비슷한 쉬운 성과를 얻을 수 있을까요?
이 멀티 에이전트 시스템에 주는 지시사항은 매우 중요했습니다.
처음에는 이를 우리의 주요 목표로 두지 않았고, 대신 안정적이고 효과적인 하네스를 만드는 데 집중했습니다. 하지만 지시사항의 중요성은 금세 분명해졌습니다. 우리는 사실상 일반적인 코딩 에이전트와 상호작용하고 있었는데, 단지 시간과 연산이 몇 자릿수나 더 많았을 뿐입니다. 이는 모든 것을 증폭시키며, 비최적이거나 불명확한 지시도 마찬가지로 증폭됩니다.
초기 지시사항에 더 많은 시간을 쓰는 것은 타당합니다. 결국 에이전트는 에이전트입니다. 당신의 지시를 엄격히 따르도록 학습되어 있으며, 설령 지시가 나쁘더라도 그 경로로 내려가고, 스스로 바꾸거나 덮어쓰지 않습니다.
우리는 연구 프로젝트에서 성공을 보고 싶었기 때문에, 프로젝트와 하네스가 진화함에 따라 초기 지시사항도 바꿨습니다. 우리는 브라우저를 만드는 법을 배우는 동시에 이 새로운 멀티 에이전트 시스템을 운영하는 법도 배우고 있었고, 부실하거나 과소명세된(spec underspecified) 요구사항이 출력물 품질에 반영되는 것을 볼 수 있었습니다. 이는 하네스 자체의 문제가 아니었습니다. 하네스는 그저 우리의 지시를 정확히 따르고 있었을 뿐입니다.
브라우저 프로젝트에서의 몇 가지 예:
JavaScript 없는 간단한 브라우저의 첫 버전은, 완전한 브라우저로 진화하기에는 부적합한 아키텍처로 수렴했습니다. 이는 초기 명세의 실패였습니다.
비슷하게, 에이전트에게 프로젝트가 “처음부터 만드는 브라우저”라고 말했음에도, 그들은 스스로 구현할 수도 있었던 의존성을 일부 끌어오거나, 제대로 된 구현이 진행되는 동안 임시 스캐폴딩(scaffolding)으로 썼습니다. 이는 지시사항에서의 누락이었습니다. 이후 실행에서는 의존성 철학과 어떤 라이브러리를 쓰면 안 되는지 명시적으로 적어 이를 교정했습니다.
그 후속 실행에서는 모놀리식에서 벗어나, 많은 self-contained 크레이트(crate)로의 큰 재구조화도 이뤄졌습니다. 리포는 심각하게 망가진 상태였지만, 멀티 에이전트 시스템은 며칠 안에 동작하는 코드로 수렴했습니다. 이는 시스템이 협업적으로, 그리고 지능적으로 함께 일할 수 있는 강한 능력을 지녔다는 것을 보여줍니다. 완전히 깨진 상태에서도 더 악화되거나 막히지 않고 버텨냈습니다. 이 실행은 컴파일 대기 시간도 훨씬 적었고, 이전보다 몇 배 더 높은 처리량으로 동작했습니다.
아키텍처와 지시사항은 중요합니다. 에이전트는 엄청난 엔지니어링 능력을 갖고 있지만, 지시가 좋든 나쁘든 끝까지 따릅니다. 지나치게 좁은 메트릭과 구조 없는 자유 사이의 균형을 찾는 것도, 무엇이 자명하고 무엇을 명시해야 하는지 아는 것도 어려웠습니다.
이 모든 것은 의도를 끌어내고(elicit), 명세하고(specify), 이해하는 것이 얼마나 중요한지 보여주며, 이 규모에서는 그 중요성이 더 커집니다. 조향 가능성(steerability)과 관측 가능성(observability)은 계속 탐구할 흥미로운 연구 분야가 될 것입니다.
프롬프트는 진화 과정의 중요한 부분이었습니다.
우리는 모델이 이미 할 줄 아는 일은 지시하지 않는 편이 더 낫다는 것을 발견했습니다. 대신 모델이 모르는 일(예: 멀티 에이전트 협업)이나 관련 도메인에 특화된 일(예: 테스트 실행 방법, 배포 파이프라인)만 지시하는 것이 좋았습니다. 모델을 엔지니어링은 알지만 당신의 코드베이스와 프로세스는 모르는, 똑똑한 신입처럼 대하세요.
제약(constraints)은 지시사항(instructions)보다 더 효과적입니다. “TODO 금지, 부분 구현 금지”는 “구현을 끝내는 걸 기억해”보다 더 잘 동작합니다. 모델은 기본적으로 좋은 일을 하는 편입니다. 제약은 그들의 경계를 정의합니다.
상위 수준이거나 더 깊은 작업에서는 체크박스 멘탈리티를 피하세요. 의도에 대한 상세한 지시는 주되, 구체적으로 할 일을 나열하면 모델이 더 넓은 범위보다 그 나열된 것들을 달성하는 데 집중하는 경향이 있다는 점을 기억하세요. 또한 나열되지 않은 것들은 암묵적으로 우선순위에서 밀리게 됩니다. 보통은 모델이 자신의 판단과 에이전시(agency)를 쓰게 두는 편이 더 좋습니다.
또한 범위의 “양”을 말할 때는 구체적인 수치와 범위를 주는 것이 유용하다는 것을 발견했습니다. “많은 작업을 생성하라” 같은 지시는 소수만 만들어내는 경향이 있는데, 보수적 기본값으로 안전하게 가면서도 기술적으로는 지시를 따르기 때문입니다. “20~100개의 작업을 생성하라”는 의도가 더 큰 범위이며, 야심차야 한다는 점을 전달하고, 우리는 매우 다른(더 넓은) 행동을 관찰했습니다.
우리는 연구를 통해 몇 가지 원칙을 세웠습니다:
이런 시스템은 제대로 만들면 우아하게 단순해지는 경향이 있지만, 어떤 “단순한 접근”이 통할지는 여러 접근을 탐색해 보기 전까지는 분명하지 않았습니다. 현재의 시스템 설계는 최소한의 오버헤드로 실행되어 왔고, 유용한 방식으로 토큰 처리량의 선형 확장을 제공합니다. 하네스에는 더 이상의 큰 반복 개선이 필요하지 않았습니다.
취향(taste), 판단(judgement), 방향(direction)은 인간에게서 나왔지만, AI는 이 연구를 빠르게 반복하고 탐색하는 데 강력한 힘의 배수(force-multiplier)였습니다.
이는 “선순환(virtuous)” AI 루프와도 닮아 있습니다. AI가 AI를 개발하는 데 사용되고, 모델과 에이전트와 하네스가 좋아질수록 자기 자신에게 되먹임되어 점점 더 빠르게 가속되는 구조입니다. 우리는 도구를 만들고, 그 도구는 우리를 형성합니다.
이 연구에는 오늘날 일부 소프트웨어 팀이 운영되는 방식과의 시적인 유사성도 있습니다. 이 모델들이 이런 방식으로 명시적으로 훈련된 것은 아닌데도 그렇다는 점은, 이것이 창발적(emergent) 행동이며 어쩌면 소프트웨어 프로젝트를 구조화하는 “올바른 방식”일 수도 있음을 시사합니다.
우리는 극도로 장기간 실행되는 에이전트를 계속 연구할 예정이며, 우리의 발견은 제품의 미래에 반영될 것입니다.