DeepSeek Harness · 오케스트레이션과 서브 Agent

Subagent는 seam: 프로세스 안에서 Claude Code 위임까지

서브 Agent는 능력 seam이에요. 프로세스 내, 원격, 다른 제품 모두 연결할 수 있습니다. 핵심 소스: packages/subagent/.

강의 목표읽고 나면 세 가지를 말할 수 있어요. DSH가 서브 Agent를 provider 레지스트리(seam, 능력 seam)로 정의해 프로세스 내 spawn, 컨텍스트 fork, Claude Code·Codex 위임이 같은 인터페이스를 쓰는 이유; 능력이 맞지 않을 때 시작 전 fail loud를 택하고 조용히 무시하지 않는 이유; 그리고 일회성 run과 지속 가능한 Activation이 각각 어떤 문제를 푸는지.
인터랙티브 데모 · 위임 전환대

같은 하위 과제 「이 모듈을 조사해 보고 보고해」에 네 provider를 바꿔가며 각각 한 번씩 위임해 보세요. 두 가지를 봅니다: seam 양쪽에서 무엇이 전혀 변하지 않는지, 구현에 따라 무엇이 바뀌는지. 「persona 요구 포함」을 켜면 능력 래치가 범위를 벗어난 요청을 시작 전에 어떻게 막는지도 확인하세요.

부모 Agent 세션 로그(DSH 프로세스 내)
user「결제 모듈을 연결해 줘」
assistant알겠어요. 먼저 기존 구조를 볼게요.
turn/end1턴 종료(시드 경계)
user「이 모듈 과거 이슈도 확인해 줘」
tool/callsubagent「이 모듈을 조사해 보고 보고해」
ctx.subagents · provider 레지스트리(seam)
spawn fork acp codex claude-code sdk
능력 래치와 서브 Agent 무대
시작 시 능력spawn
outputSchema(구조화 출력)지원
depthLimit(위임 깊이 상한)지원
toolFilter(도구 제한)지원
persona(페르소나 교체)지원
SubagentError: subagent provider "claude-code" does not support the "persona" capability · code: UNSUPPORTED_CAPABILITY
서브 Agent프로세스 내
(컨텍스트 비어 있음)
서브 Agent 작업 중
provider를 고른 뒤 「재생」을 누르거나, 여기로 스크롤하면 spawn이 자동 재생됩니다.
수업용 시뮬레이션입니다. 능력 표 데이터는 각 provider 소스 기준: spawn과 fork는 네 항목 모두 지원(subagent-spawn-in-process/src/index.ts 42행, subagent-fork-in-process/src/index.ts 62행), Claude Code와 Codex는 NO_START_CAPABILITIES(각 src/index.ts 54·49행). 오류 문구는 packages/subagent/subagent/src/index.ts 490–493행을 그대로 따랐습니다.
로직 분해 · seam은 어느 층에 정의되나

결론부터 말할게요. DSH는 별도의 서브 Agent 기능을 만든 게 아니라 ctx.subagents라는 레지스트리를 만들었어요. SubagentProvider 계약을 구현한 전송층은 이름으로 등록할 수 있습니다. 공식 배포판에는 여섯 개: spawn(프로세스 내 새로 시작), fork(프로세스 내 컨텍스트 포함), acp(프로토콜 브리지), codex, claude-code(각각 실제 제품 CLI 프로세스), sdk(원격 DSH 인스턴스). 출처: docs/subsystems/subagent.zh.md 5–7행.

입력: 도구층이 모델의 위임 요청을 SubagentStartRequest로 조립합니다. prompt, 부모 Agent, 취소 신호, 네 가지 선택 항목(구조화 출력 schema, 깊이 상한, 도구 필터, persona). 처리: 서비스가 선택한 provider의 정적 능력 표를 먼저 봅니다. 선택 항목마다 대응 capability flag가 있어야 하고, 하나라도 없으면 시작 전 UNSUPPORTED_CAPABILITY를 던집니다. 출력: SubagentRun 핸들. 부모 Agent는 result를 기다리고, 최종적으로 일반 도구 결과 한 건이 됩니다. 부모 입장에서 네 provider 모두 같은 종류의 결과를 돌려줍니다.

fork의 정체를 잠깐 짚을게요. 많은 프레임워크는 부모 컨텍스트 포함 여부를 불리언 하나로 둡니다. DSH는 fork를 독립 provider로 뒀어요. 차이가 스위치 하나가 아니기 때문입니다. fork는 부모 로그에서 「완료된 턴의 균형 접두사」를 시드로 잘라 내고, 마지막 turn/end까지 자릅니다. 진행 중인 턴은 불균형이라 리플레이할 수 없어 반드시 제외합니다. 세션 로그 계약이라 flag 하나로는 설명이 안 됩니다.

tool-subagent 모델이 「조사해」라고 함 StartRequest ctx.subagents 레지스트리 이름으로 provider 선택 · 능력 표 검증 능력 부족 → UNSUPPORTED_CAPABILITY spawn / fork 프로세스 내 서브 Agent fork는 부모 로그 균형 접두사 네 가지 능력 모두 지원 acp / sdk 프로토콜 브리지·원격 인스턴스 localAgent: undefined claude-code / codex 실제 제품 CLI 프로세스 기동 부모 세션 cwd 사용 시작 시 능력 모두 미지원 부모 Agent로 복귀 SubagentRun.result → 도구 결과 한 건 completed 아님 → isError seam 위는 모두 동일; seam 아래 전송 방식은 provider마다 다름
수업용 구조도: 노드와 연결은 소스 관계를 설명하며, 내용은 수업에 맞게 정리했습니다.
fork와 spawn은 provider 두 개

차이는 매개변수가 아니라 시드입니다. fork는 부모 로그를 마지막 turn/end까지 잘라 자식 세션 시드로 쓰고, spawn은 제로에서 시작합니다. inheritsParentContext는 설명용 필드로, 도구층이 거짓 없는 문구를 만들 때 씁니다.

일회성 run과 지속 Activation

SubagentRun은 한 번뿐: 결과 대기, dispose, 종료. 지속 가능한 서브 Agent에는 run이 없고, 영속 세션과 최대 하나의 상주 Activation입니다. 부모는 send_message로 턴 추가, interrupt_agent로 중단, report를 받습니다.

백그라운드 종료는 조용하지 않음

지속 서브 Agent가 정산될 때 관리자는 무조건 부모에 subagent-settled 알림을 넣고 최종 출력을 실어 보냅니다. 서브 Agent가 스스로 보낸 report와 메시지 출처 kind가 달라, transcript가 런타임 기장을 서브 Agent 발화로 세지 않습니다.

핵심 증거 · 능력 래치와 fork 시드

첫 번째 증거는 능력 래치 본체입니다. 코드 없이도 로직은 분명해요. assertCapabilities는 요청의 네 선택 항목을 요구 목록으로 만듭니다. outputSchema가 있으면 provider 표에서 outputSchema가 true여야 하고, maxDepth면 depthLimit, toolFilter와 persona도 같습니다. 항목별로 대조해 첫 불일치에서 SubagentError를 던지고, 어떤 provider가 어떤 능력을 지원하지 않는지 말하며 코드는 UNSUPPORTED_CAPABILITY입니다. 다운그레이드 없음, 경고 후 진행 없음, 이전에 자식 프로세스는 하나도 안 뜹니다.

출처: packages/subagent/subagent/src/index.ts 481–495행, 확인일 2026-08-13. 데모 오류 문구는 490–493행을 그대로 따랐습니다.

두 번째 증거는 fork 시드 함수입니다. 컨텍스트 포함이 실제로 무엇인지 일곱 줄로 말합니다:

packages/subagent/subagent-fork-in-process/src/index.ts48–54행 발췌
function completedTurnPrefix(parent: Agent): SessionEvent[] {
  const events = parent.session.events
  const lastEnd = events.findLast(e => e.type === 'turn/end')
  if (lastEnd === undefined) return []
  // seq === array index (the append contract), so slice up to and including it.
  return events.slice(0, lastEnd.seq + 1)
}
소스 스냅샷 안내: 로컬 저장소 deepseek-harness-master 기준, 확인 파일 packages/subagent/subagent/src/index.tspackages/subagent/subagent-fork-in-process/src/index.ts, 확인일 2026-08-13. 코드 블록은 소스 원문을 유지합니다.

코드 없이 두 메커니즘만 글로 표시합니다. Claude Code provider 전체 구현은: claude 실행 파일을 찾고, 부모 세션 cwd에서 공식 Agent SDK로 실제 CLI 프로세스를 띄워 공유 subprocess owner에 붙입니다(subagent-claude-code/src/index.ts 62–91행). 공식 네 preset에서 codex·claude-code 위임 도구 행은 disabled: true로 출하되며, 주석은 preset 복사 후 disabled를 지우면 복사본 세션에만 그 제품 백엔드를 연다고 적습니다(apps/cli/config/agent-presets/standard/agent.cordis.yml 200–219행). DSH에서 다른 제품 위임은 설정 스위치 문제입니다.

가로 비교 · 제품 내 위임 형태 vs 전송층 레지스트리

Claude Code는 위임을 제품층에 둡니다. Task 도구(AgentTool) 한 입구에 매개변수로 여러 형태를 넣습니다: subagent_type으로 역할, run_in_background 백그라운드, isolation: "worktree" 격리 복제, model로 모델 교체(원고 study/chapters/05-multi-agent.md 32–52행이 AgentTool.tsx 인용). 그 위에 Coordinator Mode와 Agent Teams가 있습니다. 표현력은 크지만 각 형태는 이 제품 안의 기능 분기입니다. 위임 대상은 항상 또 다른 Claude Code 인스턴스이고, 다른 제품에 일을 넘기는 자리는 없습니다. DSH는 반대로 제품 차이를 provider층으로 내리고, seam 위에는 어휘 하나만 둡니다.

Grok Build는 세 번째 길입니다. 서브 Agent 설정 해석을 순수 로직 crate xai-grok-subagent-resolution로 빼고, explicit override > role > persona > parent 우선순위로 유효 설정을 만듭니다(해당 crate src/lib.rs 7–8행). 실행은 자사 shell 프로세스 안에 둡니다. 추상화 대상은 서브 Agent가 어떻게 생겼는지이고, DSH는 어디서 도는지입니다. Grok의 역할·컨텍스트 상속·트리 계보는 사이트에 세 레슨이 있습니다: 서브 Agent 네 가지 역할, 컨텍스트 상속과 깊이 제어, 서브 Agent 세션 계보.

수업 실습
01

범위 밖 위임 한 번 추론하기

배포에서 subagent_claude_code 도구가 켜져 있고, 모델 위임에 outputSchema(구조화 출력 요구)가 붙었습니다. 이번 레슨 능력 래치 코드로 추론하세요: 어느 줄에서 오류가 나는지, 오류 코드는 무엇인지, Claude Code CLI 프로세스가 한 번이라도 떴는지. 추가로 DSH가 요청을 받고 schema를 무시했다면 부모 Agent의 도구 결과에 무슨 문제가 생기고, 왜 즉시 fail loud보다 디버깅이 어려운지 답하세요.

Takeaway: DSH에서 서브 Agent는 provider 레지스트리입니다. 프로세스 내 fork와 Claude Code 위임이 같은 인터페이스를 쓰고, 차이는 모두 seam 아래로 내려갑니다. 능력 불일치는 시작 전 fail loud, 받아 놓고 조용히 다운그레이드하지 않습니다. 내 Agent에 다른 제품 위임을 넣으려면 먼저 seam이 어느 층에 정의되는지, 능력 표를 어떻게 검증하는지부터 물으세요.