DeepSeek Harness 08: 서브에이전트와 에이전트 팀 협업
에이전트가 다른 에이전트를 호출해 텍스트나 구조화 JSON을 받는 dsh의 다중 에이전트 구조를 서브에이전트 실행과 도구 필터링 중심으로 설명한다.
중국어 원문을 AI로 번역했습니다. 고유명사와 수치는 원문 표기를 우선하며, 중요한 판단에는 아래 출처 원문을 함께 확인하세요.
도구 하나로 해결할 수 없는 문제
어떤 작업이 있다고 가정해 보자. 대형 코드베이스에 대한 전면적 리팩터링——모든 파일의 명명 규칙을 검사하고, 모든 순환 의존성을 찾아내고, 인터페이스 정의를 정리하는 일이다. 파일 수는 1,000개를 넘는다.
이 작업을 하나의 Agent에 맡겨서 파일을 하나하나 처리하게 할 수 있다. 하지만 문제는 금방 나타난다.
- 컨텍스트 윈도우가 한정적이다 : 300번째 파일을 처리할 즈음이면 앞선 내용은 이미 윈도우 밖으로 밀려나 있다
- 도구에는 reasoning 능력이 없다 : 도구는 고정된 로직만 실행할 수 있고, "이 파일이 어느 서브시스템에 속하는가"에 따라 전략을 조정할 수 없다
- 도구에는 독립적인 대화 이력이 없다 : 도구는 실행이 끝나면 그대로 끝나고, 상태가 보존되지 않으며, 다음 호출에서는 지난번에 무엇을 보았는지 전혀 기억하지 못한다
멀티 Agent의 본질은 하위 작업을 독립적인 대화 이력을 가진 또 다른 Agent에게 위임하는 것이다. 자식 Agent는 자신만의 Session을 가지고, 자신만의 reasoning 과정을 가지며, 도구를 사용할 수 있고, 자신만의 컨텍스트 윈도우를 가진다. 부모 Agent는 자식 Agent에게 "이 파일 묶음을 처리하고 결과를 알려줘"라고 말하기만 하면 된다.
이는 단순히 도구을 호출하는 것과의 차이가, 어떤 일을 "다른 사람에게 외주를 주는 것"과 "기계 한 대를 가동하는 것"의 차이와 같다.
dsh의 Subagent 메커니즘
dsh는 Subagent를 통해 Agent 위임을 구현한다. 시작 방식은 두 가지다.
- 모델 호출 : 모델이 특정 하위 작업을 위임하기로 결정하면 내장 도구 subagent_spawn을 호출한다
- 코드 호출 : 도구의 execute 함수 안에서 ctx.subagents.start()를 통해 직접 시작한다
자식 Agent를 시작할 때는 SubagentStartRequest를 제공해야 하며, 핵심 필드는 다음과 같다.
// packages/subagent/subagent/src/types.ts의 SubagentStartRequest에서 간추림 // 실제 필드는 더 많으며, 여기서는 가장 핵심적인 부분만 나열 interface SubagentStartRequest { // 자식 Agent의 초기 프롬프트(ContentBlock 배열, 텍스트·이미지 등 지원) prompt : ContentBlock [] // 부모 Agent 객체(작업 디렉터리, 혈연 관계 정보 제공) parent : Agent // 취소 신호(부모 Agent가 자식 Agent의 실행을 중단할 수 있게 함) signal : AbortSignal // 선택: 자식 Agent가 사용할 수 있는 도구 제한(쓸 도구만 주기) toolFilter?: ToolRestriction // 선택: 자식 Agent에게 전용 Persona 부여(전역 배포 persona 덮어쓰기) persona?: string // 선택: 구조화 출력 schema(자식 Agent가 순수 텍스트가 아닌 JSON 객체를 반환하도록) outputSchema?: ObjectJsonSchema // 선택: 최대 위임 깊이(자식 Agent가 다시 자식 Agent를 시작해 무한 재귀가 생기는 것을 방지) maxDepth?: number }
몇 가지 필드를 눈여겨보자.
toolFilter : 자식 Agent는 기본적으로 부모 Agent의 모든 도구를 상속하지만, 대부분의 경우에는 제한된 도구 집합만 주고 싶을 것이다. 예를 들어 코드 분석을 하는 자식 Agent는 파일을 읽기만 하면 되고, 파일을 쓸 필요는 없다.
persona : 자식 Agent는 자신만의 시스템 프롬프트 접두사를 가질 수 있다. 전역 Persona를 상속하는 대신, 전용 역할 정의를 부여해서 그 역할 안에서 일하게 할 수 있다.
outputSchema : 이 필드는 자식 Agent의 출력이 더 이상 순수 텍스트가 아니라 구조화된 JSON 객체가 되게 한다. 부모 Agent가 받는 result.structured는 타입 안전하다.
일회성 vs 이어가기 가능
Subagent에는 두 가지 생명주기 모드가 있으며, 차이는 부모 Agent가 자식 Agent와 여러 번 "대화"할 필요가 있는지에 있다.
일회성 Subagent(One-shot) :
시작 → 자식 Agent가 모든 작업 완료 → 결과 반환 → 종료. 전체 과정이 한 번의 왕복이며, 독립적인 하위 작업에 적합하다.
이어가기 가능 Subagent(Continuable) :
자식 Agent가 시작되면 영속화된 Session이 하나 생성된다. 부모 Agent는 계속해서 그것에 메시지를 보낼 수 있고, 자식 Agent는 메시지를 받을 때마다 실행을 이어간다. 여러 번 왕복이 필요한 협업에 적합하다.
두 모드의 비교:
일회성 Subagent: Parent ── start ()──► Child가 전체 작업 실행 ──result──► Parent (자식 Agent는 실행이 끝나면 생명주기가 종료됨) 이어가기 가능 Subagent: Parent ── startContinuable ()──► Child Session 생성, 대기 진입 Parent ── sendMessage ('첫 번째 파일 묶음')──► Child가 첫 번째 묶음 처리 ──result──► Parent Parent ── sendMessage ('두 번째 파일 묶음')──► Child가 두 번째 묶음 처리 ──result──► Parent Parent ── sendMessage ('결과 종합')───► Child 종합 ──final result──► Parent (자식 Agent Session은 전 과정 동안 활성 상태로 유지되며, 완전한 대화 이력을 가짐)
이어가기 가능 Subagent의 장점은 자식 Agent의 대화 이력이 연속적이라는 점이다. 앞선 몇 라운드에서 무엇을 처리했는지 기억할 수 있고, 종합 단계에서 이 기억을 활용할 수 있다.
통신 경로: 누가 누구에게 메시지를 보낼 수 있는가
멀티 Agent 시나리오에서 자연스럽게 떠오르는 질문은 이것이다. Agent끼리 임의로 통신할 수 있는가?
답은 아니다 . dsh는 통신 경로에 명확한 제약을 둔다.
통신 방향 허용 여부 설명 부모 → 자식 ✅ 허용 자식 Agent의 parentSession이 부모 Agent를 가리켜야 함 자식 → 부모 ✅ 허용 자식 Agent는 부모 Agent에게 메시지를 보낼 수 있음 형제 간 ❌ 불가 같은 부모의 두 자식 Agent는 서로 메시지를 보낼 수 없음 세대 간(조부 → 손자) ❌ 불가 세대를 건너뛴 통신은 거부됨
Parent / \ Child A Child B | Grandchild ✅ Parent ──► Child A ✅ Child A ──► Parent ❌ Child A ──► Child B (형제는 통신 불가) ❌ Parent ──► Grandchild(세대 간 불가)
이 제약은 의도된 것이다. 임의 통신을 허용하면 Agent 네트워크가 추적하기 어려운 메시지 그래프가 되어, 디버깅할 때 메시지가 어디서 온 것인지 전혀 알 수 없게 된다. 계층이 명확해야 각 Agent의 책임 경계도 명확해진다.
toolFilter와 persona
도구 필터링
자식 Agent에게 필요한 도구만 할당하는 것은 멀티 Agent 시스템에서 가장 중요한 권한 제어 수단이다.
// 자식 Agent는 다음 세 도구만 사용할 수 있음 // 부모 Agent가 가진 다른 도구(예: write_file, execute_shell)는 그것에게 보이지 않음 toolFilter : { allow : [ 'read_file' , 'search_files' , 'list_files' ], // deny로 특정 도구를 배제할 수도 있음 // deny: ['write_file', 'execute_shell'] }
왜 이렇게 해야 하는가?
그 이유는 안전성만이 아니라, 작업 집중도 에 있다. 코드를 분석하는 자식 Agent의 도구 목록에 write_file이 있으면, 모델이 문제가 있다고 생각하는 코드를 "겸사겸사" 수정해 버릴 수 있는데, 이는 기대하는 동작이 아니다. 읽기 전용 도구만 주면, 읽는 것밖에 할 수 없다.
Persona
// 자식 Agent는 전용 시스템 프롬프트 접두사를 사용 // 이는 전역 배포 persona를 덮어쓰며, 자식 Agent가 전용 역할 안에서 일하게 함 persona : 'You are a specialized test writer. Focus only on writing unit tests for the given code. Do not modify existing source files, do not suggest refactoring.'
Persona의 의의는 이것이다. 부모 Agent는 범용 어시스턴트일 수 있지만, 그것이 시작한 자식 Agent는 단일 작업에 극도로 집중하는 전문가일 수 있다. 자식 Agent를 만들 때마다 역할을 재정의하며, 전역 설정의 간섭을 받지 않는다.
구조화 출력(outputSchema)
자식 Agent는 기본적으로 텍스트를 반환한다. 하지만 자동화 파이프라인에서는 부모 Agent가 필요로 하는 것이 자연어 설명 한 토막이 아니라, 곧바로 처리할 수 있는 데이터인 경우가 많다.
outputSchema는 이 문제를 해결한다.
// 자식 Agent가 구조화 JSON을 반환하도록 함——부모 Agent는 텍스트를 파싱할 필요가 없음(의사코드) const result = await ctx. subagents . start ({ prompt : [{ type : 'text' , text : '이 코드를 분석해서 모든 bug를 찾아내라.' }], parent : currentAgent, signal, outputSchema : { type : 'object' , properties : { bugs : { type : 'array' , items : { type : 'object' , properties : { file : { type : 'string' }, line : { type : 'number' }, severity : { type : 'string' , enum : [ 'critical' , 'major' , 'minor' ] }, description : { type : 'string' }, }, }, }, }, }, }) // result.text는 자식 Agent의 텍스트 출력(있을 경우) // result.structured는 타입 안전한 JSON 객체——바로 사용, 파싱 불필요 console . log (result. structured . bugs ) // → [{ file: 'auth.ts', line: 42, severity: 'critical', description: '...' }, ...]
이로써 멀티 Agent 시스템의 데이터 흐름이 깔끔해진다. 자식 Agent의 분석 결과가 곧바로 부모 Agent의 다음 단계 의사결정 입력이 되며, 텍스트 파싱 계층을 하나 더 둘 필요가 없다.
여섯 가지 Subagent Provider
dsh는 다양한 하위 구현을 지원하여 서로 다른 배포 시나리오를 충족한다.
Provider 설명 적용 시나리오 spawn-in-process 같은 프로세스 안에서 새 Agent 인스턴스 생성 로컬 개발, 테스트 fork-in-process 현재 Session을 Fork, 자식 Agent가 접두 이력 상속 컨텍스트 상속이 필요한 하위 작업 dsh-sdk dsh SDK를 통해 독립 런타임 시작 완전한 격리가 필요한 자식 Agent acp ACP 프로토콜을 통해 원격 Agent와 통신 원격 Agent 클러스터 codex Codex Agent 호출 코드 전용 작업 claude-code Claude Code 호출 코드 전용 작업
대부분의 로컬 개발 시나리오에서는 spawn-in-process로 충분하다. 격리가 필요할 때(예: 서로 다른 자식 Agent가 서로 다른 파일 시스템 권한을 가짐)는 dsh-sdk를 고려하라.
실험적 기능: Agent Teams
Subagent는 계층 모델이다——부모-자식 관계가 명확하고, 통신 경로가 엄격하다. 하지만 어떤 협업 시나리오는 본질적으로 "평등하다". 여러 전문가 Agent가 같은 작업을 둘러싸고 병렬로 일하고, 누구 결과가 먼저 나오든 먼저 보고한다.
Agent Teams는 dsh가 개발 중인 멀티 Agent 협업 프레임워크로, ctx.agentTeams (실험적 서비스)를 통해 접근한다.
- roster(명부) : 팀 멤버를 등록하며, 각 멤버는 이름과 역할을 가진다
- mailbox(우편함) : 멤버끼리 우편함을 통해 메시지를 주고받는다
- task board(작업 게시판) : 팀이 공유하는 작업 목록으로, 멤버가 작업을 맡고 제출할 수 있다
- 조정자 모드 : 조정자 Agent 하나가 작업을 분배하고 결과를 종합할 수 있다
Subagent의 계층 모델과 비교하면, Teams는 "팀 협업"에 더 가깝다: 엄격한 부자(父子) 계층이 없고, 구성원 간에 평등하게 통신할 수 있으며, 작업을 동적으로 할당할 수 있다.
Subagent 모델(계층): Agent Teams 모델(평등): Orchestrator ┌──────────────────┐ / | \ │ task board │ 자식 A 자식 B 자식 C └──────────────────┘ (엄격한 부자 관계, Agent A ◄──► Agent B 단방향 위임) │ │ └────► Agent C ◄┘ (평등 통신, 동적 협업)
주의: Agent Teams는 현재 실험적 기능이며, API는 이후 버전에서 변경될 수 있다. 프로덕션 환경에서 사용하기 전에 버전 안정성을 확인하라.
실전: 메인 Agent가 서브 Agent 호출하기
다음은 완전한 패턴 예시다: 메인 Agent가 하나의 도구를 정의하고, 그 도구 내부에서 서브 Agent를 시작해 전문 분석 작업을 완료한다.
// 의사 코드: 메인 Agent의 도구 정의, 서브 Agent에 코드 분석을 위임 const analyzeCodebaseTool = defineTool ({ name : 'analyze_codebase' , description : '전용 서브 Agent를 시작해 지정된 코드베이스 디렉터리를 전면 분석한다.' , parameters : { directory : { type : 'string' , required : true , description : '분석할 디렉터리 경로' }, focus : { type : 'string' , required : true , description : '분석 중점(예: 명명 규칙, 의존 관계)' }, }, async execute ( args, exec ) { // 서브 Agent 시작(의사 코드, 실제로는 ctx.subagents.start()로 구현) const result = await ctx. subagents . start ({ prompt : [{ type : 'text' , text : `디렉터리 ${args.directory} 를 분석하고, 다음에 중점을 둘 것: ${args.focus} . 파일별로 하나씩 검사하고, 문제 목록을 요약한다.` , }], parent : exec. agent , // 부모 Agent를 전달해 혈연 관계를 설정 signal : exec. signal , // 취소 신호를 전달, 부모 Agent가 취소하면 서브 Agent도 중지 // 서브 Agent는 파일을 읽기만 할 수 있고, 코드를 수정할 수 없다 toolFilter : { allow : [ 'read_file' , 'list_files' , 'search_files' ], }, // 전용 역할: 코드 리뷰 전문가 persona : 'You are a code review expert. Read code carefully, identify issues, and provide a structured summary. Do not modify any files.' , // 구조화된 JSON 반환을 요구 outputSchema : { type : 'object' , properties : { issues : { type : 'array' , items : { type : 'object' , properties : { file : { type : 'string' }, issue : { type : 'string' }, severity : { type : 'string' , enum : [ 'high' , 'medium' , 'low' ] }, }, }, }, summary : { type : 'string' }, }, }, }) // result.structured는 타입 안전한 JSON이므로 바로 사용한다 const { issues, summary } = result. structured return `분석 완료. 총 ${issues.length} 개 문제를 발견했다.\n ${summary} ` }, })
이 패턴의 핵심 가치는: 부모 Agent가 서브 Agent가 어떻게 코드를 분석하는지 알 필요가 없고, 구조화된 결과만 받아서 후속 의사 결정을 계속하면 된다는 점이다.
다른 프레임워크와의 비교
프레임워크 | 다중 Agent 모델 | dsh와의 주요 차이 Claude Code | Agent tool, 역시 spawn subagent | 인터페이스가 더 단순하지만, toolFilter / persona / outputSchema가 없다 LangGraph | 그래프형 워크플로, 고정된 토폴로지 | dsh Subagent는 동적이다——미리 정의된 그래프가 아니라, 모델이 런타임에 언제 위임할지 결정한다 AutoGen | 대화식 다중 Agent, Agent끼리 직접 메시지를 주고받음 | dsh는 계층 모델로 명확한 부자 관계가 있고, 형제 Agent끼리 직접 통신할 수 없다
간단히 말하면: LangGraph는 흐름이 고정된 시나리오에 적합하고, dsh Subagent는 흐름을 모델이 동적으로 결정하는 시나리오에 적합하다.
시리즈 다음 편
다음 편에서는 관측성(Observability)을 다룬다: Agent가 런타임에 무엇을 했는지, 어떻게 알 수 있는가? dsh는 도구 호출, Token 소모, 서브 Agent 혈연 트리를 추적하기 위해 어떤 메커니즘을 제공하며, 프로덕션 환경에서 로그 시스템에 어떻게 연동하는가.
PrimeSkills에서는 실제 기업 시나리오에서 이미 검증된 AI Agent 스킬과 워크플로를 찾을 수 있다. 데모 수준이 아니라 실제 프로젝트에서 사용되는 것이다.
더 많은 내용은 나의 개인 홈페이지에서 볼 수 있다
冬奇Lab
소프트웨어 아키텍처
509
글
321k
조회
742
팔로워