에이전트를 늘리면 일이 빨라질까. 대부분의 하네스에서는 아니다. 막히는 곳은 에이전트가 아니라, 지휘자다.
Microsoft Research가 9월 22일 공개한 논문 Agensh: Scaling Organizational Intelligence to 1,024 Agents는 지휘자를 없앴다. 오케스트레이터 없이 워커가 스스로 일을 고르고, 서로 알리고, 검증하고, 합친다. 그렇게 에이전트를 1,024개까지 늘렸다. pandoc을 인터넷 없이 6시간 안에 처음부터 다시 만드는 과제에서 최종 테스트 통과율은 33.89%에서 55.06%로 올랐다.
모델은 그대로. 바꾼 건 하네스.
이 글은 두 부분이다. 앞은 논문 스터디 — 구조, 실험, 규모가 커지며 생긴 협업, 그리고 비판적으로 읽을 지점. 뒤는 실무 — 이 패턴을 Claude Code 에이전트 팀과 우리 환경으로 옮기는 규모별 설계, 그리고 바로 쓰는 보드 스크립트·훅·워커 프롬프트.
논문 한눈에
| 항목 | 내용 |
|---|---|
| 제목 | Agensh: Scaling Organizational Intelligence to 1,024 Agents |
| 저자 | Zhihao Zhan, Ting Song, Li Dong, Shaohan Huang, Jianxun Lian, Yan Xia, Furu Wei (Microsoft Research) |
| 공개 | 2026-09-22, arXiv 2609.26781 (기술 보고서, 13쪽) |
| 한 줄 | 중앙 오케스트레이터 없는 자기 조직형 멀티 에이전트 하네스 |
| 핵심 수치 | 5개 과제 평균 19.31% → 28.78% (1→128개, 상대 +49%) · pandoc 33.89% → 55.06% (1→1,024개) |
| 링크 | arXiv · 프로젝트 페이지 (1,024개 에이전트 활동 리플레이 제공) |
| 코드 | 논문에 github.com/microsoft/Agensh 명시 — 9월 29일 확인 기준 아직 비공개(404) |
먼저 알아둘 용어 6개
| 용어 | 이 글에서의 뜻 |
|---|---|
| 하네스 | 모델을 감싸 도구 호출·상태·루프를 관리하는 실행 틀. Claude Code, Copilot CLI, Codex가 모두 하네스다 |
| 오케스트레이터 | 일을 쪼개 워커에게 배분하고 결과를 모으는 중앙 에이전트 |
| 워커 | 실제로 일하는 에이전트. Agensh에서는 모두 같은 프롬프트, 다른 ID |
| CLAIM | "이 일은 내가 한다"는 선언. 배분받는 게 아니라 스스로 선점한다 |
| 공유 컨텍스트 | 워커들이 짧고 검증된 메모를 쌓는 추가 전용(append-only) 게시판 |
| ProgramBench | 참조 프로그램의 실행 파일만 보고 소스를 처음부터 재구현하는 벤치마크. 숨겨진 테스트 통과율로 채점 |
1. 문제 — 워커를 늘려도, 지휘자는 하나다
Codex 서브에이전트, Claude Code 서브에이전트와 에이전트 팀, Copilot /fleet, Kimi Agent Swarm. 지금 쓰이는 멀티 에이전트 하네스는 대부분 오케스트레이터-워커 구조다. 메인 에이전트가 계획하고, 쪼개고, 배분하고, 합친다.
워커가 몇 개일 때는 잘 돈다. 문제는 규모다. 모든 배분과 통합이 한 에이전트의 컨텍스트 창을 지난다. 워커가 100개면 오케스트레이터는 100개의 진행 상황을 읽고 100개의 결과를 합쳐야 한다. 논문은 이 구조의 확장성이 오케스트레이터가 관리·조율할 수 있는 용량에 묶여 있다고 본다. 잘 쪼개려면 오케스트레이터를 따로 학습시켜야 하는 경우도 많다. Kimi Agent Swarm이 그렇다.
Agensh의 답은 단순하다. 지휘자를 없앤다. 대신 모두가 같은 상태를 볼 수 있는 인프라를 둔다.
2. 구조 — 5단계 협업 루프와 인프라 3종
모든 워커는 같은 루프를 동시에, 비동기로 돈다.
- 컨텍스트 수집(Gather) — 사용자 목표, 현재 상태, 동료 진행, 메시지, 쌓인 발견을 읽는다. 무엇이 끝났고 무엇이 남았는지 파악해 다음 할 일을 정한다.
- 서브태스크 클레임(Claim) — 할 일을 스스로 제안하고, 범위를
CLAIM으로 공유 컨텍스트에 올린다. 다른 워커와 겹치면 직접 메시지로 푼다. - 실행(Act) — 도구로 서브태스크를 푼다. 다른 워커에게 도움이 될 발견은 그때그때 공유 컨텍스트에 올린다.
- 검증(Verify) — 로컬 결과를 서브태스크의 수용 기준과 맞춰 본다. 못 넘으면 넘을 때까지 고친다.
- 병합(Merge) — 공유 워크스페이스에 합치고, 무엇을 왜 바꿨고 어떻게 검증했는지 올린다. 충돌로 막히면 최신 상태를 받아 풀고 다시 합친다.
그리고 1단계로 돌아간다. 모두가 한 바퀴를 마칠 때까지 기다리는 동기화 지점은 없다.
루프를 받치는 인프라는 셋이다.
| 인프라 · 논문 구현 | 역할과 설계 포인트 |
|---|---|
| 공유 워크스페이스 Gitea (Git 서버) |
제안·진행·완료된 작업을 담는 파일 시스템. 워커마다 개인 체크아웃·브랜치에서 일하고 main에 병합한다. 이력과 출처가 남고, 텍스트 충돌은 Git이 잡는다. 이슈·PR도 여기서 |
| 메시지 인터페이스 Mattermost (팀 채팅) |
진행 공유, 충돌 해소, 중복 회피. 공용 채널은 루프 시작 때 전달(낮은 우선순위), 1:1 DM은 인프라 도구 호출이 끝날 때마다 전달(높은 우선순위) |
| 공유 컨텍스트 DeLM 방식 변형 |
재사용할 발견과 작업 의도를 보존. 짧고 타입이 붙은 기록만 받는다. 추가 전용 DB, 최근 기록은 프롬프트에 자동 주입, 전체 이력은 grep 도구로 검색 |
공유 컨텍스트는 Agensh에서 가장 옮겨 쓰기 좋은 부품이다. 기록 타입은 다섯 개뿐이다.
| 타입 | 뜻 → 받은 워커의 행동 (워커 프롬프트 기준) |
|---|---|
OBSERVED |
참조 프로그램에서 관찰한 눈에 띄는 동작 → 가져다 쓴다 |
FACT |
확인을 마친 사실 → 가져다 쓴다. 동료의 FACT를 다시 도출하지 않는다 |
FAIL |
기각된 가설, 실패한 접근 → 그 일을 하고 있었다면 멈춘다. 다시 시도하지 않는다 |
CLAIM |
지금 작업 중인 범위 → 내 작업과 겹치면 충돌이다. DM으로 정리한다. 아니면 하던 일을 계속한다 |
PATCH_SUMMARY |
끝낸 변경 요약 (files·idea·evidence 3칸) → main에 무엇이 들어갔는지 확인한다. 병합 전엔 항상 최신 main과 동기화 |
길이도 자른다. 요약은 100자, PATCH_SUMMARY만 300자. 긴 설명은 detail에 넣고 필요한 워커만 펼쳐 본다. 워커 프롬프트는 FAIL을 가장 가치 높은 기록이라고 부른다. 동료가 같은 막다른 길에 예산을 쓰지 않게 막아 주기 때문이다.
3. 구현 — 루프는 코드가 아니라 프롬프트에 있다
Agensh는 단일 에이전트 하네스를 대체하지 않는다. 그 위에 얹힌다. 워커 하나하나는 기존 하네스(실험에서는 GitHub Copilot)가 돌린다. 대화 상태, 모델 호출, 도구 실행은 그대로다. Agensh가 더하는 것은 조직 단위의 행동이다. 워커 ID, 협업 프로토콜, 이벤트 라우팅, 워크스페이스·메시지·컨텍스트 접근, 복구와 생존 확인.
가장 눈여겨볼 선택은 이것이다. 협업 루프를 런타임에 하드코딩하지 않고, 워커 프롬프트의 작업 지시로 구현했다. 1,024개 워커의 프롬프트는 워커 ID 한 줄만 빼고 전부 같다(논문 부록 A에 전문). 그래서 가벼운 어댑터만 붙이면 Claude Code나 Copilot 같은 다른 하네스에 그대로 연결된다.
런타임은 이벤트 기반이다(부록 B).
- 라우터: Gitea의 저장소 활동과 Mattermost 메시지(실시간 연결 + 재연결 시 누락분 회수)를 듣고, 워커별 내구성 큐에 적재한다. 이벤트마다 고유 ID로 중복을 거른다.
- 디스패처: 큐의 이벤트를 묶어 다음 프롬프트로 만들고 워커의 지속 세션에 넣는다. 턴 도중 도착한 이벤트는 다음 턴을 기다린다. 전달 실패는 점점 긴 간격으로 재시도하고, 라우터가 재시작되면 미완료 이벤트는 큐로 돌아간다.
- 유휴 감지기: 워커가 10분간 놀면 계속 일하라는 프롬프트를 보낸다. 턴이 끝났는데 공유 컨텍스트 기록이 하나도 없으면 발견을 올리라고 다시 알린다.
- 공유 컨텍스트 전달 경로 2개: 새 턴 시작 때 프롬프트에 붙이거나, 턴 도중엔 인프라 도구(MCP) 호출 결과 뒤에 덧붙인다. 실행 중인 프로세스를 끊지는 않는다.
마지막 항목이 실무에서 가장 중요하다. 메시지를 끼워 넣는 타이밍이 곧 협업의 품질이다. 너무 자주 끊으면 일을 못 하고, 너무 늦으면 같은 일을 두 번 한다. Agensh는 급한 것(DM, 새 보드 기록)은 도구 호출 결과에 얹고, 나머지는 턴 경계로 미뤘다.
4. 실험 — 무엇을, 어떻게 쟀나
과제는 ProgramBench다. 참조 프로그램의 실행 파일만 주고, 인터넷 없이 6시간 안에 소스를 처음부터 다시 만들게 한다. 바이너리는 읽을 수 없고(디컴파일 금지), 실행해서 동작을 관찰하는 것만 허용된다. 채점은 숨겨진 테스트 통과율. ProgramBench 확장 결과 200개 과제 중 최신 모델 평균 통과율이 가장 낮은 5개를 골랐다.
| 과제 | 분야 | 파일 수 | 코드 줄 수 |
|---|---|---|---|
| FFmpeg | 멀티미디어 처리 | 10,090 | 1,549,005 |
| gromacs | 분자 시뮬레이션 | 8,982 | 815,539 |
| pandoc | 문서 변환 | 2,767 | 104,336 |
| PHP-src | 언어 인터프리터 | 26,266 | 2,812,757 |
| ctags | 코드 인덱싱 | 7,313 | 246,228 |
통제는 엄격하다. 모델은 모든 설정에서 GPT-5.6-sol(high reasoning), 단일 에이전트 하네스는 Copilot(입력 272K·출력 128K 토큰 한도), 워커 프로토콜과 평가 설정도 같다. 바뀌는 것은 에이전트 수뿐이다. 1,024개 실험은 노드 16대에 노드당 64개씩 나눠 돌렸다.
5. 결과 — 에이전트 수는 새로운 스케일링 축이다
5-1. 최종 점수
5개 과제 평균은 1개 19.31%, 8개 20.68%, 32개 26.52%, 128개 28.78%. 1→128개에서 +9.47pp, 상대 약 49% 향상이다. pandoc은 1,024개까지 늘렸다.
과제별로 보면 그림이 달라진다.
| 과제 | 1개 | 8개 | 32개 | 128개 |
|---|---|---|---|---|
| FFmpeg | 6.54 | 11.97 | 13.22 | 12.89 |
| gromacs | 19.69 | 19.37 | 19.37 | 22.12 |
| pandoc | 33.89 | 41.75 | 43.11 | 50.94 |
| PHP-src | 10.63 | 11.86 | 12.23 | 12.35 |
| ctags | 25.82 | 18.47 | 44.69 | 45.62 |
| 5개 평균 | 19.31 | 20.68 | 26.52 | 28.78 |
단위 %, 논문 그림 5 기준. pandoc 1,024개는 55.06%.
5-2. 같은 점수에 닿는 시간
실무에서는 이쪽이 더 중요할 수 있다. 첫 2시간을 30분 간격으로 보면, 큰 조직이 같은 점수에 더 빨리 닿는다. pandoc에서 128개는 30분 체크포인트에 이미 30%를 넘었다. 32개는 60분, 8개는 90분에 처음 넘었다. 1개는 2시간 내내 30% 아래였다. 마감이 정해진 일이라면 최종 점수보다 이 곡선을 봐야 한다.
6. 규모가 커지자 생긴 협업 — 아무도 시키지 않았다
모든 워커는 같은 루프, 같은 프롬프트를 받는다. 역할도, 리뷰어도, 통합 절차도 정해 주지 않았다. 그런데 궤적을 보면 규모마다 다른 협업이 나타난다.
| 규모 · 협업 | 논문 속 장면 |
|---|---|
| 8개 동료 조율 |
gromacs: 모듈 인터페이스를 먼저 공지하고, 각자 그 인터페이스에 맞춰 명령 모듈을 구현. FFmpeg: 겹침을 대화로 확인한 워커가 범위를 바꿔 보완 작업으로 이동 |
| 32개 다중 워커 통합 |
PHP-src: 여러 동료가 승인한 기여에 한 워커가 반례를 제시 → 승인 철회 → 작성자 수정 → 재리뷰 → 동료가 병합 |
| 128개 전문화·표준화 |
이전 경험을 기준으로 리뷰어를 고르고 그 관계를 재사용. 통합 책임을 충돌 해결·검증·병합을 맡는 동료에게 넘김. pandoc: 두 워커가 만든 통합 프로토콜(작성자가 브랜치 갱신·테스트 → 커밋 해시 전달 → 동료가 검증·병합)을 다른 워커들이 재사용. 실패 후엔 동료에게 갱신·테스트·검사·병합 전 과정을 맡기는 방식으로 개정 |
| 1,024개 역할 분화 |
pandoc: 여러 워커가 통합 담당으로 자리 잡음. 후보 통합자 여럿에게 연락해 가장 먼저 응답한 유효한 사람을 고르고 나머지 요청은 취소. 같은 영역 전문 워커가 실패한 작업을 이어받아 특정 워커 의존을 줄임 |
협업의 범위가 규모를 따라 넓어진다. 구현을 맞추는 데서 시작해, 통합을 관리하고, 절차를 표준화하고, 역할을 나눈다. 사람 조직이 커지며 겪는 순서와 닮았다. 다만 이 부분은 궤적에서 고른 사례 관찰이다. 몇 번이나, 얼마나 일관되게 나타났는지는 수치로 제시되지 않았다.
7. 비판적으로 읽기 — 스터디에서 짚을 6가지
좋은 논문일수록 한계를 같이 읽어야 한다. 사내 스터디라면 이 여섯 가지는 꼭 짚자.
- 비용이 없다. 1,024개 × 6시간의 토큰 사용량과 비용을 논문은 밝히지 않는다. 비용은 에이전트 수에 비례해 느는데, 성능은 로그 스케일로 오른다. pandoc에서 128→1,024개(8배)는 +4.12pp다.
- 단조 증가가 아니다. ctags는 8개에서 25.82%→18.47%로 오히려 떨어졌고, FFmpeg는 32개(13.22%)가 128개(12.89%)보다 높다. gromacs는 32개까지 제자리다. 에이전트가 늘면 조율 비용도 는다.
- 반복 실행이 보이지 않는다. 과제·규모별 결과가 한 번씩 제시되고, 반복 횟수나 분산은 본문에 없다. ctags의 출렁임이 구조 탓인지 운인지 가릴 수 없다.
- 조건이 좁다. 모델 하나(GPT-5.6-sol), 하네스 하나(Copilot), 벤치마크 하나(ProgramBench). 1,024개는 pandoc 한 과제뿐이다.
- 절대 수준은 아직 낮다. 128개 평균 28.78%. 가장 어려운 과제를 고른 탓이지만, 재구현이 끝났다는 뜻은 아니다.
- 이 과제는 멀티 에이전트에 유리하다. 숨겨진 테스트가 많고, 참조 바이너리라는 완벽한 검증기가 있고, 인터넷이 막혀 있다. 일이 잘게 쪼개지고, 각자 맞았는지 스스로 확인할 수 있는 환경이다. 검증기가 없는 일에도 같은 곡선이 나올지는 별개의 질문이다.
그럼에도 기여는 분명하다. 모델과 하네스를 고정하고 에이전트 수만 바꿔도 품질과 속도가 함께 오른다는 것, 그리고 그 확장을 막는 것이 오케스트레이터라는 것을 1,024개 규모로 보였다.
8. 실무 적용 ① — 언제 쓰고, 언제 쓰지 말까
멀티 에이전트는 공짜가 아니다. Claude Code 공식 문서도 에이전트 팀이 단일 세션보다 토큰을 훨씬 많이 쓰고, 순차 작업·같은 파일 수정·의존성이 많은 일에는 단일 세션이 낫다고 적어 두었다. Agensh와 Anthropic의 C 컴파일러 실험(Claude 16개, git + 락 파일, 오케스트레이터 없음)을 겹쳐 보면 조건은 넷으로 좁혀진다.
| 조건 | 왜 필요한가 → 없으면 |
|---|---|
| 자동 검증기 | 워커가 "끝났다"를 스스로 판단해야 한다. Agensh는 참조 실행 파일, C 컴파일러는 테스트 스위트와 GCC. → 없으면 틀린 문제를 열심히 푼다 |
| 쪼개지는 일 | 실패한 테스트가 여럿이면 각자 하나씩 고른다. → 없으면 C 컴파일러 실험의 리눅스 커널 빌드처럼 된다. 한 덩어리 과제에서 16개가 같은 버그를 고치며 서로 덮어썼다 |
| 시간 제약 | Agensh의 진짜 가치는 같은 점수에 더 빨리 닿는 것. → 시간이 넉넉하면 단일 에이전트 장기 실행이 싸다 |
| 토큰 예산 | 비용은 에이전트 수에 비례한다. → 없으면 품질 없이 비용만 는다 |
C 컴파일러 실험의 해법도 기억해 둘 만하다. 커널처럼 한 덩어리인 일은 GCC를 정답 오라클로 삼았다. 대부분의 파일은 GCC로, 일부만 자체 컴파일러로 빌드해 문제 파일을 좁혔다. 그러자 에이전트마다 다른 파일의 다른 버그를 고칠 수 있었다. 쪼갤 수 없는 일을 쪼개는 것도 하네스 설계의 몫이다.
9. 실무 적용 ② — 규모별 설계 3단계
| 단계 · 규모 | 구성 → 적합한 일 |
|---|---|
| 1단계 3~5개 |
Claude Code 에이전트 팀 + 공유 보드 + 훅 → 병렬 리뷰, 경쟁 가설 디버깅, 레이어별 기능 개발 |
| 2단계 8~16개 |
컨테이너별 클론 + git 업스트림 + 공유 보드 + 무한 루프 → 테스트가 많은 재구현·마이그레이션, 대규모 테스트 수리 |
| 3단계 32개 이상 |
Agensh형: Git 서버(PR·이슈) + 팀 채팅(채널·DM) + 보드 서비스 + 이벤트 라우터 → 검증기가 확실한 장기 대형 과제 |
하네스 계층 전체를 설계하는 법은 AI 에이전트 하네스 엔지니어링 6계층 가이드, 서브에이전트와의 경계는 서브에이전트는 경계가 먼저다에 정리해 두었다.
1단계 — Claude Code 에이전트 팀에 보드 붙이기
Claude Code 에이전트 팀은 이미 Agensh 패턴의 절반을 갖고 있다. 공유 태스크 리스트에서 팀원이 스스로 다음 일을 클레임하고(파일 락으로 경합 방지), 팀원끼리 직접 메시지를 보낸다. 다른 점은 둘이다. 리더 세션이 고정돼 있고, 공유 컨텍스트 보드가 없다. 논문도 Claude Code 에이전트 팀을 "고정된 리더 세션 중심"으로 분류한다.
| Agensh 부품 | Claude Code 에이전트 팀에서 |
|---|---|
| 공유 워크스페이스 | 같은 저장소. 팀원마다 담당 파일을 나눠 덮어쓰기 방지 |
| 메시지 인터페이스 | 팀원 간 직접 메시지(메일박스) |
| CLAIM | 공유 태스크 리스트의 셀프 클레임 |
| 공유 컨텍스트 | 없음 → 아래 board.py로 추가 |
| 검증 후 병합 | TaskCompleted 훅: 테스트를 못 넘으면 완료 거부 |
| 유휴 감지기 | TeammateIdle 훅: 마감 전이면 계속 일하게 |
에이전트 팀은 실험 기능이라 직접 켜야 한다. 훅 두 개와 함께 프로젝트의 .claude/settings.json에 넣는다.
{
"env": { "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1" },
"hooks": {
"TeammateIdle": [
{ "hooks": [ { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/teammate-idle.sh" } ] }
],
"TaskCompleted": [
{ "hooks": [ { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/task-completed.sh" } ] }
]
}
}
공유 컨텍스트 보드 — board.py. 논문의 5타입·길이 제한·AND/OR grep을 파일 하나로 옮긴 개념 구현이다. 동시 쓰기는 파일 락으로 직렬화한다. 50개 프로세스 동시 쓰기로 돌려 깨진 줄이 없는 것을 확인했다. 논문 코드가 아니라, 공개된 설계를 바탕으로 우리가 재구성한 예시다.
#!/usr/bin/env python3
"""board.py — append-only 공유 컨텍스트 보드 (Agensh 패턴 개념 구현)"""
import argparse, fcntl, json, os, sys, time
BOARD = os.environ.get("BOARD_PATH", ".team/board.jsonl")
ME = os.environ.get("WORKER_ID", "unknown")
TYPES = ["OBSERVED", "FACT", "FAIL", "CLAIM", "PATCH_SUMMARY"]
CAP = {"PATCH_SUMMARY": 300} # 나머지는 100자
def load():
if not os.path.exists(BOARD):
return []
with open(BOARD, encoding="utf-8") as f:
return [json.loads(line) for line in f if line.strip()]
def show(e, detail=False):
print(f'{e["ts"]} [{e["type"]}] {e["by"]}: {e["summary"]}')
if detail and e.get("detail"):
print(" " + e["detail"].replace("\n", "\n "))
def cmd_write(a):
t = a.type.upper()
if t not in TYPES:
sys.exit(f"type은 {TYPES} 중 하나")
now = time.time()
entry = {"ts": time.strftime("%H:%M:%S", time.localtime(now)), "epoch": int(now),
"by": a.by, "type": t, "summary": a.summary[:CAP.get(t, 100)], "detail": a.detail}
os.makedirs(os.path.dirname(BOARD) or ".", exist_ok=True)
with open(BOARD, "a", encoding="utf-8") as f:
fcntl.flock(f, fcntl.LOCK_EX) # 동시 쓰기 직렬화
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
def cmd_read(a):
rows = [e for e in load() if not a.type or e["type"] == a.type.upper()]
for e in rows[-a.last:]:
show(e, a.detail)
def cmd_grep(a):
# "a&b,c&d" = (a AND b) OR (c AND d), 대소문자 무시
groups = [[k.strip().lower() for k in g.split("&") if k.strip()] for g in a.query.split(",")]
for e in load():
text = f'{e["type"]} {e["by"]} {e["summary"]} {e.get("detail") or ""}'.lower()
if any(g and all(k in text for k in g) for g in groups):
show(e, a.detail)
p = argparse.ArgumentParser()
sub = p.add_subparsers(dest="cmd", required=True)
w = sub.add_parser("write"); w.add_argument("type"); w.add_argument("summary")
w.add_argument("--detail"); w.add_argument("--by", default=ME); w.set_defaults(fn=cmd_write)
r = sub.add_parser("read"); r.add_argument("--last", type=int, default=2000)
r.add_argument("--type"); r.add_argument("--detail", action="store_true"); r.set_defaults(fn=cmd_read)
g = sub.add_parser("grep"); g.add_argument("query")
g.add_argument("--detail", action="store_true"); g.set_defaults(fn=cmd_grep)
a = p.parse_args(); a.fn(a)
.team/board.py로 저장하고 이렇게 쓴다. --by에는 팀원 이름을 넣는다.
python3 .team/board.py write CLAIM "parser: if문·while문 파싱" --by parser-dev
python3 .team/board.py write FAIL "정규식으로 중첩 인용부호 처리 → 2단계부터 깨짐" --by parser-dev --detail "재현 입력: ..."
python3 .team/board.py read --type FAIL # 시작 전에 막다른 길부터 확인
python3 .team/board.py grep "parser&fail,lexer" # (parser AND fail) OR lexer
TeammateIdle 훅 — 논문의 유휴 감지기. 마감 전에는 쉬지 못하게 하고, 최근 보드 기록이 없으면 먼저 남기게 한다. 마감 시각은 반드시 둔다. 없으면 팀원이 끝없이 토큰을 쓴다.
#!/bin/bash
# .claude/hooks/teammate-idle.sh — 마감 전에는 쉬지 못하게 한다
cd "${CLAUDE_PROJECT_DIR:-.}"
INPUT=$(cat)
NAME=$(echo "$INPUT" | jq -r '.teammate_name')
BOARD=.team/board.jsonl
# 마감 시각(epoch)이 지났거나 파일이 없으면 쉬어도 된다 — 무한 루프 방지
DEADLINE=$(cat .team/deadline 2>/dev/null || echo 0)
[ "$(date +%s)" -ge "$DEADLINE" ] && exit 0
# 최근 15분간 보드 기록이 없으면 먼저 남기게 한다
SINCE=$(( $(date +%s) - 900 ))
touch "$BOARD"
RECENT=$(jq -s --arg n "$NAME" --argjson t "$SINCE" \
'[.[] | select(.by==$n and .epoch>=$t)] | length' "$BOARD")
if [ "$RECENT" -eq 0 ]; then
echo "최근 15분간 보드 기록이 없다. 확인한 사실(FACT), 실패한 시도(FAIL), 끝낸 작업(PATCH_SUMMARY) 중 하나를 board.py로 남겨라." >&2
exit 2
fi
echo "할 일은 항상 남아 있다. 이전 CLAIM 범위에 갇히지 말고, 아직 시도하지 않은 입력으로 기준과 비교해 격차를 찾아라. 새 작업은 board.py read로 확인한 뒤 CLAIM부터." >&2
exit 2
마감은 파일 하나로 건다. 2시간이면 echo $(( $(date +%s) + 7200 )) > .team/deadline.
TaskCompleted 훅 — 검증 없는 완료 거부. 루프의 4단계를 코드로 강제한다.
#!/bin/bash
# .claude/hooks/task-completed.sh — 테스트를 못 넘으면 완료 불가
cd "${CLAUDE_PROJECT_DIR:-.}"
SUBJECT=$(jq -r '.task_subject')
LOG=$(mktemp)
if ! ./scripts/test-fast.sh > "$LOG" 2>&1; then
echo "검증 실패로 '$SUBJECT' 완료 불가. 실패 요약:" >&2
grep -m 5 "ERROR" "$LOG" >&2 # 컨텍스트를 아끼려고 핵심 줄만 돌려준다
exit 2
fi
exit 0
test-fast.sh는 프로젝트의 빠른 테스트로 바꾼다. C 컴파일러 실험의 조언대로 테스트 출력은 짧게, 오류는 ERROR로 시작하는 한 줄로 남겨야 에이전트가 grep으로 찾는다. 두 훅 모두 chmod +x를 잊지 말 것. jq가 필요하다.
팀은 이렇게 띄운다. 리더가 배분하지 않게 하는 게 핵심이다.
에이전트 팀을 만들어 줘. 팀원 4명, 이름은 parser-dev, codegen-dev, runtime-dev, test-dev.
각자 담당 디렉터리만 수정한다(parser/, codegen/, runtime/, tests/).
모든 팀원은 .team/WORKER_PROMPT.md를 먼저 읽고 그 작업 순환을 따른다.
board.py에 쓸 때 --by에는 자기 팀원 이름을 넣는다.
리더는 태스크를 잘게 만들어 두기만 하고, 배분하지 말고 팀원이 스스로 클레임하게 둔다.
2단계 — 8~16개: 컨테이너와 git으로
에이전트 팀은 세션당 팀 하나, 리더 고정, 중첩 불가다. 8개를 넘기면 C 컴파일러 실험 방식이 현실적이다. 워커 하나 = 컨테이너 하나 = 무한 루프. 저장소는 공유 볼륨의 bare 저장소를 업스트림으로 쓰고, 보드 파일도 공유 볼륨에 둔다.
#!/bin/bash
# run-worker.sh <번호> — 반드시 컨테이너 안에서 실행 (개념 예시)
ID=$1
git clone -q /upstream /workspace && cd /workspace
export WORKER_ID="worker-$ID" BOARD_PATH=/shared/board.jsonl
while [ "$(date +%s)" -lt "$(cat /shared/deadline)" ]; do
# 매 세션은 최신 main에서 출발한다. 끝내지 못한 작업은 버린다
# → 그래서 서브태스크는 한 세션 안에 끝날 크기로 CLAIM한다
git fetch -q origin && git reset -q --hard origin/main
claude -p "$(cat /shared/WORKER_PROMPT.md)" --dangerously-skip-permissions \
> "/shared/logs/${WORKER_ID}-$(date +%s).log" 2>&1
done
--dangerously-skip-permissions는 격리된 컨테이너 밖에서 절대 쓰지 않는다. 띄울 때는 논문처럼 시차를 둔다. 한꺼번에 켜면 모두 같은 일을 클레임한다.
for i in $(seq 1 16); do
docker run -d --name "worker-$i" -v upstream:/upstream -v shared:/shared agent-image ./run-worker.sh "$i"
sleep 30
done
이 단계에는 채팅이 없다. 충돌은 보드의 CLAIM으로만 조정한다. 규칙은 하나면 된다 — 겹치면 늦게 CLAIM한 쪽이 비킨다.
3단계 — 32개 이상: Agensh 구성 그대로
| 부품 · 논문 선택 | 필요한 기능 |
|---|---|
| Git 서버 Gitea |
PR·이슈로 제안·진행·완료 작업을 드러낸다. 저장소 이벤트를 밖으로 알린다 |
| 팀 채팅 Mattermost |
공용 채널과 1:1 DM. 우선순위별로 전달 시점을 나눈다 |
| 보드 서비스 DeLM 변형 |
추가 전용 DB, 최근 기록 자동 주입, 전체 이력 grep |
| 이벤트 라우터 자체 구현 |
워커별 내구성 큐, 중복 제거, 재시도, 재시작 복구, 유휴 감지 |
| 실행 인프라 노드 16대 |
노드당 워커 64개 |
이 단계는 직접 짓기보다 논문 코드 공개를 기다렸다가 하네스 어댑터만 붙이는 편이 싸다.
10. 워커 프롬프트 템플릿
논문 부록 A 워커 프롬프트의 구조(위치 → 팀 → 목표 → 철칙 → 이벤트 처리 → 작업 순환)를 따라 우리가 재구성한 범용 템플릿이다. .team/WORKER_PROMPT.md로 저장하고, 모든 워커에 같은 프롬프트를 준다. 이름만 다르다.
# 너는 {WORKER_ID}다
## 어디서 일하나
- 코드: git 저장소. main이 공용 결과물이다. 너는 네 영역에서 일하고 main에 병합한다.
- 보드: .team/board.py — 모든 팀원이 쓰는 짧은 검증된 기록. 시작 전에 반드시 읽는다.
- 메시지: 다른 팀원과 같은 곳을 건드리게 되면 그 팀원에게 직접 알린다.
(메시지 수단이 없으면 늦게 CLAIM한 쪽이 범위를 바꾼다.)
## 팀
{N}명, 모두 동등하다. 배분하는 사람은 없다.
무엇을 만들지 네가 고르고, 만들고, 병합하고, 다음을 고른다.
## 목표
{목표 한 문단}
정답 기준은 {검증기: 테스트 명령 또는 참조 프로그램}이다.
## 철칙
1. 지시를 기다리지 않는다. "다음에 뭘 하죠?"라고 묻지 않는다. 답은 늘 "일을 앞으로 민다"이다.
2. 보드의 FAIL은 다시 시도하지 않는다. 동료의 FACT는 다시 확인하지 않는다.
3. 검증하지 않은 코드는 main에 넣지 않는다.
## 보드 기록 규칙
- OBSERVED: 기준에서 관찰한 눈에 띄는 동작
- FACT: 확인을 마친 사실
- FAIL: 틀린 것으로 확인된 가설·접근 — 가장 가치 높은 기록이다
- CLAIM: 지금 맡은 범위 — 시작할 때 올린다
- PATCH_SUMMARY: 끝낸 변경 — "files= | idea= | evidence=" 형식
요약은 100자(PATCH_SUMMARY는 300자). 긴 내용은 --detail에 넣는다.
## 작업 도중 보드 기록이 도착하면
- FAIL: 네가 하던 일이면 멈춘다
- FACT / OBSERVED: 가져다 쓴다
- 네 작업과 겹치는 CLAIM: 충돌이다. 직접 알려 정리한다
- 그 밖의 CLAIM: 하던 일을 계속한다
## 작업 순환
1. 기준을 실행하고 보드를 읽어, 아직 아무도 손대지 않은 영역을 찾는다
2. 맡을 영역을 CLAIM으로 올린다
3. 최신 main 위에서 구현한다
4. 기준과 같은 입력으로 비교해 검증한다
5. main에 병합한다. 거부되면 최신 main을 받아 다시 검증하고 다시 병합한다
6. PATCH_SUMMARY를 올리고 1로 돌아간다
11. 운영 장치 — 논문 부록 C의 디테일
| 장치 | 논문 설정 → 이유 |
|---|---|
| 시차 가동 | 첫 1시간은 30초마다 1개, 이후 3초마다 1개. → 한꺼번에 켜면 모두 같은 범위를 노린다(scope contention) |
| T−45분 알림 | 새 기능 중단, 루트 빌드 확인, 열린 PR 병합. 병합 못 하면 그렇다고 말하고 넘어가라. → 마감 직전 반쯤 된 기능이 main을 깨는 것을 막는다 |
| T−5분 알림 | 준비된 것만 병합하고 정지. main에서 빌드·실행 확인 후 최종 상태 공유. → 제출물이 빌드되는 상태로 끝나게 한다 |
| 유휴 10분 | 이전 CLAIM 범위에 갇히지 말고, 참조를 새 입력으로 돌려 격차를 좁히라는 프롬프트. → "다 했다"고 착각해 멈춘 워커를 다시 깨운다 |
| 단일용 stop 훅 | 이전 계획 범위에 갇히지 말고 새 입력에서 할 일을 찾으라는 프롬프트. → 단일 에이전트는 6시간을 스스로 버티지 못한다. 비교를 공정하게 하려는 장치 |
마지막 줄이 실무 팁이다. 에이전트는 혼자 두면 일찍 멈춘다. 멀티든 싱글이든, 장기 실행에는 "할 일은 늘 남아 있다"는 재점화 장치가 필요하다. Claude Code라면 단일 세션은 Stop 훅, 팀원은 TeammateIdle 훅이 그 자리다. 그리고 재점화에는 언제나 마감이 짝으로 붙어야 한다.
12. 이번 주에 해볼 것 — 5가지
- 검증기부터 만든다. 에이전트를 늘리기 전에, 에이전트가 "맞았다"를 스스로 확인할 명령 하나를 만든다. 출력은 짧게, 오류는 ERROR 한 줄로.
- 3명 팀으로 시작한다. Claude Code 에이전트 팀으로, 병렬 리뷰나 경쟁 가설 디버깅처럼 코드를 쓰지 않는 일부터. 공식 문서 권장도 3~5명이다.
- 보드를 붙인다. board.py를 저장소에 넣고, 팀원에게 시작 전
read --type FAIL을 시킨다. 가장 먼저 줄어드는 것은 같은 실수의 반복이다. - 완료 게이트를 건다. TaskCompleted 훅으로 테스트를 못 넘은 완료를 막는다.
- 곡선을 잰다. 1명·3명·5명으로 같은 과제를 돌려, 최종 점수와 목표 점수 도달 시간을 둘 다 기록한다. Agensh가 본 것도 이 두 곡선이다.
사내 논문 스터디용 토론 질문
- 오케스트레이터가 없으면 "무엇을 할지"는 누가 정하나? 목표와 검증기가 그 역할을 대신한다는 설계에 동의하는가?
- ctags는 8개에서 점수가 떨어졌다가 32개에서 크게 올랐다. 무엇이 이런 비선형을 만들까?
- pandoc 128→1,024개는 8배 인원에 +4.12pp다. 우리 업무라면 비용 대비 합리적인 규모는 어디인가?
- 우리 업무 중 "참조 실행 파일"에 해당하는 검증기가 있는 일은 무엇인가? 없다면 어떻게 만들 수 있나?
- FAIL이 가장 가치 높은 기록인 이유는? 사람 팀의 회고·장애 기록과 무엇이 같고 무엇이 다른가?
사람과 에이전트가 한 팀으로 일하는 원칙은 AI 에이전트를 팀원으로 쓰는 법에서 이어 읽을 수 있다.
지휘자는 없어도 된다. 검증기는 없으면 안 된다
더 큰 모델을 기다리는 길이 있다. 더 많은 에이전트를 일하게 하는 길도 있다. Agensh는 두 번째 길의 조건을 보여 줬다. 모두가 같은 상태를 본다. 스스로 일을 고른다. 검증하고, 합친다. 지휘자는 빠져도 된다. 빠지면 안 되는 것은 "맞았다"를 판정해 줄 기준이다.
AX LABS는 이런 에이전트 협업 구조를 실제 업무에 붙이는 일을 한다. 사내 에이전트의 협업 구조와 검증 설계가 필요하다면 — AI 에이전트 개발 →
참고
- Zhan et al., "Agensh: Scaling Organizational Intelligence to 1,024 Agents", arXiv 2609.26781, 2026-09-22: https://arxiv.org/abs/2609.26781
- Agensh 프로젝트 페이지: https://aka.ms/Agensh
- Mao & Mirhoseini, "Decentralized Multi-Agent Systems with Shared Context" (DeLM), arXiv 2606.10662: https://arxiv.org/abs/2606.10662
- Yang et al., "ProgramBench: Can language models rebuild programs from scratch?", arXiv 2605.03546: https://arxiv.org/abs/2605.03546
- Nicholas Carlini, "Building a C compiler with a team of parallel Claudes", Anthropic Engineering, 2026-02-05: https://www.anthropic.com/engineering/building-c-compiler
- Claude Code Docs, "Orchestrate teams of Claude Code sessions": https://code.claude.com/docs/en/agent-teams
- Claude Code Docs, "Hooks reference": https://code.claude.com/docs/en/hooks


