멀티 에이전트 비교 실습: 왜 나누고, 무엇으로 판단할까
전달받은 학습자료로 단일 에이전트·워크플로·오케스트레이션을 비교하고, 분업의 이득과 비용을 실행 기록으로 판단한 학습일지입니다.
시간이 지나서 나중에 보더라도 이해가 안 가면 이 내용은 삭제하겠다.
이 글은 내가 실험 방법을 새로 고안한 기록이 아니다. 전달받은 D35 학습자료와 참고코드로 실험을 실행하며 배운 과정을 정리했다. 내 요청에 따라 Codex가 실행·판정 스크립트를 작성하고 명령을 실행했다. 버그 수정은 Claude Agent SDK의 Haiku가, 행동 판정은 Sonnet이 수행했다.
왜 이 테스트를 했나
에이전트를 여러 개 쓰면 더 잘할 것 같지만, 역할을 나누는 것 자체가 성능 향상을 보장하지 않는다. 탐색에 집중하게 할 수 있는 대신, 보고서를 전달하고 결과를 확인하는 호출·토큰·시간이 추가된다. 서로 같은 일을 하거나 잘못된 보고를 다음 담당이 받아들일 수도 있다.
그래서 이번 실습에서는 같은 버그를 세 방식으로 풀고, 일을 나눠 얻은 이득이 추가 비용보다 컸는지 확인했다. 비교 대상은 에이전트 수뿐 아니라 다음 행동을 누가 결정하는가다.
| 방식 | 진행 흐름 | 다음 행동을 정하는 주체 | 턴 예산 |
|---|---|---|---|
| A: 기준선 | 한 에이전트가 탐색·수정 | 에이전트 하나 | 최대 25턴 |
| B: 워크플로 | locator 탐색 → fixer 수정 | 파이썬 코드 | 12턴 + 18턴 |
| C: 오케스트레이션 | 리드가 직접 처리하거나 locator 호출 | 리드 모델 | 리드 최대 40턴 |
A가 있어야 B·C의 결과가 분업 덕분인지, 원래 혼자서도 풀 수 있었는지 비교할 수 있다. B는 두 담당이 순서대로 실행되므로 병렬 처리의 속도 이점을 시험한 구성은 아니다. 역할을 분리하는 효과를 살펴본 것이다.
어떻게 비교했나
실습 저장소의 SymPy 문제 세 개를 사용했다.
- 24152: 텐서곱 전개에서 두 번째 인수까지 전개되지 않는 문제.
- 24213: 같은 차원인 항을 더할 때 오류가 나는 문제.
- 24909:
milli * W가1이 되어 단위가 사라지는 문제.
| 구분 | 설정 |
|---|---|
| 고정 | 같은 문제, 모델 별칭 haiku, 문제별 기준 커밋, 새 체크아웃, 채점 패치·테스트 |
| 변경 | A·B·C 조율 방식과 턴 예산 |
| 측정 | 해결 여부, 규칙 위반, 비용, 턴, 호출 시간, 행동 점수 |
전달받은 참고코드.md의 공통 설정과 A·B·C 본문은 함수에 넣는 들여쓰기 외에는 그대로 사용했다. arms.py는 실행과 기록, common.py는 저장소 준비와 채점을 담당한다. 각 실행은 info.json의 기준 커밋에서 새로 시작해 다른 실행의 수정이 섞이지 않게 했다.
1
2
3
4
5
# 한 문제를 A 방식으로 실행
.venv/bin/python arms.py A sympy__sympy-24909
# 세 문제를 세 방식으로 실행
.venv/bin/python arms.py A,B,C
문제 이름은 info.json의 instance_id다. result.json이 이미 있으면 건너뛴다. 전체 실행에서는 먼저 완료한 24909의 세 결과를 유지하고 나머지 여섯 건을 실행했다.
Python 3.11 가상환경을 사용했고, 실험 SDK 버전은 0.2.168이었다. 옵션의 setting_sources=[]는 사용자·프로젝트의 Claude Code 설정 파일을 불러오지 않도록 한 것이다. 인증·환경변수까지 제거한 완전 격리를 뜻하지는 않는다.
결과는 두 층으로 확인했다
1. 실제 테스트를 통과했는가
| 항목 | 의미 |
|---|---|
resolved | test_patch.diff 적용 후 지정 테스트의 종료 코드가 0인가 |
invalid | 에이전트가 tests/ 아래 파일을 수정했는가 |
resolved=False, invalid=False는 규칙은 지켰지만 채점에는 실패했다는 뜻이다. 에이전트의 변경을 먼저 검사하고 채점 패치를 적용하므로, 평가기가 추가한 테스트는 규칙 위반으로 세지 않는다. 최종 채점은 실험 폴더의 .venv 파이썬으로 수행했다.
2. 어떤 행동으로 결과를 만들었는가
judge.py는 실험 모델과 다른 Sonnet으로 result.json, transcript*.jsonl, changes.diff를 읽어 판정한다. 판정에도 setting_sources=[]를 넣고 JSON Schema로 출력 항목을 고정했다.
- 근거_추적: 관련 코드를 읽고 원인을 수정과 연결했는가.
- 편집_최소성: 문제와 무관한 변경을 넓히지 않았는가.
- 검증_성실성: 수정 후 실제 검증을 하고 한계를 정직하게 보고했는가.
각 점수는 0·1·2다. evidence_quote에는 트랜스크립트 원문을 인용하게 하고, 실제 존재하는지 quote_found로 확인했다. 설명이 좋아도 테스트 통과를 대신할 수 없고, 인용문이 존재해도 판정의 해석까지 옳다는 보장은 없다.
실행 결과는 무엇을 보여 줬나
각 실행의 result.json과 집계 파일을 대조한 결과다. 세 방식 모두 24152·24213은 해결했고 24909는 실패했다. 모든 실행은 invalid=False였다.
| 방식 | 해결 | 비용 합계(USD) | 턴 합계 | 호출 시간 합계 |
|---|---|---|---|---|
| A | 2/3 | 0.03230 | 25 | 109.47초 |
| B | 2/3 | 0.06213 | 35 | 134.84초 |
| C | 2/3 | 0.02713 | 22 | 90.43초 |
B는 locator·fixer 두 호출의 합계다. 비용은 SDK 보고값이며, 시간은 SDK 호출 구간이다. 다운로드·저장소 준비·사후 채점을 포함한 전체 시간은 아니다.
이 결과에서는 B가 추가 비용을 썼지만 해결 수는 늘지 않았다. C의 비용·시간 합계는 가장 작았다. 다만 이를 곧바로 협업의 이득이라고 볼 수 없는 이유가 있다.
기록을 읽으며 확인한 세 가지
완료 보고와 실제 채점은 달랐다
24909에서 에이전트는 버그를 고쳤고 기존 테스트도 통과했다고 보고했다. 그러나 사후 채점에서는 A·B·C 모두 다음 검사에 실패했다.
1
assert m * k is S.One
수정 후에도 파이썬 정수 1을 반환하는 경로가 남았다. 위 검사는 값의 동등성인 ==가 아니라 SymPy의 S.One과 같은 객체인지 확인하는 is다. 증상 일부를 개선한 것과 테스트가 기대하는 동작을 충족한 것은 달랐다.
근거는 runs/A-sympy__sympy-24909/result.json의 tail과 같은 폴더의 changes.diff다. Sonnet의 판정 사유에는 출력 표기 형태도 언급됐지만, 그 설명을 그대로 실패 원인으로 확정하지 않고 실제 실패 로그를 우선했다.
locator가 정의됐다고 호출된 것은 아니었다
C의 24909 트랜스크립트에서는 Bash 9회, Edit 4회가 확인됐고, Agent·Task 위임 호출은 없었다. 서브에이전트 기록도 없었다. 이 실행은 리드가 직접 탐색하고 수정했다. 다른 두 C 실행의 위임 여부까지 이 결과로 일반화하지 않았다.
또한 B의 allowed_tools=["Read", "Grep", "Glob"]는 자동 승인 목록이지, 다른 도구를 차단하는 설정 자체는 아니다. B의 locator가 읽기만 했는지는 기록으로 확인할 사항이다. C의 AgentDefinition(tools=...) 및 CLI 파일의 tools: 제한과 구분해야 한다.
별도 요청으로 .claude/agents/locator.md도 만들었다. Haiku·최대 12턴·읽기와 검색만 허용하는 CLI 정의지만, 파일 저장까지만 확인했고 이 정의를 실제 호출하는 추가 실험은 하지 않았다. 기존 C 정의에는 locator 전용 12턴 제한이 명시돼 있지 않아 완전히 같은 설정도 아니다.
판정 모델도 검증 대상이었다
Sonnet 판정은 9건 중 7건 완료 후 세션 사용 한도로 중단됐다. C의 24213·24909 점수는 null이며, 실험의 해결 여부와 별개다. 완료된 7건 중 B의 24909는 명령과 출력을 재구성한 문장을 인용해 quote_found=False가 됐다.
A의 24909는 judgment.json에 2 / 2 / 1과 잘못된_수정이 저장됐고 다음 인용은 원문에서 확인됐다.
The existing test
dodeca * m == 12 * mrequires folding the scale factor into a plainQuantity, so that behavior is intentional and I shouldn’t remove it.
이 인용의 존재는 확인했지만, 이것만으로 판정 사유 전체가 입증되는 것은 아니다. 인용이 맞지 않은 B의 판정과 누락된 두 건을 완전한 평가처럼 취급하지 않았다.
완료된 판정의 SDK 보고 비용은 약 $14.36로, 수정 실험 전체 비용 약 $0.12157와 별도다. 원본과 SDK 스트림을 모두 입력해 중복 기록이 포함된 구현이었다. 평가 자체의 비용도 측정해야 한다는 사례지만, 비용 증가 원인을 항목별로 분석한 것은 아니다.
1
2
3
4
5
# 판정 호출 후 집계. 기존 판정도 다시 호출하므로 비용이 추가된다.
.venv/bin/python judge.py
# 모델 호출 없이 집계만 갱신
.venv/bin/python judge.py --aggregate
무엇을 알고 넘어가야 하나
학습자료는 일을 나누기 전에 두 가지를 묻는다.
- 서로의 중간 결과 없이 독립적으로 탐색할 수 있는가?
- 돌아온 결과를 독립적인 기준으로 검증할 수 있는가?
둘 다 가능하면 병렬 탐색 후보가 된다. 그렇지 않으면 한 에이전트가 연속해서 처리하는 방식부터 고려한다. 나눴을 때의 집중 효과와, 전달·중복·누락·오류 전파·합성 비용을 함께 봐야 한다.
이번 실습에서 가져갈 것은 다음 세 가지다.
- 기준선부터 세운다. 혼자서도 가능한 일을 분업의 성과로 착각하지 않는다.
- 설정과 실제 행동을 구분한다. 정의, 실행 요청, 실제 호출, 성공은 서로 다른 확인 단계다.
- 평가도 검증한다. 테스트 결과와 과정 점수를 구분하고, 인용을 원문으로 되짚는다.
나중에 기록을 읽을 때도 다음 순서를 따르면 된다.
1
2
3
4
5
6
실험 조건 확인
→ result.json: 결과와 실패 로그
→ changes.diff: 실제 수정
→ transcript*.jsonl: 실제 행동
→ judgment.json / grading.jsonl: 판정과 인용
→ results-table.md: 비교와 제한적인 결론
이번 조건에서의 판정과 다음 확인
과제에서 채택은 B·C 사용, 축소는 일부에만 분업 적용, 폐기는 A처럼 단일 처리를 뜻한다.
이번 세 문제의 기록만으로 판단하면 B를 기본 흐름으로 쓰는 분업은 폐기하고 A를 유지한다. B가 해결 수를 늘리지 못했고 비용·호출 시간·턴 합계는 더 컸기 때문이다. C의 낮은 비용은 관측으로 남기되, 24909에서 실제 위임이 없었으므로 멀티 에이전트 협업의 효과라고 결론 내리지 않는다.
이것은 세 문제를 방식별 한 번씩 실행한 경량 평가에 한정한 판단이다. 턴 예산이 다르고, 모델은 정확한 버전이 아닌 haiku 별칭이다. 최종 채점 환경은 고정했지만 에이전트 자체 검증에서는 서로 다른 Python 환경도 사용됐다. 따라서 구조 자체의 일반적인 우열을 입증한 것은 아니다.
다시 실행한다면 같은 문제·커밋·채점을 유지하고, 모델 버전과 자원 예산을 명확히 맞추며 반복 실행하겠다. C의 위임 횟수도 모든 실행에서 확인하고, 판정은 누락·인용 불일치와 중복 입력을 보완해야 한다. 이는 다음 계획이며 아직 수행하지 않았다.
이 테스트의 목적은 에이전트를 많이 쓰는 데 있지 않았다. 일을 나눠야 하는 이유를 정하고, 나눈 결과가 그 비용을 정당화했는지 기록으로 설명하는 것이었다.
자료: 전달받은 「D35 멀티에이전트는 어떻게 적용할까」 PPT(38쪽), 참고코드.md, 평가 루브릭, 실습 문제 저장소.
기록 위치: 로컬 ~/aiffel_work/Multi_agent_codex/의 runs/, grading.jsonl, results-table.md. 이 글은 2026년 10월 21일 확인한 상태이며, 원본 트랜스크립트나 벤치마크 문제 전체를 게시한 것은 아니다.