MMax's Develop Story

사내 Claude Code 세미나 5주차 회고: Sub-Agents, 격리된 전문가에게 일을 위임하기

Max··26분 읽기
Claude CodeSub-Agents위임아키텍처세미나회고

세미나 시리즈의 마지막 주차를 마쳤다. Sub-Agents는 시리즈의 정점이자, 1주차에서 짧게 짚고 넘어간 그 그림으로 돌아오는 자리였다. "외주 전문가에게 일을 통째로 맡긴다" 라는 비유 하나로 4주를 달려온 챕터원들이 마지막 순간에 모두 같은 그림에 도달하는 걸 봤을 때, 발표자로서 가장 보람 있는 한 시간이었다.

사내 기술연구소 Claude Code 세미나 다섯 번째 주차. 1주차의 API 요청 구조, 2주차의 Memory, 3주차의 Hooks, 4주차의 Skills를 차곡차곡 쌓아 올린 자리에서, 이번 주는 Sub-Agent를 다뤘다. 한 번의 사용자 메시지가 어떻게 격리된 컨텍스트로 갈라져, 어떤 안전장치 아래에서, 어떤 결과만 메인으로 되돌아오는지. 이 글은 그 발표를 백엔드 시니어의 시선에서 다시 풀어 쓴 회고다.

1. Sub-Agent란 무엇인가: 자체 컨텍스트 윈도우를 가진 독립 AI

발표 첫 슬라이드에 박은 정의는 이것이었다.

Sub-Agent는 자체 컨텍스트 윈도우에서 독립 실행되는 특화된 AI 어시스턴트 다. 주 대화(Main Thread)는 sub-agent의 description을 보고 작업을 위임하고, sub-agent는 결과 요약만 되돌려준다. 긴 출력이 주 컨텍스트를 오염시키지 않는다.

Sub-Agent 격리 컨텍스트와 위임

여기서 가장 좋아하는 비유를 발표장에 꺼냈다.

메인 Claude는 나와 같은 회의실에 앉아 화이트보드 앞에서 함께 작업하는 동료 다. Sub-Agent는 외주 전문가에게 작업을 통째로 위임하는 일 이다. 동료에게 시키면 그 사람 머리에 우리 대화가 다 쌓이지만, 외주 전문가에게는 결과 요약만 받아온다. 긴 사이드 미팅의 부담을 메인 대화에 떠넘기지 않는다.

그날 발표에서 가장 자주 인용된 한 줄이기도 했다. 4주차에 Skill을 "꺼내 펼치는 매뉴얼"로, 이번 주차에 Sub-Agent를 "외주 전문가"로 비유한 게 머릿속에 깔리니, 둘의 차이가 단번에 정리되었다는 피드백이 많았다.

2. 왜 Sub-Agent를 쓰는가: 시니어가 받아들이는 6가지 가치

이 부분은 한 슬라이드에 6가지를 그대로 박았다. 각각이 백엔드 시각에서 무엇과 대응되는지를 함께 적어둔다.

  • 컨텍스트 보존: 탐색·구현의 긴 출력이 주 대화 컨텍스트를 오염시키지 않음. → 트랜잭션 격리 수준의 LLM 버전이다.
  • 제약 조건 적용: subagent별 도구 허용/거부, 권한 모드 강제. → IAM의 최소 권한 원칙(Least Privilege).
  • 구성 재사용: 사용자/프로젝트 수준 agent 파일로 프로젝트 간 공유. → 라이브러리화.
  • 동작 특화: 도메인별 집중 프롬프트로 품질 향상. 만능 agent보다 한 가지만 잘하는 agent가 결과가 좋다. → 단일 책임 원칙(SRP)의 LLM 버전.
  • 비용 제어: 단순 수집은 Haiku, 보안은 Opus 등 작업별 모델 라우팅. → 비용·성능 트레이드오프의 명시적 선언.
  • 병렬 실행: 서로 독립적인 조사는 동시에 띄워 시간 단축. → 비동기 워크로드 분산.

발표장에서 어떤 챕터원이 이렇게 말했다. "이건 그냥 마이크로서비스 아키텍처를 LLM 안에서 다시 그리는 거네요." 정확히 그게 핵심이다. 격리된 컨텍스트로 책임을 분리하고, 명확한 인터페이스로 위임하며, 권한과 자원을 제약한다. 우리가 백엔드 시스템에서 이미 30년간 해온 일이 LLM 컨텍스트 위에 다시 그려진 것이다.

3. 내장 Sub-Agent: 사용자 정의 전에 먼저 알아둘 것

Claude Code에는 이미 다섯 개의 내장 sub-agent가 있다. 새 agent를 만들기 전에 먼저 이들을 알아두는 게 정답이다.

에이전트모델도구용도
ExploreHaiku읽기 전용파일/코드 검색, 코드베이스 탐색. quick / medium / very thorough
Plan메인 상속읽기 전용plan mode에서 계획 수립 전 컨텍스트 수집
General-purpose메인 상속모든 도구탐색+수정이 모두 필요한 복잡한 다단계 작업
statusline-setupSonnet:/statusline으로 상태 표시줄 구성
Claude Code GuideHaiku:Claude Code 기능 질문에 자동 응답

시니어로서 강조한 한 줄: "변경 없이 코드베이스를 이해만 하면 되는 작업이라면, 사용자 정의 agent보다 Explore를 먼저 떠올려라." 주 대화 컨텍스트를 가장 적게 소모하는 도구가 이미 들어 있다. 새 agent를 만드는 것은 그것으로도 안 되는 작업이 명확해진 다음의 일이다.

4. Frontmatter: name · description · tools · model부터 차근차근

Sub-Agent는 마크다운 한 파일이다. 상단 YAML frontmatter가 행동의 모든 제약을 정의한다.

필수 필드: name, description.

description은 Claude가 "이 작업을 누구에게 맡기지?"를 판단하는 근거다. "use proactively" 같은 적극적 문구가 들어가면 위임 빈도가 올라간다. 4주차의 Skill description과 같은 원리다.

도구·권한 필드:

tools: Read, Grep, Glob, Bash      # 허용 목록 (MCP 도구 포함 제외됨)
disallowedTools: Write, Edit       # 거부 목록 (상속된 것에서 뺌)
permissionMode: default            # default/acceptEdits/auto/dontAsk/bypassPermissions/plan
maxTurns: 15                       # 중지 전 최대 턴 수
hooks: { PreToolUse: [...] }       # subagent 라이프사이클 hooks

실행 환경 필드:

model: sonnet              # sonnet/opus/haiku/inherit
skills: [security-review]  # 시작 시 주입할 skills (부모로부터 상속 안 됨)
mcpServers: { ... }        # 이 subagent 전용 MCP
memory: project            # user/project/local: 세션 간 학습
background: false          # true면 항상 background task
isolation: worktree        # 임시 git worktree에서 격리

발표장에서 가장 놀란 부분은 isolation: worktree 였다. 임시 git worktree를 만들어 그 안에서만 작업하고, 변경이 없으면 자동 정리, 변경이 있으면 브랜치를 유지한다. 위험한 리팩토링을 메인 코드베이스를 건드리지 않고 실험할 수 있는 완벽한 안전망이다. 코드 격리의 가장 깔끔한 LLM 패턴이다.

5. 모델 선택: 작업의 가치와 비용을 매칭한다

발표에서 한 슬라이드를 통째로 띄워놓고 강조했다.

모델적합한 용도예시 agent
Haiku ($)단순 수집·보고. 빠르고 저렴git-summary, test-runner, dependency-checker, todo-finder
Sonnet ($$)분석·리뷰. 균형잡힌 기본값api-reviewer, migration-validator, pr-preparer, explain-code
Opus ($$$)정확도 최우선. 오류 비용이 클 때security-auditor, full-stack-debugger, 복잡한 아키텍처 결정

시니어 입장에서 이 한 줄을 모두에게 박았다. "haiku로 충분한 작업에 opus를 쓰지 마라. 그 반대는 더 위험하다." 비용과 정확도의 트레이드오프는 백엔드 시스템 설계에서도 매일 마주하는 결정이다. 모델 라우팅은 그 결정을 코드로 박는 일이다.

모델 인식 우선순위(위가 이김): CLAUDE_CODE_SUBAGENT_MODEL 환경 변수 → 호출별 model 매개변수 → frontmatter model → 주 대화 모델 (기본 inherit).

6. 도구·권한·hook의 세밀한 제어

여기서 한 시간을 가장 길게 썼다. 이 영역이 sub-agent의 안전성을 결정짓는다.

A. tools vs disallowedTools

# 허용 목록: 이것만 쓸 수 있음
tools: Read, Grep, Glob, Bash
 
# 거부 목록: 상속된 전체에서 이것만 뺌
disallowedTools: Write, Edit

둘 다 지정하면 disallowedTools가 먼저 적용되고 그 다음 tools가 남은 풀에서 해결된다.

B. 권한 모드

모드동작
default표준 권한 확인
acceptEdits파일 편집 자동 수락
autoAI 분류기가 각 도구 호출을 평가
dontAsk권한 프롬프트 자동 거부 (명시적 허용은 통과)
bypassPermissions권한 프롬프트 건너뜀 ⚠️
plan읽기 전용 탐색

여기서 발표에서 명시적으로 못 박은 한 줄.

부모 우선 원칙: 부모가 bypassPermissions면 이것이 우선하며 재정의 불가. 부모가 auto면 sub-agent는 auto를 상속하고 frontmatter의 permissionMode는 무시된다. 권한은 항상 위쪽이 이긴다.

C. Hook으로 조건부 규칙: "Bash는 허용, 위험 명령은 차단"

---
name: db-reader
description: Execute read-only database queries
tools: Bash
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./scripts/validate-readonly-query.sh"
---
#!/bin/bash
# validate-readonly-query.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
 
if echo "$COMMAND" | grep -iE '\b(INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|TRUNCATE)\b' > /dev/null; then
  echo "Blocked: Only SELECT queries are allowed" >&2
  exit 2
fi
exit 0

3주차의 Hook 라이프사이클을 그대로 활용한 패턴이다. Sub-agent의 권한을 "허용/거부" 이분법이 아니라 "허용하되 동적으로 검증"으로 확장한다. 시니어 입장에선 너무 익숙한 그림: 인증과 인가의 분리, Spring Security의 @PreAuthorize SpEL 표현식.

D. 지속적 메모리: 세션 간 학습

memory: project   # user / project / local
범위위치사용 시기
user~/.claude/agent-memory/<agent>/모든 프로젝트에서 공유할 학습
project.claude/agent-memory/<agent>/프로젝트별, 버전 관리로 팀 공유 권장
local.claude/agent-memory-local/<agent>/프로젝트별, 체크인 금지

특정 agent가 디버깅을 거듭하면서 "이런 패턴은 거의 항상 N+1 쿼리 문제더라" 같은 학습을 누적한다. 다음 호출에 그 학습이 자동으로 시스템 프롬프트에 주입된다. 이건 단순 메모리가 아니라 agent별 도메인 지식의 누적 이다.

7. 간단한 Agent vs 복잡한 Agent

같은 "agent"라도 디자인 복잡도가 크게 다르다. 발표에서 두 카테고리를 명확히 나누었다.

간단한 Agent ("읽고 보고해줘")

  • frontmatter 3~4개 필드 (name, description, tools, model)
  • 도구 2~3개 (Read, Grep, Glob)
  • 모델 haiku, 프롬프트 20~40줄
  • maxTurns, Hooks, 격리, 메모리 불필요

복잡한 Agent ("분석·판단·수정해줘")

  • frontmatter 6~10개 필드 (+ hooks, maxTurns, isolation, memory)
  • 도구 4~5개
  • 모델 sonnet 또는 opus, 프롬프트 80~150줄
  • maxTurns 15~30, PreToolUse/PostToolUse 안전장치

대표 예시 둘.

간단 Agent: git-summary (haiku)

---
name: git-summary
description: 최근 Git 히스토리를 요약한다. "최근 변경사항", "프로젝트 현황" 등을 물으면 사용.
tools: Bash, Read
model: haiku
---
 
당신은 Git 히스토리 요약 전문가다.
주어진 기간의 커밋 로그를 분석해 핵심 변경사항만 요약해 보고하라.
- 기능 추가는 ✨, 버그 수정은 🐛, 리팩토링은 ♻️로 구분
- 작성자별 활동 비율을 표로 정리

복잡 Agent: migration-validator (sonnet · isolation)

---
name: migration-validator
description: 마이그레이션 파일의 안전성·롤백 가능 여부·정확성을 적용 전에 검증.
tools: Read, Grep, Glob, Bash
model: sonnet
maxTurns: 20
isolation: worktree
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: |
            INPUT=$(cat)
            COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
            if echo "$COMMAND" | grep -iE '\b(DROP\s+DATABASE|TRUNCATE|DELETE\s+FROM)\b' > /dev/null; then
              echo "차단: 파괴적인 DB 명령어가 감지되었습니다." >&2
              exit 2
            fi
            exit 0
---

이 예시 한 장에 sub-agent의 고급 기능 세 가지가 모두 들어 있다.

  1. isolation: worktree: 임시 git worktree에서 실행. 변경 없으면 자동 정리.
  2. PreToolUse Hook: DROP DATABASE, TRUNCATE, DELETE FROM 사전 차단.
  3. exit 2 + stderr: 차단 메시지가 Claude에게 그대로 전달되어 다음 행동을 보정.

security-auditormodel: opus까지 끌어올린다. 보안에서 false negative는 치명적이므로 정확도 최우선. full-stack-debuggermemory: project까지 켜서 이전 디버깅 경험을 누적시킨다. agent의 복잡도는 작업의 위험도·반복성·정확도 요구에 비례해야 한다.

8. 호출 방법: 4가지 패턴

확신도와 영속성이 다른 네 가지 호출 방식.

① 자연어: Claude 자동 위임

마이그레이션 파일 검증해줘
# → description을 읽고 migration-validator 자동 선택

② @-mention: 특정 agent 보장

@"security-auditor (agent)" src/auth/ 보안 감사해줘
# → 반드시 이 agent가 실행

--agent: 세션 전체를 agent로

claude --agent code-reviewer
# 주 스레드 자체가 리뷰어 모드로 동작

④ 병렬 실행

security-auditor와 api-reviewer를 동시에 돌려서 결과 알려줘
# → 두 agent가 병렬로 실행되고 결과가 합쳐서 메인으로 돌아옴

발표에서 한 가지 작은 트릭을 알려줬다: 체인 패턴. 결과를 다음 sub-agent의 입력으로 넘기는 순차 파이프라인.

code-reviewer로 성능 이슈 찾고, optimizer로 고쳐줘

Claude가 첫 결과를 두 번째 agent의 입력으로 전달한다. 한 번의 사용자 메시지로 "탐색 → 분석 → 수정"의 3단 파이프라인이 완성된다.

9. 실행 모드: Foreground vs Background

🖥️  Foreground (기본)
- 주 대화를 차단
- 권한 프롬프트가 사용자에게 실시간 전달
- AskUserQuestion 호출이 사용자에게 전달됨
- 대화형 디버깅·권한 확인에 유리
 
🌙  Background
- 주 대화와 동시 실행
- 시작 전 필요한 권한을 일괄 요청
- AskUserQuestion 호출은 실패 (subagent는 계속 진행)
- 장기 실행 작업·병렬 조사에 유리

Ctrl+B로 실행 중 언제든 background로 이동 가능. frontmatter background: true로 항상 background. 환경 변수 CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1로 전면 비활성화.

실전 팁: Background가 권한 부족으로 실패하면, foreground에서 새로 시작해 대화형으로 재시도하라. 대부분의 권한 이슈가 이 방식으로 해결된다.

10. 설계 원칙 7가지와 제한사항 7가지

발표 막바지에서 모두에게 박은 체크리스트.

모범 사례 7가지

  1. 최소 권한 원칙: 읽기 전용 agent라면 tools: Read, Grep, Glob만.
  2. 비용 대비 모델 선택: haiku로 충분한 작업에 opus 금지.
  3. Hook으로 안전장치: 위험 명령 exit 2, 주의 명령 exit 0 경고.
  4. Description이 핵심: 모호한 "코드 리뷰" 대신 "REST API의 일관성·보안·모범사례 리뷰. API 라우트 수정 시 자동 사용".
  5. 한 Agent = 한 책임: 만능 agent는 아무것도 잘 못한다.
  6. 프롬프트는 한글로: 한글 컨텍스트에서 자동 위임 판단·응답·로그가 모두 정렬.
  7. 팀 Agent는 버전 관리: 프로젝트 agent는 .claude/agents/에 커밋.

제한사항 7가지

  • 중첩 금지: sub-agent는 다른 sub-agent를 스폰할 수 없음 (무한 재귀 방지). 중첩 위임이 필요하면 Skills나 주 대화 체인 사용.
  • 시스템 프롬프트 대체: --agent로 주 세션 실행 시 sub-agent 프롬프트가 기본 Claude Code 시스템 프롬프트를 완전히 대체. CLAUDE.md는 여전히 로드됨.
  • Skills는 상속 안 됨: sub-agent는 부모로부터 skills를 상속하지 않음. skills 필드에 명시해야 주입.
  • bypassPermissions 부모 우선: 부모가 이 모드면 sub-agent는 override 불가.
  • 플러그인 sub-agent 제약: hooks, mcpServers, permissionMode 미지원. 이 기능이 필요하면 .claude/agents/로 복사.
  • 자동 로드 아님: 수동으로 파일 추가 시 세션 재시작 또는 /agents로 리로드 필요.
  • 병렬 실행 주의: 결과가 모두 주 대화로 돌아오므로 많이 띄우면 컨텍스트가 빠르게 소모됨.

11. 시니어의 시선으로 정리하는 인사이트

① Sub-Agent는 마이크로서비스 아키텍처의 LLM 컨텍스트 버전이다

격리된 책임, 명시적 인터페이스(description), 최소 권한, 비용/정확도 트레이드오프, 병렬 실행. 백엔드 시스템 설계에서 우리가 30년간 해온 일이 LLM 컨텍스트 위에 다시 그려진 것이다. 익숙한 원리를 새로운 무대에 적용하는 일, 시니어 입장에서 이만큼 즐거운 학습은 드물다.

② "한 Agent = 한 책임"이 만능 agent보다 항상 좋다

만능 do-everything-agent는 description이 모호해져 자동 위임도 안 잡히고, 도구 권한도 넓어져 안전성도 떨어진다. SRP는 LLM에서도 절대적이다. 시니어 코드 리뷰의 가장 흔한 코멘트가 "이 클래스는 너무 많은 일을 한다"인데, agent에서도 똑같은 문제가 그대로 재현된다.

③ 권한과 안전성은 frontmatter 한 줄이 아니라 layered defense다

tools 허용 목록만으로는 부족하다. permissionMode로 권한 모드를 한 번 더 좁히고, PreToolUse Hook으로 동적 검증을 한 번 더 걸고, maxTurns로 무한 루프를 막고, isolation: worktree로 작업 자체를 격리한다. 방어는 한 층이 아니라 여러 층이다. 보안 시스템의 보편 원리.

④ 메모리는 agent의 "장기 기억"이다

memory: project로 agent가 세션 간 학습을 누적하게 만들면, 그 agent는 시간이 지날수록 그 프로젝트에 특화된 전문가가 된다. full-stack-debugger가 한 달 동안 우리 프로젝트의 이슈를 보고 나면, 그 agent는 우리 시스템의 흔한 버그 패턴을 알게 된다. 이건 그 자체로 자산이다.

⑤ 실행 모드와 재개를 알면 sub-agent의 진짜 활용이 시작된다

Foreground/Background 전환(Ctrl+B), 재개(Resume), 트랜스크립트 위치(~/.claude/projects/{p}/{s}/subagents/)를 알면 sub-agent가 한 번 쓰고 버리는 일회성 도구가 아니라 장기 실행 가능한 워커가 된다. 백엔드 시각에서 보면 단순 RPC가 아니라 장기 워크플로우 엔진으로 격상되는 것이다.

마무리: 시리즈가 끝난 자리에서 남는 것

이 글로 5주차 시리즈가 끝났다. 발표를 마치고 회의실을 정리하면서, 가장 마음에 남는 한 줄을 다시 떠올렸다.

본인의 학습이 가장 깊어지는 자리는 결국 그것을 다른 사람에게 설명하는 무대다.

다섯 번의 발표를 준비하며 내가 가장 크게 얻은 건 Claude Code에 대한 깊은 이해 그 자체가 아니라, "이 도구가 도대체 어떤 그림 위에서 동작하는지"를 한 번 더, 또 한 번 더 단단하게 다지는 시간이었다. 매 발표가 끝날 때마다 받은 질문 하나하나가 내 멘탈 모델의 빈 구멍을 메꿔주었다. 챕터원들의 인사이트를 넓혀주는 자리에 섰다고 생각했지만, 정작 가장 많이 배운 사람은 발표자였다.

마지막 발표가 끝나고 누군가가 이런 말을 했다.

"5주 동안 Claude Code의 부품을 하나씩 분해했는데, 마지막 주차의 sub-agent까지 오니까 결국 모든 부품이 한 그림 안에서 다시 조립되는 느낌이에요."

정확히 그게 발표자가 듣고 싶었던 가장 좋은 피드백이었다. 1주차의 "단일 API 요청" 그림, 2주차의 "Memory의 두 슬롯", 3주차의 "라이프사이클 hook", 4주차의 "3단계 Skill 로딩", 5주차의 "격리된 sub-agent의 위임". 이 다섯 장이 머릿속에서 하나의 큰 그림으로 합쳐지는 자리. 그 자리에 챕터원들과 함께 서 있었다는 사실이 시리즈를 정리한 발표자에게 가장 큰 보람이다.

다음 시리즈를 무엇으로 잡을지는 아직 정하지 않았다. 다만 한 가지는 분명하다. 사내에 전파할 수 있는 기회가 또 한 번 주어진다면, 그 시간은 결국 나 자신을 가장 단단하게 다지는 시간으로 돌아온다는 것. 이 명제만으로 다음 시리즈를 준비할 동기가 충분하다. 시리즈를 끝까지 같이 와준 챕터원들에게, 그리고 매주 발표를 준비하며 나 자신과 한 번씩 더 만나게 해준 이 무대에, 진심으로 감사하다.