CH 13 · 서브에이전트와 멀티 Agent 오케스트레이션
본장 목표
작업이 너무 크거나 너무 엉켜 있을 때, 한 Agent가 끝까지 매달리지 말고 몇 조각으로 나눠 여러 서브에이전트에게 병렬로 시키고, 끝나면 결과를 모읍니다. 이번 장에서는 다음을 명확히 합니다. 왜 멀티 Agent인지, dsh의 서브에이전트는 어떤 모습인지, 그리고 실습으로 "작업 분할 → 병렬 위임 → 결과 병합"을 돌려보고, 마지막으로 전체 도구 패밀리를 목록으로 정리합니다.
왜 멀티 Agent인가: 단일 스레드의 세 가지 난관
한 Agent가 시작부터 끝까지 가는 길에서 세 가지 문제에 부딪힙니다.
| 난관 | 증상 |
|---|---|
| 컨텍스트 오염 | 작업이 절반쯤 진행됐을 때, 초반 단계의 내용, 중간 결과, 오류가 같은 세션에 쌓여 모델이 "지금 가장 중요한 정보가 무엇인지" 점점 구분하지 못함 |
| 병렬화 불가 | 정보 조회, 초안 작성, 코드 리뷰 — 세 가지 일을 줄 세울 수밖에 없어, 앞 것이 끝나야 다음 것이 대기 |
| 점점 빗나감 | 긴 작업에서 모델이 잘못된 길로 빠지면 같은 실패 경로를 반복하며 토큰을 태움 |
멀티 Agent 사고방식이 바로 이 해법입니다. 컨텍스트 격리. 각 서브에이전트는 자기 작업에 관련된 컨텍스트만 들고, 메인 Agent는 분할, 파견, 수집을 담당합니다. 토큰 소비는 통제 가능하고, 모든 단계가 트래젝토리에서 검토 가능합니다.
하나의 사실: 서브에이전트 역시 플러그인
먼저 안심하시기 바랍니다. 서브에이전트는 dsh가 따로 숨겨둔 무엇이 아니라, 플러그인입니다 — CH 08의 "모든 것은 플러그인"을 다시 한 번 확인시켜 줍니다. 그리고 서브에이전트는 이미 현재 Agent 도구들의 표준입니다 — Claude Code, Codex 모두 이 능력을 가지고 있고, 유일한 차이는 dsh에서는 여전히 플러그인으로 존재한다는 점입니다. 원하면 쓰고, 아니면 통째로 교체해도 됩니다.
설정 트리에서 위임 계약 ctx.subagents를 볼 수 있으며, 그 유일한 임무는 "누가 서브에이전트가 될 수 있는지"를 등록하는 것입니다. 그리고 서로 다른 제공자 플러그인들이 구체적인 백엔드를 그것에 연결합니다.
| 플러그인 | 실행 대상 |
|---|---|
subagent-spawn-in-process | 완전히 새로운 in-process 서브에이전트 (독립 컨텍스트) |
subagent-fork-in-process | 부모 세션의 완료된 히스토리에서 "파생"된 서브에이전트 |
subagent-acp | Agent Client Protocol 사용, 프로세스 밖 ACP 호환 Agent를 수용 |
subagent-codex | 실제 Codex 실행 (공식 app-server 프로토콜) |
subagent-claude-code | 실제 Claude Code 실행 (공식 Agent SDK) |
subagent-dsh-sdk | TypeScript SDK로 다른 Harness 전체를 띄움 |
메인 Agent는 무엇을 볼 수 있을까요. 그것은 "모델에 보이는 도구" 몇 가지입니다. web 설정 트리를 열면 기본값은 다음과 같습니다.
| 도구 | 하는 일 | 기본값 |
|---|---|---|
subagent | 서브에이전트 파견, continuable 모드 | on |
subagent_fork | 부모 세션의 메모리를 들고 서브에이전트 파견, 일회성 | on |
ralph | 매 라운드마다 새 서브에이전트로 같은 목표를 전진 | on |
workflow | 스크립트를 작성해 여러 하위 작업을 병렬 오케스트레이션 | off (기본 미활성) |
send_message | 특정 서브에이전트에 후속 지시 전달 | off (기본 미활성) |
interrupt_agent | 서브에이전트의 현재 라운드를 중단 | off (기본 미활성) |
list_agents | 현재 서브에이전트와 그 상태 나열 | off (기본 미활성) |
즉, 별다른 설정 없이 subagent, subagent_fork, ralph를 바로 부를 수 있습니다. 나머지 도구는 기본이 꺼져 있어 직접 켜야 하거나, "continuable 모드"에서만 의미가 있는데, 다음 절에서 그것들이 어디에 등장하는지 다룹니다.
spawn vs fork: 두 가지 "출발"
가장 흔히 쓰는 두 서브에이전트의 차이는 단 하나입니다. 부모 세션의 히스토리를 가져오느냐 마느냐.
subagent(spawn): 완전히 새로운 컨텍스트. 서브에이전트는 메인 Agent가 무슨 대화를 했는지 모르고, 이번에 주어진 지시만 따릅니다. 토큰을 아끼고, 독립적인 작업에 적합합니다.subagent_fork(fork): 부모 세션의 완료된 부분을 상속. 서브에이전트는 "여태까지 무엇을 분석했는지"를 알고, 거기서 이어 갈 수 있습니다. 컨텍스트가 반복되니 비용이 약간 더 들고, 이전 결론 위에 쌓아야 하는 작업에 적합합니다.
한 가지 주목할 디테일이 있습니다. dsh는 "상속 여부"를 도구 설명에 직접 적어 둡니다 — spawn 스타일 도구 설명은 "이 대화를 볼 수 없다"고 하고, fork 스타일은 "진행 중인 turn을 볼 수 없다"고 합니다. 즉, 모델 자신이 지시 안에 컨텍스트를 반복할지 판단합니다.
한 줄 기억법
subagent는 새로운 동료가 낙하산으로 내려오는 것과 같고, 그에게는 당신이 말로 한 작업 브리핑만 있습니다. subagent_fork는 회의록을 그에게 툭 던져주며 "방금 여기까지 했으니 거기서부터 계속"이라고 하는 것과 같습니다.
직접 해보기: 첫 병렬 서브에이전트
병렬로, 서브에이전트 세 개를 띄우되, 각각 다음을 수행합니다.
deepseek-harness리포지토리의packages/디렉터리 구조를 정리해 각 능력 패밀리가 무엇을 하는지 설명할 것.- 루트 README가 다루는 핵심 개념을 요약할 것.
docs/아래에서 서브에이전트 관련 문서를 찾아 목록으로 나열할 것. 세 개가 모두 끝난 다음, 그 결과를 하나의 요약으로 병합할 것.
이 한 문장에는 세 가지 요소가 들어 있고, 어느 하나라도 빠지면 혼란이 옵니다.
| 요소 | 왜 필요한가 |
|---|---|
| 작업 분할 규칙 | 모델에게 "어떤 조각으로 나눌지" 알려주지 않으면 모델이 제멋대로 추측 |
| 대기 메커니즘 (모두 끝날 때까지 대기) | 이것이 없으면 메인 Agent가 한 서브에이전트가 끝나기도 전에 불완전한 결과를 정리하기 시작할 수 있음 |
| 출력 요구사항 (요약으로 병합) | 이것이 없으면 세 서브에이전트가 각자 원시 텍스트 더미를 던지고, 사용자가 직접 이어 붙여야 함 |
보낸 뒤, 대화에서 모델이 연속으로 subagent를 여러 번 부르는 것을 볼 수 있습니다 — 그것이 파견하는 모습입니다. 기본 continuable 모드에서는, 모델이 "병렬 가능한 것들을 한꺼번에 먼저 보내고, 자기 일을 계속 진행"하며, 첫 서브에이전트가 돌아올 때까지 기다리지 않습니다.

어떤 서브에이전트가 더 일찍 끝나도 괜찮습니다 — 메인 Agent는 남은 것들을 계속 기다리며, 모두 모였을 때 비로소 병합을 시작합니다.


포어그라운드 / 백그라운드 / Continuable: 세 가지 실행 모드
서브에이전트는 "파견하고 결과를 기다리는" 것만이 아닙니다. "기다리느냐, 계속 대화하느냐"에 따라 세 가지 모드로 나뉩니다.
| 모드 | 언제 쓰나 | 보이는 것 |
|---|---|---|
| 포어그라운드 일회성 | 다음 단계가 바로 이 결과에 의존해야 할 때 | 서브에이전트의 최종 텍스트가 돌아올 때까지 대기 |
| 백그라운드 잡 (일회성 백그라운드) | 기다릴 필요 없이 나중에 수집 | started background subagent job <id>, job_output으로 수집, job_kill로 중단 |
| Continuable 서브에이전트 | 후속 지시를 보내야 할 수 있어 왕복해야 할 때 | started subagent <childId>, 나중에 메시지 전송 가능 |
기본 subagent은 continuable 모드입니다. 기본적으로 백그라운드로 파견되어 돌아가고, 메인 Agent는 자기 일을 진행합니다. 서브에이전트가 끝나면 콜백 알림으로 "끝났다, 여기 결과"가 전달됩니다. 모델은 다음 단계가 정말로 그 결과를 필요로 할 때만 포어그라운드 대기로 전환합니다.
continuable 서브에이전트를 더 정교하게 다루고 싶다면, send_message (후속 지시), interrupt_agent (현재 라운드를 중단, 파괴하지 않으며 재개 가능), list_agents (서브에이전트와 상태 나열)를 켜야 합니다. 이들이 기본 꺼져 있는 이유는 대부분의 시나리오에서 필요 없기 때문이며, "필요할 때 켜는 고급 능력"입니다.
workflow와 ralph: 두 가지 "오케스트레이션" 도구
"작업을 나눠서 병렬로 돌리는" 것 외에, dsh에는 더 구조화된 두 가지 오케스트레이션 유형이 있고, 둘 다 플러그인입니다.
| 도구 | 하는 일 | 적합한 대상 |
|---|---|---|
workflow | 모델이 작은 오케스트레이션 스크립트를 작성하게 하고, agent() / parallel() / pipeline()으로 하위 작업을 펼쳐서, 마지막에 결과를 반환 | 작업이 많고 구조가 고정적이며 실행 순서를 정밀하게 통제해야 할 때 (예: 문서 10건을 한꺼번에 분석) |
ralph | 매 라운드마다 새 서브에이전트로 같은 목표를 전진시키며, 매 라운드 끝에 인계 보고를 써서 다음 라운드에 넘김 | 다라운드 개선, 같은 함정에 빠지기 쉬운 작업, 창의적 또는 디버깅 작업 |
ralph는 기본 켜져 있습니다 (서브에이전트는 spawn 사용, 최대 64라운드). workflow는 기본 꺼져 있습니다 — 그 스크립트는 별도 스레드에서 실행되며, "오케스트레이션 제약"이지 보안 샌드박스가 아니므로, 명확한 일괄 시나리오에서만 켤 가치가 있습니다.
::: tl;dr 일상 병렬 → subagent; 이전 결론 이어서 → subagent_fork; 구조화된 대량 → workflow (기본 꺼짐); 같은 함정 탈출용 반복 개선 → ralph. :::
흔한 함정
| 함정 | 회피법 |
|---|---|
| 여러 서브에이전트가 같은 파일에 동시에 쓴다 | 병렬 작업은 읽기(검색, 분석, 리뷰)를 우선시하고, 쓰기는 메인 Agent가 모든 결과를 모아 한 번에 쓰게 한다 |
| 지시에서 "모두 끝날 때까지 대기"라고 말하는 것을 잊음 | 파견할 때 대기 메커니즘을 포함한다, 그렇지 않으면 메인 Agent가 반쪽짜리 결과로 일을 시작할 수 있음 |
| 작업을 너무 잘게 쪼갬 | 각 서브에이전트에는 시작 비용이 든다; 5분짜리 작업을 10개로 쪼개면 오히려 느려진다; "하나의 의미 있는 일을 독립적으로 완수"할 수 있는 단위까지로 쪼갠다 |
| 서브에이전트가 무한 중첩됨 | 위임 깊이에 상한이 있다 (기본 3, 0이면 금지); "메인 Agent → 서브에이전트" 스타 토폴로지를 유지하는 것이 가장 안정적 |
이번 장에서 배운 것
다음 항목들을 스스로 완수할 수 있으면 합격입니다.
- [ ] 단일 Agent의 긴 작업에서 나타나는 세 가지 난관과 멀티 Agent의 핵심 가치(컨텍스트 격리)를 말할 수 있다
- [ ] 서브에이전트 역시 플러그인임을 안다:
ctx.subagents는 위임 계약이고, spawn / fork가 가장 흔히 쓰는 두 백엔드다 - [ ]
subagent(완전히 새로운 컨텍스트)와subagent_fork(부모 세션의 완료된 히스토리를 가짐)를 구분한다 - [ ] 적어도 한 번 "작업 분할 + 모두 끝날 때까지 대기 + 결과 병합" 병렬 서브에이전트를 돌렸고, 트래젝토리에서 서브에이전트 호출을 보았다
- [ ] 포어그라운드, 백그라운드 잡, continuable 서브에이전트 모드의 차이를 말할 수 있다
- [ ]
workflow,ralph,send_message,interrupt_agent,list_agents가 각각 무엇을 하는지 안다
