자율 주행 코드베이스를 향해

저희는 장시간 실행되는 자율 코딩 확장에 관한 연구에 대한 반응에 큰 고무를 받았습니다.
이 작업은 현재 모델의 한계를 밀어붙이기 위한 내부 연구로 시작되었습니다. 연구의 일환으로, 저희는 수천 개의 에이전트를 조율하고 그 동작을 관찰할 수 있는 새로운 에이전트 하네스를 만들었습니다. 지난달에는 시스템이 1주일 내내 연속으로 실행될 만큼 충분히 안정화되었고, 연구 프로젝트(웹 브라우저)의 커밋 대부분을 만들어냈습니다. 이 브라우저는 외부 사용을 염두에 두고 만든 것이 아니었기 때문에, 코드에 미흡한 부분이 있을 것이라고 예상했습니다.
그럼에도 다소 거친 부분이 있었지만, 수천 개의 에이전트가 함께 작업해 사람의 개입 없이도 거의 그대로 실행 가능한 결과물을 만들어냈다는 사실은 충분히 공유할 만한 이정표처럼 느껴졌습니다. 그 이후로도 저희는 연구를 계속해 왔고, 이 하네스가 어떻게 만들어졌는지 더 깊이 있게 다뤄보고 싶었습니다.
또한 일부 사용자분들이 이 연구의 일부를 직접 사용해 볼 수 있도록 공개하고 있습니다.
배경
우리의 연구 프로젝트는 제 개인적인 사이드 프로젝트로 시작되었습니다.
브라우저는 흥미로운 벤치마크처럼 보였습니다. 최전선 모델의 한계를 드러내기에 충분히 복잡했고, 함께 작동해야 하는 여러 하위 시스템도 있었습니다.
처음 계획은 JavaScript 지원 없이 웹 페이지를 렌더링하도록 만드는 것이었습니다. 저는 Opus 4.5에 브라우저 엔진을 만들기 위한 자세한 계획을 세워 달라고 프롬프트한 뒤, 그 계획을 어디까지 이어 갈 수 있는지 보기 위해 반복해서 "keep going"이라고 유도했습니다.
이 시도는 금방 실패했습니다. 모델은 자신이 무엇을 하고 있는지 자주 놓쳤고, 목표에 한참 못 미쳤는데도 성공했다고 선언하곤 했으며, 복잡한 구현 세부 사항에서 막혔습니다. 하지만 깊은 지식과 지능의 징후도 보여주었습니다. 작은 단위로는 좋은 코드를 작성할 수 있었습니다.
핵심 문제는 브라우저가 너무 버거운 작업이었고, 더 작은 하위 작업으로 나눌 필요가 있다는 점이었습니다. 그래서 다음으로는 에이전트에게 에이전트들이 병렬로 맡을 수 있는 주요 작업의 의존성 그래프를 계획하게 했습니다. 에이전트는 작업별로 수동 생성했고, 멈추면 다시 유도했습니다. 이로 인해 처리량은 늘었지만 결과가 크게 좋아지지는 않았습니다. 에이전트들은 서로 소통할 수 없었고, 프로젝트 전체에 대한 피드백도 제공할 수 없었습니다. 시스템은 더 동적으로 움직여야 했습니다.
한편 GPT-5.1(이후에는 GPT-5.2)도 지시를 정확하게 따르는 능력에서 더 나은 결과를 보이기 시작했습니다. 이는 장시간 실행되는 에이전트에 잘 맞아 보였고, 그래서 우리는 이러한 실험을 바탕으로 OpenAI 모델을 사용하도록 하네스를 업데이트했습니다.
이 시점에서 하네스는 JavaScript 없는 단순한 웹 브라우저 버전은 만들 수 있었지만, 하나의 에이전트로 완전한 브라우저 엔진을 만드는 것은 지나치게 느려 현실적으로 어려웠습니다.
이것이 다음 연구 단계의 시작이었습니다. 더 의미 있는 처리량을 10배 늘리기 위해 컴퓨팅 비용을 10배 더 쓸 수 있을까?
단일에서 멀티 에이전트로
저희는 간단한 Rust 기반 하네스로 새 리포지토리를 시작했습니다.
분산 시스템의 복잡성을 다루기보다는, 리소스가 풍부한 단일 대형 Linux VM(가상 머신)에서 하네스를 실행했습니다. 하네스를 제어하기 위해 VM에 SSH로 접속해 간단한 터미널 인터페이스를 사용했습니다.
저희는 초기에 시스템을 제대로 관찰할 수 있도록 하는 데 더 많은 시간을 들였습니다. 모든 에이전트 메시지, 시스템 동작, 명령 출력물을 타임스탬프와 함께 기록해 세션을 분석하고 재현할 수 있게 했습니다. 이는 저희가 수동으로 리뷰하는 데 도움이 되었을 뿐 아니라, 이를 다시 Cursor에 입력해 대량의 데이터를 추려 보고 패턴을 빠르게 찾아내는 데에도 유용했습니다.
자기 조정
첫 번째 멀티 에이전트 아이디어는 가장 단순했습니다. 동일한 역할을 가진 에이전트들이 공유 상태 파일을 사용해 다른 에이전트가 무엇을 작업 중인지 확인하고, 자신이 무엇을 할지 결정한 뒤, 그 파일을 업데이트하게 하는 방식이었습니다.


무엇을 해야 하는지에 대해 구체적으로 지시하는 대신, 에이전트들이 스스로 조정하는 방법을 알아내도록 맡기려 했습니다. 하지만 이 방식은 금방 실패했습니다.
조정 파일은 곧 더 많은 문제를 낳았습니다. 에이전트들은 잠금을 너무 오래 유지했고, 잠금 해제를 잊어버렸으며, 허용되지 않는 상황에서 잠그거나 잠금 해제를 시도했고, 전반적으로 조정 파일에 잠금을 걸고 있는 것의 의미를 이해하지 못했습니다. 잠금은 구현을 잘못하기 쉽고, 정확하게 구현할 수 있는 범위도 좁았으며, 프롬프트를 더 추가해도 도움이 되지 않았습니다.
잠금은 지나친 경합도 일으켰습니다. 에이전트 20개를 두어도 대부분의 시간을 잠금을 기다리느라 실제 처리량은 1~3개 수준까지 떨어졌습니다. 다른 에이전트의 작업을 명시적으로 기다리도록 하는 도구도 제공해 봤지만, 거의 사용하지 않았습니다. 잠금 없는 낙관적 동시성 제어 방식도 시도했지만, 오버헤드는 줄었어도 혼란은 해소되지 않았습니다.
에이전트 사이에 구조가 부족하다는 것은 어떤 단일 에이전트도 크고 복잡한 작업을 맡지 않는다는 뜻이었습니다. 이들은 경합과 충돌을 피하면서, 프로젝트 전체에 대한 책임을 지기보다 더 작고 안전한 변경을 택했습니다.
구조와 역할 추가하기
다음으로, 에이전트에 소유권과 책임감을 부여하기 위해 역할을 분리했습니다.


먼저 Planner가 사용자의 지시를 바탕으로 진전을 이루기 위한 구체적인 접근 방식과 산출물을 정리했습니다. 이 내용은 executor에게 전달되었고, executor는 플랜이 완전히 달성되도록 보장하는 유일한 총괄 에이전트가 되었습니다. executor는 워커를 위한 작업을 생성할 수 있었고, 이를 통해 선형적인 확장성과 처리량을 확보할 수 있었습니다.
지속적인 진행과 책임성을 위해 executor가 작업을 마친 뒤에는 독립적인 judge가 실행되어 작업이 완료되었는지, 그리고 한 번 더 반복을 실행해야 하는지를 판단했습니다. 이는 많은 조정 문제를 해결했습니다. 실행을 전담하고 감독하는 단일 역할을 둠으로써 워커는 각자의 작업에 집중할 수 있었고, 전체 시스템은 계속해서 결과를 만들어낼 수 있었습니다.
관찰과 점진적 개선
이 설계에 이르기까지는 시스템을 면밀히 관찰하는 과정이 필요했습니다.
큰 문제가 있으면 대개 반복적으로, 그리고 여러 에이전트와 도구 호출 전반에서 나타났습니다. 예를 들어 많은 에이전트가 동시에 git restore를 실행하면서 경합이 과도하게 발생한다는 점을 확인했습니다. 저희는 Cursor를 사용해 로그를 분석하고 프롬프트와 비교해, 동작이 기대와 일치하지 않는 이유를 파악했습니다.
결국 이 시스템은 가장 느린 워커에 의해 병목이 생긴다는 사실을 알게 되었습니다. 지나치게 경직되어 있었습니다.
또한 모든 계획을 미리 세우는 방식은 새로운 문제가 발견될 때 시스템이 동적으로 재조정하기 어렵게 만들었습니다. 일부 에이전트는 비생산적인 방향으로 흘러가고, 루프의 다음 반복이 오기 전까지는 스스로 바로잡지 못했습니다.
연속 실행기
다음 버전에서는 독립적인 Planner가 제거되었습니다.
이제 executor는 작업을 생성하는 것뿐만 아니라 목표를 어떻게 달성할지도 플랜할 수 있었습니다. 유일한 에이전트였기 때문에 플랜을 따로 어딘가에 작성할 필요도, 하나의 고정된 플랜만 고수할 필요도, 모든 워커를 기계적으로 기다릴 필요도 없었습니다.
최신성 유지
모든 역할의 에이전트가 장기간에 걸쳐 본래 역할에서 벗어나지 않도록, 최신성 유지 메커니즘을 도입했습니다:
scratchpad.md는 내용을 계속 덧붙이기보다 자주 새로 작성하도록 했습니다.- 개별 에이전트는 컨텍스트 한도에 도달하면 자동으로 요약하도록 했습니다.
- 시스템 프롬프트에 자기 성찰과 정렬 상태를 상기시키는 내용을 추가했습니다.
- 에이전트는 언제든 방향을 전환하고 기존 가정에 의문을 제기하도록 장려했습니다.
이제 시스템은 매우 동적이고 유연해졌습니다. 능동적으로 코드를 살펴보고, 결정을 재검토하고, 워커를 관리하고, 작업을 교차로 처리하며, 최신 정보를 지속적으로 반영할 수 있었습니다. 저희는 에이전트가 지시를 끝까지 꽤 잘 수행한다는 점을 확인했기 때문에, 시스템을 단순하게 유지하기 위해 Judge를 제거했습니다.


비정상적 동작
이러한 개선에도 불구하고, 연속 Executor는 비정상적인 동작을 보이기 시작했습니다. 무작위로 멈춰 있거나, 에이전트 실행을 중단하거나, 해야 할 일을 스스로 처리하거나, 플랜을 세우지 않으려 하거나, 범위가 지나치게 좁은 작업 몇 개 이상은 생성하지 않으려 하거나, 워커의 변경 사항을 제대로 병합하지 못하거나, 너무 이른 시점에 완료되었다고 판단하곤 했습니다.
분석해 보니, 이 시스템에는 플랜 수립, 탐색, 조사, 작업 생성, 워커 확인, 코드 리뷰, 수정 수행, 출력 병합, 그리고 루프가 완료되었는지 판단하는 일까지, 너무 많은 역할과 목표가 한꺼번에 주어지고 있었습니다. 돌이켜보면, 과부하가 걸린 것도 무리가 아니었습니다.
최종 시스템 설계
최종 설계에는 그동안 얻은 모든 교훈이 반영되어 있습니다.
- 루트 플래너는 사용자의 지시 전체 범위를 책임집니다. 현재 상태를 파악하고 목표를 향해 나아갈 수 있도록 구체적이고 집중된 작업을 전달하는 역할을 합니다. 직접 코드를 작성하지는 않습니다. 자신이 만든 작업이 실제로 처리되고 있는지, 또는 누가 맡고 있는지는 알지 못합니다.
- 플래너가 자신의 범위를 더 잘게 나눌 수 있다고 판단하면, 위임된 좁은 범위를 온전히 책임지는 하위 플래너를 생성합니다. 하위 플래너도 같은 방식으로 전적인 책임을 지되, 자신에게 맡겨진 그 범위에 대해서만 책임집니다. 이 구조는 재귀적으로 반복됩니다.
- 워커는 작업을 받아 끝까지 완료할 전적인 책임을 집니다. 더 큰 시스템 전체는 인식하지 못합니다. 다른 플래너나 워커와도 소통하지 않습니다. 각자 자신의 repo 사본에서 작업하고, 작업이 끝나면 시스템이 해당 작업을 요청한 플래너에게 제출할 단일 핸드오프를 작성합니다.
흥미롭게도, 이는 오늘날 일부 소프트웨어 팀이 실제로 운영되는 방식과 닮아 있습니다.


하위 플래너는 전체 시스템이 에이전트의 책임 아래 일관되게 관리되도록 유지하면서도, 워커를 빠르게 확장해 처리량을 높입니다. 또한 단일 플래너라면 과부하에 빠지거나 시야가 지나치게 좁아질 수 있는 대규모 프로젝트와 작업에도 도움이 되었습니다.
핸드오프에는 무엇이 완료되었는지만이 아니라, 중요한 메모, 우려 사항, 변경 사항, 발견 사항, 생각, 피드백도 담깁니다. 플래너는 이를 후속 메시지로 받습니다. 이 덕분에 시스템은 계속해서 움직입니다. 플래너가 "완료"된 상태이더라도 지속적으로 업데이트를 받고, 최신 repo를 pull한 뒤, 계속 계획을 세우고 다음 결정을 내릴 수 있습니다.
모든 에이전트에는 이 메커니즘이 있어, 전역 동기화나 상호 간 통신에 따르는 오버헤드 없이도 시스템이 매우 동적이고 스스로 수렴하는 상태를 유지할 수 있습니다. 또한 정보는 점점 더 넓은 전역적 시야를 가진 책임 주체에게 체인을 따라 위로 전파됩니다.
통합자 제거
원래는 중앙에서 전역적으로 인지하는 품질 관리를 수행하고, 너무 많은 워커가 동시에 push, rebase, 충돌 해결, merge를 시도하면서 생기는 경합을 줄이기 위해 통합자를 추가했습니다.
하지만 이는 곧 명백한 병목이 되었습니다. 수백 명의 워커가 있었고, 모든 작업이 반드시 통과해야 하는 하나의 관문(즉, "불필요한 절차")이 있었습니다. 우리는 prompt 변경도 시도했지만, 결국 이것이 불필요하다고 판단했고 시스템을 단순화하기 위해 제거할 수 있다고 결론 내렸습니다.
처리량과 트레이드오프
이 시스템은 1주일 동안 총 1천만 번의 도구 호출을 처리하며 시간당 약 1,000개의 커밋까지 도달했습니다. 시스템이 가동된 후에는 저희의 개입이 전혀 필요하지 않았습니다.
이 처리량을 달성하기 위해 의도적인 트레이드오프가 있었습니다.
커밋 정확성
모든 커밋마다 100% 정확성을 요구했을 때, 심각한 직렬화와 실제 처리량 저하가 발생했습니다. API 변경이나 오타 같은 사소한 오류 하나만 있어도 시스템 전체가 거의 멈춰 섰습니다. 워커들은 자기 범위를 벗어나 관련 없는 것들까지 고치기 시작했고, 여러 에이전트가 같은 문제를 해결하려고 한꺼번에 달려들면서 서로의 작업을 방해했습니다.
이런 방식은 도움이 되지도, 필요하지도 않았습니다. 어느 정도 여유를 허용하면 에이전트는 다른 문제도 곧 동료 에이전트가 해결할 것이라고 믿고 작업할 수 있습니다. 실제로도 시스템은 코드베이스 전반에서 소유권과 위임이 효과적으로 작동하기 때문에 그렇습니다. 오류는 생기더라도 빠르게 수정됩니다. 오류율은 낮고 일정하게 유지되며, 완전히 깨끗한 상태가 드물 수는 있어도 안정적이고 관리 가능한 수준을 유지하지, 폭증하거나 악화되지는 않습니다.
이는 이상적으로 효율적인 시스템이라면 어느 정도의 오류율을 받아들이는 편이 낫다는 뜻일 수 있습니다. 다만 최종적으로는 에이전트가 정기적으로 스냅샷을 찍고 릴리스 전에 빠르게 마무리 수정하는 최종 "green" 브랜치가 필요합니다.
동기화 오버헤드
때로는 여러 에이전트가 같은 파일을 건드리거나 같은 코드를 리팩터링합니다. 이를 완전히 없애거나 해결책을 지나치게 복잡하게 설계하려 하기보다는, 어느 정도의 일시적인 혼란은 받아들이고 시스템이 짧은 시간 안에 자연스럽게 수렴해 안정되도록 둡니다.
이로 인해 토큰이 조금 더 소모되고 국지적인 경합이 생기지만, 전체 시스템은 더 단순하게 유지됩니다. 즉, 모델을 서로 조율하고 과부하를 주지 않기가 더 쉽고, 관리하고 관찰하기도 더 쉬우며, 마찰은 줄고 전반적인 생산성은 높아집니다. 또한 지나치게 복잡한 접근 방식도 피할 수 있습니다.
인프라에서 얻은 교훈
각 멀티 에이전트 실행은 분산 시스템의 복잡성을 섣불리 도입하지 않기 위해, 시스템 리소스가 넉넉한 독립적인 대형 머신에서 실행되었습니다. 이는 대부분의 실행이 많게는 수백 개의 에이전트까지 늘어났고, 일반적으로 이런 머신의 자원을 충분히 활용하면서도 과도하게 초과하지는 않았기 때문에 적절한 선택이었습니다. 이 아키텍처 덕분에 시스템 메트릭을 더 쉽게 관찰할 수 있었고, 필요할 때 상태를 공유하거나 복사하기도 수월했습니다.
에이전트의 RAM 사용량을 제한한 뒤에는 디스크가 병목 지점이 되었습니다. 특히 모놀리식 프로젝트에서는 수백 개의 에이전트가 동시에 컴파일하면서 빌드 아티팩트에 대해 초당 수 GB에 달하는 읽기 및 쓰기가 발생했습니다. 이는 하네스의 전체 처리량에 상당한 영향을 미쳤고, 흥미로운 교훈을 남겼습니다. 즉, 프로젝트 구조, 아키텍처 결정, 개발자 경험은 token 및 commit 처리량에 영향을 줄 수 있으며, 그 이유는 이상적으로는 생각하고 코딩하는 데 시간이 쓰여야 하지만 실제로는 코드베이스를 다루는 작업(예: 컴파일)이 대부분의 시간을 차지하기 때문입니다.
일반적인 개발 환경에도 제약과 비효율이 있었습니다. 단일 사용자 워크스페이스에서는 합리적이거나 크게 중요하지 않은 것들이, 하나의 머신에서 수백 개의 에이전트가 같은 작업을 할 때는 두드러지게 드러날 수 있습니다. 이를 해결하는 가장 단순한 방법 중 하나는 각 에이전트에 전용 머신을 할당하는 것입니다. 하지만 이런 기본 구성 요소와 도구 일부를 다시 생각하고 재설계하는 것만으로도, 큰 효율 향상을 얻을 수 있는 흥미로운 기회가 많이 있습니다.
예를 들어, Git이나 Cargo 같은 많은 도구는 단순한 동시성 제어 메커니즘으로 공유 잠금을 사용합니다. 데이터베이스 같은 동시성 시스템에서 이미 잘 확립된 메커니즘을 도입하면, 이런 도구들이 멀티 에이전트 시스템에서도 똑같이 잘 작동하도록 만들 수 있을까요? 모든 에이전트는 각자 repo의 복사본을 갖고 있지만, 대부분의 파일과 아티팩트는 동일합니다. 별도의 인프라를 구축하지 않고도, 더 정교한 프로덕션 스토리지 시스템에서 볼 수 있는 단순한 copy-on-write 및 중복 제거 기능을 추가하면, 일반적으로는 "단일 사용자" 시스템인 환경에서도 비슷한 손쉬운 이점을 얻을 수 있을까요?
에이전트에 의도를 명확히 전달하기
이 멀티 에이전트 시스템에 주는 지침은 매우 중요했습니다.
처음에는 이것을 최우선 목표로 두지 않고, 대신 안정적이고 효과적인 하네스를 만드는 데 집중했습니다. 하지만 지침의 중요성은 금방 분명해졌습니다. 본질적으로 우리가 다루고 있던 것은 전형적인 코딩 에이전트였지만, 여기에 투입되는 시간과 연산 자원이 몇 자릿수는 더 컸습니다. 그러면 최적이지 않거나 불명확한 지침을 포함해 모든 것이 증폭됩니다.
초기 지침에 더 많은 시간을 들이는 것은 충분히 합리적입니다. 결국 에이전트는 어디까지나 에이전트입니다. 즉, 사용자의 지침을 엄격하게 따르고, 그 방향으로 밀고 나가며, 설령 그 지침이 좋지 않더라도 스스로 바꾸거나 덮어쓰지 않도록 훈련되어 있습니다.
우리는 연구 프로젝트에서 성공적인 결과를 보고 싶었기 때문에, 프로젝트와 하네스가 발전함에 따라 초기 지침도 바꿔 나갔습니다. 우리는 이 새로운 멀티 에이전트 시스템을 운영하는 법을 배우는 동시에 브라우저를 만드는 법도 배우고 있었고, 부실하거나 충분히 구체적이지 않은 명세가 결과물의 품질에 그대로 반영된다는 점을 확인할 수 있었습니다. 이는 하네스 자체의 문제가 아니었습니다. 하네스는 그저 우리의 지침을 정확히 따르고 있었을 뿐입니다.
브라우저 프로젝트에서 나온 몇 가지 예시입니다.
- 처음에는 지침이 명세 구현과 버그 수정에 초점을 맞췄습니다. "명세 구현" 같은 지시는 충분히 모호해서, 에이전트가 현명하게 우선순위를 정하기보다 거의 쓰이지 않는 난해한 기능을 깊게 파고들곤 했습니다.
- 우리는 사용자 친화적인 범위 안에서 성능에 대한 기대치가 암묵적으로 공유되어 있다고 생각했습니다. 하지만 에이전트가 다른 목표와 함께 성능도 균형 있게 고려하도록 하려면, 명시적인 지침과 강제된 시간 초과 설정이 필요했습니다.
- 시스템의 복잡한 부분에서는 에이전트가 메모리 누수를 일으키거나 교착 상태를 유발하는 코드를 작성할 수 있습니다. 사람이라면 이를 알아차리겠지만, 에이전트에게는 항상 자명하지 않았습니다. 시스템이 문제를 우아하게 복구하고 더 방어적으로 동작하도록 하려면, 명시적인 프로세스 기반 리소스 관리 도구가 필요했습니다.
JavaScript가 없는 단순한 브라우저의 첫 번째 버전은, 완전한 브라우저로 발전시키기에는 적합하지 않은 아키텍처로 수렴했습니다. 이는 초기 명세의 실패였습니다.
마찬가지로, 에이전트에게 이 프로젝트가 처음부터 브라우저를 만드는 일이라고 알려주었음에도, 에이전트는 스스로 구현할 수 있었던 일부 dependencies를 가져오거나, 제대로 된 구현이 진행되는 동안 임시 scaffolding으로 사용했습니다. 이는 지침의 허점이었습니다. 이후 실행에서는 dependencies에 대한 원칙과 사용하면 안 되는 라이브러리를 명시적으로 정리했고, 그 결과 이 문제가 바로잡혔습니다.
그 이후 실행에서는 모놀리식 구조에서 벗어나 많은 독립적인 crate로 대대적인 재구성도 이루어졌습니다. repo는 심각하게 망가진 상태였지만, 멀티 에이전트 시스템은 며칠 만에 작동하는 코드로 수렴했습니다. 이는 시스템이 협업적이고 지능적으로 작업할 수 있는 강력한 역량을 갖추고 있으며, 완전히 망가진 상태에서도 더 악화되거나 멈춰 버리지 않고 계속 버틸 수 있다는 점을 보여주었습니다. 이 실행에서는 컴파일을 기다리는 시간도 훨씬 적었고, 이전보다 몇 배 높은 처리량으로 실행되었습니다.
아키텍처와 지침은 중요합니다. 에이전트는 뛰어난 엔지니어링 역량을 갖추고 있지만, 지침이 좋든 나쁘든 끝까지 따릅니다. 지나치게 좁은 지표와 비구조적인 자유 사이에서 균형을 찾는 일은 까다로웠고, 무엇이 자명한지와 무엇을 명시적으로 언급해야 하는지를 구분하는 일도 마찬가지였습니다.
이 모든 것은 의도를 끌어내고, 명확히 규정하고, 이해하는 일이 얼마나 중요한지를 보여주며, 이런 규모에서는 그 중요성이 더욱 커집니다. 조정 기능과 관측 가능성은 앞으로도 계속 탐구해 볼 만한 흥미로운 연구 분야가 될 것입니다.
프롬프트 최적화
프롬프팅은 발전 과정에서 중요한 부분을 차지했습니다.
저희는 모델이 이미 할 줄 아는 일에는 지시하지 않고, 모르는 것(예: 멀티 에이전트 협업)이나 관련 도메인에 특화된 것(예: 테스트 실행 방법, 배포 파이프라인)에 대해서만 지시하는 편이 더 낫다는 점을 발견했습니다. 모델은 엔지니어링은 잘 알지만 여러분의 특정 codebase와 작업 과정은 모르는, 매우 뛰어난 신입 사원처럼 대하세요.
제약은 지시보다 더 효과적입니다. "TODO 금지, 부분 구현 금지"는 "구현을 끝까지 마무리하는 것을 잊지 마세요"보다 더 잘 작동합니다. 모델은 대체로 기본적으로 올바른 방향으로 행동합니다. 제약은 그 경계를 정해 주는 역할을 합니다.
수준이 높거나 더 깊이 있는 task에는 체크리스트식 사고방식을 피하세요. 의도는 자세히 설명하되, 구체적으로 해야 할 일을 나열하면 모델이 더 넓은 범위보다는 그것들을 달성하는 데 집중하는 경향이 있다는 점을 기억하세요. 또한 목록에 없는 것들의 우선순위를 암묵적으로 낮추게 됩니다. 일반적으로는 모델이 자체적인 판단과 주도성을 발휘하도록 두는 편이 더 낫습니다.
다만 범위의 규모를 논의할 때는 구체적인 숫자와 범위를 제시하는 것이 유용했습니다. "많은 task를 생성하라" 같은 지시는 적은 양을 만들어내는 경향이 있습니다. 보수적인 기본값을 따르고, 안전한 선택을 하면서도, 기술적으로는 여전히 지시를 따르고 있는 셈입니다. "20~100개의 task를 생성하라"는 의도가 더 큰 범위에 있음을 전달하고, 더 야심차게 접근해야 한다는 점을 보여주며, 실제로 훨씬 더 폭넓게 달라진 동작을 관찰할 수 있었습니다.
시스템 설계에서 얻은 교훈
연구를 통해 몇 가지 원칙을 정립했습니다:
- 시스템은 안티프래질해야 합니다. 동시에 실행되는 에이전트 수가 늘어날수록 실패 가능성도 함께 커집니다. 우리 시스템은 개별 에이전트의 실패를 견딜 수 있어야 하며, 다른 에이전트가 이를 복구하거나 다른 접근 방식을 시도할 수 있어야 합니다.
- 가정보다 실증을 우선합니다. 인간 조직이나 기존 시스템 설계를 바탕으로 어떻게 작동해야 하는지 미리 가정하기보다, 데이터와 관찰을 통해 조정하고자 했습니다.
- 처리량을 명시적으로 고려해 설계합니다. 이는 코딩의 다른 측면과 절충하는 것을 의미했습니다. 예를 들어 시스템을 크게 느리게 만드는 100% 완벽한 코드 대신, 최종 정합성 점검 단계를 거쳐야 하더라도 작지만 안정적인 오류율은 받아들이는 것입니다.
이런 시스템은 제대로 만들면 놀라울 만큼 단순해지는 경향이 있지만, 여러 가지 접근 방식을 실제로 살펴보기 전까지는 어떤 단순한 접근 방식이 효과적일지 분명하지 않았습니다. 현재의 시스템 설계는 최소한의 오버헤드로 운영되어 왔으며, 토큰 처리량을 유용한 방식으로 선형 확장할 수 있게 해줍니다. 하네스에는 이후에도 큰 폭의 추가 개선이 필요하지 않았습니다.
결론
취향, 판단, 방향성은 사람이 맡았지만, AI는 이 연구를 빠르게 반복하고 탐구하는 데 있어 강력한 촉진제 역할을 했습니다.
이는 AI를 개발하는 데 AI를 활용하는 "선순환" AI 루프와 어느 정도 닮아 있습니다. 모델과 에이전트, 하네스가 더 발전할수록 그 성과가 다시 스스로를 강화하며 점점 더 빠르게 가속됩니다. 우리는 우리를 빚는 도구를 만듭니다.
이 연구에는 오늘날 일부 소프트웨어 팀의 운영 방식과 맞닿아 있는 시적인 유사성이 있습니다. 이러한 모델은 이런 방식으로 명시적으로 학습되지 않았다는 점에서, 이것이 창발적 행동일 수 있으며 결국 소프트웨어 프로젝트를 구조화하는 올바른 방식일 가능성을 시사합니다.
저희는 앞으로도 극도로 장시간 실행되는 에이전트에 대한 연구를 계속할 것이며, 그 발견 사항은 저희 제품의 미래에 반영될 것입니다.