Claude Code로 블로그 작업 자동화를 위한 Skill 시스템을 구축해왔어요. 포스트 관리, Git 워크플로우, 빌드 체크 등 다양한 Skill이 있는데, 문제는 어떤 Skill이 발동되어야 하는지 매번 암묵적으로 처리되다 보니 누락되는 경우가 생겼습니다.

특히 복잡한 작업에서 여러 Skill이 순차적으로 실행되어야 할 때, 하나가 빠지거나 순서가 뒤바뀌는 일이 발생했어요. 이를 해결하기 위해 subagent를 활용한 Skill 평가 시스템을 도입했습니다.

이 글에서는 다음 내용을 다룹니다.

  • subagent를 활용한 Skill 활성화 여부 평가 시스템 구축
  • 평가 결과를 summary.json에 저장하여 추적 가능하게 만들기
  • 매 요청마다 자동으로 Skill 평가가 실행되도록 CLAUDE.md 설정

참고로 Reliable Skill Activation에서 언급했던 스킬 최적화의 내용입니다.

기존 CLAUDE.md는 이런 형태였어요.

CLAUDE.md
## 최우선 규칙 1. **Push 승인 필수**: 커밋 목록 보여주고 명시적 허락 후 푸시 2. **응답 종료 시**: `.claude/summary.json` Edit (request/response 한글 요약) ## Skills - git-workflow: 커밋/PR/push - post-management: 포스트/MDX/검사 - dev-workflow: 빌드/타입/린트 - skill-creator: 스킬 생성 - work-documentation: 작업하면서 포스트 작성

문제가 보이시나요? main agent가 모든 판단을 직접 해야 합니다. 어떤 Skill을 실행할지, summary.json에 뭘 기록할지, 모든 책임이 main agent에 집중되어 있죠.

사용자 요청main agent (모든 것을 직접 처리)Skill 판단"어떤 Skill을 실행하지?"작업 수행"코드 수정..."요약 작성"summary.json 업데이트"Context Window (점점 쌓임)대화 내역코드 내용에러 로그...CLAUDE.md 규칙들 (context 끝으로 밀림...)→ Skill 발동 누락, 순서 오류 발생문제 발생Skill 누락 | 요약 누락 | 순서 오류 | 일관성 부족

main agent 단독 처리 방식의 한계

  1. Skill 발동 누락: 복잡한 요청에서 어떤 Skill이 필요한지 판단을 잊거나 틀림
  2. 순서 오류: 여러 Skill을 실행해야 할 때 의존 관계를 무시하고 잘못된 순서로 실행
  3. 일관성 부족: 같은 요청에도 매번 다르게 판단

main agent의 context window에는 작업 코드, 대화 내역, 에러 로그 등이 쌓이면서 CLAUDE.md에 적어둔 규칙들이 밀려나게 됩니다. 결과적으로 부수적인 규칙들이 무시되는 일이 빈번했어요.

핵심 아이디어는 단순합니다. main agent가 모든 것을 처리하는 대신, 특정 역할을 전담하는 subagent를 두고 위임하자.

변경 전후 비교
기존: main agent가 Skill 판단 + 작업 수행 + 기록 개선: main agent → skill-manager에게 판단 위임 → 결과에 따라 작업

subagent 아키텍처를 구성하면서 가장 헤맨 문제를 먼저 공유합니다.

Claude Code에서 subagent는 또 다른 subagent를 호출할 수 없습니다.

공식 문서 | GitHub Issue #19077

Claude Code 서브에이전트는 메인 대화가 한 번 호출해서 일을 시키는 단일 레벨 워커처럼 동작해요. 공식 문서에서도 "Subagents cannot spawn other subagents (no nesting)"라고 명시하고 있습니다.

계층적/중첩 워크플로우가 필요하면 메인 대화에서 여러 서브에이전트를 순차·병렬로 조합하거나, Skill을 이용해 한 서브에이전트 안에서 단계적 작업을 하도록 설계하는 방식을 권장하고 있어요.

사용자 요청main agent (opus)1. skill-manager 호출 (매 요청 시)2. 평가 결과에 따라 SKILL.md 읽고 실행3. 작업 수행4. summary.json 직접 업데이트 (응답 종료 시)skill-manager (통합)(sonnet)1. summary.json에서 skill_info 확인2. skill_info 없으면 → SKILL.md 직접 스캔3. 사용자 요청 분석하여 skill_eval 직접 평가4. summary.json에 결과 저장summary.jsonskill_info: 각 Skill 메타데이터skill_eval: 활성화 여부 평가 결과request: 사용자 요청 요약response: Claude 응답 요약started_at: 시작 시간평가 결과: activate: true/falsemain agent가 결과에 따라 SKILL.md 실행
Agent역할모델호출 시점
skill-managerskill_info 초기화 + skill_eval 평가를 직접 수행sonnet매 요청

skill-manager는 skill 시스템의 진입점으로, skill_info 초기화와 skill_eval 평가를 직접 수행합니다.

처음에는 skill-manager → skill-initiator → skill-evaluator 구조로 설계했으나, subagent 중첩 불가 제약 때문에 skill-manager가 모든 작업을 직접 처리하도록 통합했어요.

실행 절차
1. summary.json에서 skill_info 필드 확인 2. skill_info 없으면 → SKILL.md 직접 스캔하여 초기화 3. 사용자 요청 분석하여 skill_eval 직접 평가 4. summary.json에 결과 저장

모든 SKILL.md를 스캔하여 skill 정보를 summary.json에 캐싱합니다.

  • 모든 SKILL.md의 frontmatter 읽기 (name, description, trigger, activate_when)
  • summary.json의 skill_info 필드에 저장
  • 최초 1회만 실행되므로 이후 요청에서는 파일 I/O가 줄어들어요

summary.json의 skill_info를 기반으로 어떤 Skill을 활성화해야 하는지 판단합니다.

  • summary.json의 skill_info 읽기 (개별 SKILL.md를 읽지 않음)
  • 사용자 요청의 의도와 매칭
  • summary.json의 skill_eval 필드에 결과 저장

sonnet 모델을 사용하는 이유는 단순한 패턴 매칭 작업이라 빠르고 저렴한 모델로 충분하기 때문이에요.

의도 파악이 핵심입니다. 단순히 키워드 매칭만 하면 "포스트 확인해줘"라는 요청에 post-management를 활성화하지 못할 수 있어요. "확인"이라는 단어가 trigger에 없거든요.

디렉토리 구조
.claude/ ├── agents/ │ └── skill-manager.md # skill_info 초기화 + skill_eval 평가 ├── skills/ │ ├── git-workflow/ │ │ └── SKILL.md │ ├── post-management/ │ │ └── SKILL.md │ ├── dev-workflow/ │ │ └── SKILL.md │ └── ... └── summary.json # skill_info, skill_eval 저장

각 Skill은 frontmatter에 평가에 필요한 정보를 담아요.

SKILL.md
--- name: post-management description: 블로그 포스트 작성, 검사, 수정을 지원합니다. trigger: 포스트, 글, MDX, 블로그, 맞춤법, 검사, 새 글 activate_when: 블로그 포스트 생성, 검사, 수정 요청 시 ---
  • trigger → 키워드 기반 매칭 (빠른 필터링)
  • activate_when → 의도 기반 매칭 (정확한 판단)

summary.json
{ "status": "in_progress", "request": "", "response": "", "started_at": 1769410966, "skill_info": { "git-workflow": { "description": "Git 커밋, PR, push 작업을 처리합니다.", "trigger": ["커밋", "commit", "PR", "push"], "activate_when": "Git 관련 작업 요청 시" }, "post-management": { "description": "블로그 포스트 관리를 지원합니다.", "trigger": ["포스트", "글", "MDX"], "activate_when": "포스트 관련 작업 요청 시" } ... }, "skill_eval": { "git-workflow": { "activate": false, "reason": "Git 작업 요청 없음" }, "post-management": { "activate": true, "reason": "포스트 수정 요청" }, ... } }

기존 20줄 가까이 되던 규칙들이 핵심만 남았습니다.

CLAUDE.md
# CLAUDE.md ## 최우선 규칙 1. **Push 승인 필수**: 커밋 목록 보여주고 명시적 허락 후 푸시 2. **응답/질문 종료 시**: summary.json Edit (request/response 한글 요약) ## Skill 실행 **매 요청 시:** 1. skill-manager 에이전트 호출 2. summary.json의 `skill_eval`에서 `activate: true`인 skill의 SKILL.md 읽고 실행 **실행 순서** (복수 skill): 1. 코드/콘텐츠: post-management, skill-creator, work-documentation 2. 검증: dev-workflow 3. Git: git-workflow (항상 마지막) 4. 공유: linkedin-post (Git 이후)

main agent가 기억해야 할 것은 "언제 누구를 부를지"만 남았어요. 구체적인 실행 로직은 각 subagent가 알아서 처리합니다.

  1. Skill 발동 누락 해결: skill-manager가 skill_info 캐시 기반으로 명시적으로 판단
  2. 일관성 확보: 같은 로직이 매번 동일하게 실행
  3. 파일 I/O 최소화: skill_info가 한 번 초기화되면 SKILL.md를 다시 읽지 않음

  • 관심사 분리: 각 agent가 하나의 역할만 담당
  • 재사용성: agent 정의를 다른 프로젝트에서도 사용 가능
  • 디버깅 용이: 문제가 생기면 해당 agent만 확인하면 됨
  • 비용 효율: 단순 작업은 sonnet으로 처리
  • 캐싱 전략: skill_info를 summary.json에 캐싱하여 반복 스캔 방지

이런 아키텍처를 만들었음에도 불구하고 다시 되돌아갔습니다. summary-manager의 역할을 main agent로 회수했어요.

subagent 기반 시스템을 운영하면서 한 가지 비효율을 발견했어요. summary-manager의 비용이 하는 일에 비해 과도했습니다.

summary-manager는 매 응답 종료 시 호출되어 summary.json의 requestresponse 필드를 업데이트하는 역할이에요. JSON 필드 2개를 수정하는 단순한 작업인데, 실제 소비량은 이랬습니다.

항목소비량
토큰~7.3k / 호출
시간~16초 / 호출
Tool 사용4회 (Read → Edit × 2 → Read 검증)

커밋 메시지 하나 추천받는 데 summary 업데이트에만 7.3k 토큰과 16초가 추가되는 셈이죠.

subagent는 매 호출마다 새로운 컨텍스트를 구성합니다.

  1. 시스템 프롬프트 로드
  2. 에이전트 정의(.claude/agents/summary-manager.md) 해석
  3. 도구 사용 권한 설정
  4. 실제 작업 수행 (Read → Edit × 2 → Read 검증)

이 중 1~3은 작업 복잡도와 무관한 고정 비용이에요. skill-manager처럼 여러 Skill의 활성화 여부를 판단하는 작업에는 subagent의 독립된 컨텍스트가 정당화됩니다. 하지만 "필드 2개 수정"이라는 단순 작업에는 과도한 구조였어요.

subagent 분리가 만능은 아닙니다. 작업의 복잡도가 subagent의 고정 오버헤드를 정당화할 만큼 높은지 따져봐야 해요.

summary 업데이트를 다시 main agent가 직접 처리하도록 변경했습니다.

CLAUDE.md
# 변경 전 2. **응답/질문 종료 시**: summary-manager 에이전트 호출 # 변경 후 2. **응답/질문 종료 시**: summary.json Edit (request/response 한글 요약)

main agent는 이미 대화 컨텍스트를 갖고 있으므로 별도의 Read 없이 Edit 1~2회로 바로 수정할 수 있어요. 검증을 위한 추가 Read도 필요 없습니다.

Beforemain agentsummary-manager (subagent)고정 비용1. 시스템 프롬프트 로드2. 에이전트 정의 해석3. 도구 권한 설정실제 작업4. Read → Edit × 2 → Read 검증7.3k tokens · 16sAftermain agentEdit 1~2회summary.json0.5k tokens · 2s토큰 93% · 시간 87% 절감

항목변경 전 (subagent)변경 후 (main agent)절감
토큰~7.3k~0.5k~93%
시간~16초~2초~87%
Tool 호출4회1~2회~62%

이번 경험을 통해 subagent 도입 기준이 명확해졌어요.

  • 판단이 필요한 작업: skill-manager처럼 여러 조건을 비교하고 의도를 파악해야 할 때
  • main agent의 컨텍스트를 보호해야 할 때: 대량의 파일 스캔이나 탐색처럼 컨텍스트를 오염시키는 작업
  • 독립적인 실행 로직이 있을 때: 복잡한 절차를 캡슐화해야 할 때

  • 단순 CRUD 작업: summary-manager처럼 필드 몇 개를 수정하는 수준
  • 이미 컨텍스트를 갖고 있을 때: 대화 내용을 요약하는 작업은 main agent가 이미 알고 있는 정보
  • 고정 오버헤드가 실제 작업량을 초과할 때: subagent 초기화 비용이 작업 자체보다 클 때

AI agent 시스템도 결국 소프트웨어 설계 원칙을 따릅니다. 단일 책임 원칙(SRP), 의존성 주입 같은 검증된 패턴들이 그대로 적용돼요.

  1. 책임 분리의 중요성: main agent에 모든 것을 맡기면 context가 쌓이면서 규칙이 무시됩니다. 특정 역할을 독립적인 subagent로 분리하면 일관성이 확보돼요.

  2. subagent의 제약: Claude Code에서 subagent는 또 다른 subagent를 호출할 수 없어요. 처음에는 skill-manager → skill-initiator/skill-evaluator 구조로 설계했으나, 이 제약 때문에 skill-manager가 모든 작업을 직접 수행하도록 통합했습니다. 설계 시 플랫폼 제약을 먼저 파악하는 것이 중요해요.

  3. 캐싱 전략: 반복적으로 필요한 정보(skill 메타데이터)는 summary.json에 캐싱하여 파일 스캔을 최소화합니다. skill-manager가 skill_info 존재 여부를 확인하고, 없을 때만 SKILL.md를 스캔하는 구조예요.

  4. 의도 기반 매칭: 단순 키워드 매칭보다 사용자 요청의 의도를 파악하는 것이 중요합니다. trigger 키워드뿐만 아니라 activate_when 조건도 함께 고려하죠.

  5. subagent 도입 기준: 모든 역할을 subagent로 분리하는 것이 답은 아닙니다. 작업의 복잡도가 subagent의 고정 오버헤드를 정당화할 수 있는지 따져봐야 해요. summary-manager를 main agent로 회수하면서 토큰 93%, 시간 87%를 절감한 사례가 이를 잘 보여줍니다.