스킬은 SKILL.md 한 장으로 시작하라. progressive disclosure는 폴더 구조가 아니라 Claude가 실제로 읽는 context를 통제하는 원칙이다.
Claude Skill을 SKILL.md 한 장으로 시작하는 이유
스킬은 SKILL.md 한 장으로 시작하라. scripts/·templates/·references/는 관찰된 실패에서만 추가하라. Anthropic이 말하는 progressive disclosure는 폴더 구조가 아니라 Claude가 실제로 읽는 context를 통제하는 원칙이다. 이 글은 그 원칙을 공식 문서·정량 데이터·실측 가능한 3계층 케이스 스터디로 뒷받침한다.
처음 스킬을 만들면 한 가지 풍경이 반복된다. skill-creator를 돌리거나 공식 문서의 예시 디렉터리를 그대로 베끼면 다음 폴더가 자동으로 생긴다.
my-skill/
├── SKILL.md
├── scripts/
├── templates/
└── references/
빈 폴더 셋. 우리는 이 폴더들을 언젠가 채울 것이라고 가정한다. templates/에 출력 양식을, references/에 도메인 문서를, scripts/에 검증 자동화를. 그래야 "진짜 스킬" 같다고 느낀다.
그런데 정작 SKILL.md 본문을 적기 시작하면 30줄도 안 돼서 끝난다. templates/에 넣을 양식은 머릿속에 떠오르지 않고, references/에 분리할 문서는 아직 없다. 그 순간 본능적으로 "이거 다 빼두는 게 맞지 않을까?"라는 망설임이 든다.
이 글은 그 망설임이 옳다는 주장이다. 그리고 그 주장이 단순한 개인 취향이 아니라 Anthropic이 명시한 핵심 설계 원칙(progressive disclosure)과 정확히 일치한다는 점을 공식 문서·정량 데이터·실측 가능한 3계층 케이스로 보여줄 것이다.
핵심 문장 하나를 미리 박아두자: 폴더가 있어서 비싼 게 아니다. Claude가 실제로 읽은 context가 비용이다.
Agent Skills overview는 스킬을 세 단계로 로드한다고 명시한다.
| Level | 언제 로드 | 토큰 비용 | 내용 |
|---|---|---|---|
| 1. 메타데이터 | 시작 시 항상 | 스킬당 ~100 토큰 | YAML frontmatter의 name과 description |
| 2. SKILL.md 본문 | 스킬이 트리거됐을 때 | 5,000 토큰 이하 권장 | instruction 본문 |
| 3. 번들 리소스 | 필요할 때만 | 액세스 전까지 0 | references·scripts·assets |
여기서 가장 자주 잊기 쉬운 사실: Level 3는 액세스되지 않는 한 토큰을 전혀 쓰지 않는다. 공식 문서의 표현 그대로 옮기면 "There's no context penalty for bundled content that isn't used."
이게 progressive disclosure의 기계적 정의다. Anthropic 엔지니어링 블로그는 이 원칙을 더 단정적으로 쓴다.
"Progressive disclosure is the core design principle that makes Agent Skills flexible and scalable."
여기까지만 들으면 "그럼 폴더를 미리 만들어 둬도 되는 것 아닌가? 어차피 안 읽히면 0이니까"라고 생각하기 쉽다. 다음 챕터가 그 추론을 무너뜨린다.
문제는 Level 2다. Claude Code skills 문서에는 한 줄이 묻혀 있다.
"Once a skill loads, its content stays in context across turns, so every line is a recurring token cost."
해석: 스킬이 한 번 로드되면 SKILL.md 본문은 그 turn뿐 아니라 이후 turn 내내 컨텍스트에 머문다. 다음 대화에서 "방금 한 작업 정리해줘"라고만 해도 SKILL.md 전체가 계속 따라온다. 모든 줄이 반복 비용이다.
여기에 두 가지 정량 사실을 얹는다.
| 항목 | 값 | 출처 |
|---|---|---|
| Level 1 메타데이터 비용 | ~100 토큰/스킬 | Overview |
| Level 2 SKILL.md 권장 상한 | 5,000 토큰 / 500 줄 | Best Practices |
| Level 3 리소스 비용 | 0 (액세스 전까지) | Overview |
| Auto-compact 시 스킬 재첨부 budget | 25,000 토큰 합산 | Code 문서 |
| 스킬당 listing 오버헤드 | 75–150 토큰 | claudefa.st |
| 기본 listing budget (200k 모델) | ~2,000 토큰 (1%) | claudefa.st |
| 1% budget으로 description 유지 가능 스킬 수 | 15–25개 | claudefa.st |
| description + when_to_use 합산 cap | 1,536자 | Code 문서 |
Best Practices는 이 상황을 한 문장으로 요약한다.
"The context window is a public good."
그리고 더 결정적인 한 줄.
"Claude is already very smart. Only add context Claude doesn't already have."
스킬은 Claude가 모르는 것만 적는다. 이미 아는 코딩 상식·일반 PDF 설명·라이브러리 소개는 토큰을 정당화하지 못한다.
여기까지는 "본문을 짧게 써라"는 이야기였다. 그런데 처음부터 폴더 구조를 만들어 두는 것에는 별개의 함정이 있다.
스킬에 scripts/setup.py 같은 파일을 미리 두면, SKILL.md 본문에 자연스럽게 다음 문구가 들어간다.
초기화는 다음 스크립트를 실행하라:
python scripts/setup.py
이 문구가 적히는 순간, Claude는 어떤 요청에서도 그 스크립트로 우회한다. 다른 방식이 더 단순한 상황(예: 사용자가 이미 환경을 셋업해놓은 경우)에서도. 우리가 "필요할 때만 쓰면 되니까"라며 미리 둔 도구가, 알고 보니 Claude의 디폴트 경로가 된다.
여기서 어긋난다. progressive disclosure의 핵심은 "필요할 때만 읽힌다"인데, 본문에 경로를 미리 박아두는 순간 그 분기는 조건부가 아니라 고정된다. Best Practices의 "Avoid offering too many options" 절은 이 함정을 정확히 짚는다 — 디폴트 하나와 *예외용 비상구(escape hatch)*만 적어라.
또 하나의 안티패턴은 Claude가 직접 짤 수 있는 코드를 미리 스크립트로 만드는 것이다. 30줄짜리 분석 스크립트는 Claude가 매번 새로 짜는 게 더 유연하다. 미리 만든 스크립트는 결정론이 진짜로 필요한 경우(DB 마이그레이션, OOXML pack/unpack, 매번 같은 결과를 보장해야 하는 검증)에만 가치 있다.
Best Practices에는 한 절이 따로 있다.
"Build evaluations BEFORE writing extensive documentation. This ensures your Skill solves real problems rather than documenting imagined ones."
번역하면: 문서를 쓰기 전에 평가 시나리오부터 만들어라.
이 원칙은 Claude A / Claude B 패턴으로 구체화된다. 한 인스턴스(Claude A)와 스킬을 다듬고, 다른 fresh 인스턴스(Claude B)가 실제 작업에서 그 스킬을 사용한다. Claude B가 어디서 실패하는지를 보고 그 실패에서만 확장한다. 가정이 아니라 관찰에서.
이 글에는 그 원칙을 그대로 적용한 3계층 케이스가 있다. advanced-skill/.claude/skills/ 안에 셋이 차례로 자리잡고 있다.
daniel-plan (SKILL.md 1장, 24줄)---
name: daniel-plan
description: 사용자가 던진 작업 계획·구현 의도를 짧게 점검해 누락된 검증 단계, 숨은 가정, 역할 분리 문제를 골라낸다. 사용자가 "이 계획 검토", "이 작업 어떻게 시작", "이대로 진행해도 될까", "계획 점검" 같은 표현을 쓰거나, 작업을 시작하기 전 짧은 sanity check를 원할 때 트리거.
---
# daniel-plan
사용자가 제시한 계획을 *짧게* 점검한다. 길게 늘이지 않는다.
## 점검 순서
1. **누락된 검증 단계**: 테스트, 롤백, 사전 백업, 회귀 영향 중 빠진 것
2. **숨은 가정**: 사용자가 당연하다고 여기는 외부 상태 중 명시되지 않은 것
3. **역할 분리**: 한 단계에 *결정*과 *실행*이 섞여 있어 되돌리기 어려운 부분
## 출력 형식
- 항목 3개 이하. 각 한 문장.
- "이런 것도 좋겠다" 류 제안은 금지. *지금 빠진 것*만.
## 금지
사용자가 이미 적은 내용 복창, 일반론적 조언, 점검 항목 외 영역 확장.이게 그 자체로 작동하는 스킬이다. 외부 명령 0개, references 0개, scripts 0개. 그럼에도 "이 계획 점검해줘"라는 한국어 요청에 디스크립션의 키워드가 매칭돼 자동 발동된다.
summarize-pr-risks (dynamic context injection, 33줄)스크립트를 만들지 않고도 현재 작업 맥락을 SKILL.md에 끌어올 수 있다. ! 백틱 한 줄이면 된다.
---
name: summarize-pr-risks
description: 현재 작업 트리의 git diff를 읽고 PR 머지 전 위험 신호 2–4개를 짧게 보고한다. 누락 테스트, 하드코딩, 에러 처리 공백, 회귀 가능성 우선 검출. 사용자가 "이 변경 위험한 거 없나", "PR 리뷰 전 점검", "지금 diff 위험도" 같은 표현을 쓸 때 트리거.
allowed-tools: Bash(git diff *)
---
# summarize-pr-risks
## 현재 변경
!`git diff --no-ext-diff HEAD`
## 점검 기준 (우선순위 순)
1. **누락 테스트**: 새 로직·새 분기·새 API 표면에 대응 테스트가 빠짐
2. **하드코딩**: URL, 타임아웃, 매직 넘버, 자격증명이 코드에 직접 박힘
3. **에러 처리 공백**: 외부 호출·파일 IO·파싱에 예외 처리 부재
4. **회귀 가능성**: 기존 public 시그니처·DB 스키마가 바뀌었는데 호출자 영향 명시 없음
## 출력 형식
- 위험 신호 **2–4개**만 골라 한 줄씩. `[유형] 파일:줄번호 — 한 문장`
- diff가 비었으면 "변경 없음"으로 끝낸다. !git diff --no-ext-diff HEAD`` 한 줄은 스킬 호출 이전에 실행되고, 그 출력이 본문에 인라인된다. Claude는 이미 채워진 diff를 본다. --no-ext-diff는 사용자가 difft·delta 같은 external diff를 global git config에 설정해 둔 경우에도 일관된 unified diff를 받기 위한 안전장치다.
검증용 임시 repo(/tmp/skill-test-pr-risks)에 — 회귀 가능성 1, 누락 테스트 1, 하드코딩 1, 에러 처리 공백 1, 총 4개의 위험 신호를 의도적으로 심은 변경 — 을 만들고 /summarize-pr-risks를 호출한 결과:
- [하드코딩] src/payments.py:16 — sk_live_ 라이브 API 키가 소스에 박혀 있음.
즉시 회수·환경변수로 분리 필요.
- [회귀] src/payments.py:7 — process_payment 반환 타입이 bool → dict로 변경됨.
기존 호출자(if process_payment(...) 형태)가 모두 truthy dict로 평가되어
실패도 성공처럼 보일 수 있음.
- [누락 테스트] src/payments.py:13 — 새 refund_payment 분기(전액/부분, 실패 경로)에
대응 테스트 없음.
- [에러 처리 공백] src/payments.py:26 — refund_payment는 status_code 검증 없이
response.json()["refunded"]를 바로 인덱싱. 비-200 응답·키 누락 시
KeyError/JSONDecodeError.
심어둔 4개 모두 정확한 유형·정확한 파일·근사 라인 번호로 잡혔다. 특히 "회귀" 항목은 단순히 타입이 바뀌었다에 그치지 않고 기존 호출자가 truthy dict로 평가돼 실패도 성공처럼 보일 수 있다까지 추론했다. 30줄짜리 SKILL.md, 외부 스크립트 0개, dynamic context injection 한 줄만으로 이 정도 진단이 나온다.
여기서 두 가지는 분리해서 봐야 한다.
(A) 작성 위치와 실행 위치의 분리. 이 SKILL.md 파일은 advanced-skill/.claude/skills/에 작성하지만, 입력이 git diff이므로 실행 검증은 변경사항이 있는 임의의 git repo에서 한다. 검증 방법은 셋 중 택1:
.claude/skills/summarize-pr-risks/로 복사~/.claude/skills/summarize-pr-risks/에 personal skill로 설치--add-dir /path/to/advanced-skill로 추가 디렉터리 로드advanced-skill 폴더 자체가 git repo여야 한다는 뜻이 아니다.
(B) 권한과 정책 footnote. allowed-tools: Bash(git diff *)는 스킬 활성화 중 해당 명령을 사전 승인하는 좁은 권한이지, 다른 도구를 막는 제한은 아니다. 또 settings에 disableSkillShellExecution: true가 켜져 있으면 ! 치환 자체가 실행되지 않고 [shell command execution disabled by policy]로 대체된다. diff가 비어 보이는데 이유를 모르겠다면 이 설정부터 확인하자.
이 두 스킬이 §6의 측정 대상이고 §7의 "그래도 확장이 가치 있는 경우" 비교군이다.
여기서 한 번 멈춰서 데이터를 본다. 왜 §7로 가기 전에 측정을 끼우는가? 확장은 상상한 미래가 아니라 관찰된 현재에서 출발해야 하기 때문이다. 측정이 §6에 있으면, §7의 "이런 경우만 확장"은 측정 결과에 따른 결정이 된다.
| 축 | 무엇을 보는가 | 도구 |
|---|---|---|
| 스킬 효과 | 기대 동작 충족률 | 수동 채점, baseline 대비 |
| 토큰 경제 | SKILL.md 줄 수 / listing 점유 / turn당 input·output 토큰 | wc -l / /doctor / /context / /cost |
| 트리거 정확도 | 자연어 요청 → 의도한 스킬 자동 발동 비율 | 10개 트리거 문장 사전 정의 |
| 점진 확장 효과 | 계층1 → 계층3으로 references/ 비용 변화 | A/B/C 비교 세션 |
/cost는 input/output 토큰 누적, /context는 현재 turn의 context breakdown — 둘은 상호 보완이고 측정 시 둘 다 기록한다.
처음에는 계층1·2·3을 한 표에서 비교하려 했다. 그런데 계층 2 (summarize-pr-risks)는 git diff 출력 크기에 따라 토큰이 비례하므로 references 비용 비교군으로 부적절하다. 그래서 측정을 둘로 쪼갠다.
실험 A — references/ 점진 공개 검증 (핵심)
daniel-plan (계층1) vs daniel-plan-docs (계층3)계층1 + 비도메인 / 계층3 + 도메인 / 계층3 + 비도메인계층3 + 비도메인 의 input 토큰 ≈ 계층1 + 비도메인 + 작은 dispatcher 오버헤드references는 도메인 요청에서만 읽히고 비도메인에서는 SKILL.md 디스패처만 로드돼야 한다.
실험 B — dynamic context injection 비용 모델 (보조)
summarize-pr-risks 단독, diff 크기 작음/중간/큼 × 2회 = 6세션이건 references와 다른 종류의 확장이라 별도 표로 분리한다.
정적 측정 (이미 채워짐) — fresh 세션 없이도 측정 가능한 정적 값. 토큰은 한국어 글자수 ÷ 2의 어림치.
| 항목 | daniel-plan (계층1) | daniel-plan-docs SKILL.md (계층3 디스패처) | references 2개 (Level 3, 합산) |
|---|---|---|---|
| SKILL.md 줄 수 | 24 | 34 | 55 |
| description 글자 수 (cap 1,536) | 196 | 268 | — |
| Level 1 listing 추정 토큰 | ~98 | ~134 | — |
| Level 2 본문 추정 토큰 | ~306 | ~520 | (Level 3) ~689 |
이 정적 값에서 이미 한 가지가 보인다: 계층3 디스패처는 계층1보다 본문 토큰이 약 +214만큼 크다(520 − 306). references 두 개의 합산 ~689 토큰은 읽혀야 비용으로 잡힌다. 동적 측정의 핵심 가설은 비도메인 요청에서는 그 689 토큰이 잡히지 않는다 는 것이다.
동적 측정 (사용자가 6세션 라이트로 채움 — 30분)
| 조건 | 평균 input 토큰 | 평균 output 토큰 | 충족률 (3 중) | 평균 /context % | /cost 누적 |
|---|---|---|---|---|---|
| 계층1 + 비도메인 (baseline) | _____ | _____ | __/3 | _____ | _____ |
| 계층3 + 비도메인 | _____ | _____ | __/3 | _____ | _____ |
| 차이 (계층3 − 계층1) | _____ | _____ | — | _____ | _____ |
판정 기준: 차이 행의 평균 input 토큰이 +200~+400 정도면 progressive 작동(✅). +800 이상이면 references가 의도와 달리 항상 읽힘(❌).
풀버전 9세션(계층3 + 도메인 행 추가) 및 실험 B(
summarize-pr-risks의 diff 크기 vs 토큰)는 별도 측정 노트에 표로 정리한다.
이 측정은 통계적 검정이 아니라 trend 신호다. 정직하게 적자.
측정은 글의 주장을 내 환경에서 재현하는 단계다. 결과가 가설과 다르다면 — 즉 계층3 비도메인 행이 계층3 도메인 행과 비슷하게 비싸다면 — 스킬이 progressive하게 작동하지 않고 있다는 뜻이다. description이 도메인 분기를 표현 못 하거나, SKILL.md의 references 링크 문구가 너무 강한 신호여서 항상 읽히고 있을 가능성이 크다.
§6의 측정이 progressive disclosure의 작동을 보여줬다면, 이 챕터는 그럼에도 확장이 정당한 경우를 정직하게 인정한다.
(a) 결정론적 검증 스크립트. DB 마이그레이션, OOXML pack/unpack, 매번 같은 결과를 보장해야 하는 검증. Best Practices의 "low freedom" 영역. Claude가 즉석에서 짠 코드는 매번 다를 수 있다.
(b) 도메인 분기 references. 한 스킬이 다중 도메인을 다룰 때 SKILL.md는 디스패처, references는 도메인별. daniel-plan-docs가 정확히 이 모양이다.
---
name: daniel-plan-docs
description: 작업 계획을 점검하되, 서버/인프라 도메인인지 데이터 도메인인지에 따라 별도 체크리스트(references/)를 조건부로 읽어 정밀 점검한다. (...)
---
# daniel-plan-docs
이 SKILL.md는 *디스패처*다. 실제 점검 항목은 도메인별 references 파일에 있다.
## 도메인 판별
- 서버·인프라·배포·롤백·스키마 변경·트래픽·캐시·인증
→ [references/server-checklist.md](references/server-checklist.md)
- 데이터·ETL·파이프라인·집계·백필·증분 처리
→ [references/data-checklist.md](references/data-checklist.md)
둘 다 해당 없으면 references를 *읽지 않는다*. daniel-plan과 동일한 일반 점검만.공식 BigQuery 예시도 같은 패턴이다 — reference/finance.md, reference/sales.md, reference/product.md, reference/marketing.md로 도메인을 분리.
(c) 시각 자료 생성 스크립트. Claude가 즉석에서 짜는 코드보다 미리 만든 스크립트가 출력 일관성·토큰 효율 모두 우위. 공식 codebase-visualizer 예시처럼 HTML/SVG 생성기를 두는 경우.
(d) Gotchas 누적이 본문을 흐릴 때. SKILL.md 본문이 500줄에 근접하면 별도 GOTCHAS.md 로 분리한다. 본문이 핵심 워크플로우에 집중되게 유지하는 게 목적.
이 글의 주장은 "내가 사이드로 만드는 스킬"에 한정된다. 다음 경우는 처음부터 폴더 구조를 갖춰 만드는 게 옳다:
핵심 구분: "내가 만드는 사이드 스킬" vs "프로덕션 표준 스킬". 사이드는 미니멀로 시작하고, 표준은 처음부터 큰 구조를 정당화할 수 있다. Anthropic 공식 스킬 저장소를 보면 예제 스킬은 SKILL.md만, 문서 스킬(docx/xlsx/pptx/pdf)만 scripts/templates를 동반한다. Anthropic 본인들도 단순 스킬은 한 장으로 끝낸다.
확장은 상상한 미래가 아니라 관찰된 현재에서 출발한다. 처음부터 큰 스킬이 옳은 경우는 상상이 아니라 팀·배포·결정론 같은 외적 제약이 명시될 때뿐이다.
Phil Whittaker의 "Progressive Discovery" 글은 용어 자체를 비튼다. "공개(disclosure)"는 UX에서 온 말로, 인터페이스가 능동적으로 무엇을 드러낼지 결정하는 어감이다. 그런데 스킬에서 능동적인 쪽은 인터페이스가 아니라 Claude다.
"Claude is discovering. The Skill is being discovered."
이 관점이 §6의 측정 결과가 왜 그렇게 나오는지를 설명한다. 우리가 폴더를 미리 만들고 SKILL.md에 "필요하면 X.md 보세요" 식 링크를 박아두면, 그건 "Claude가 발견할 수 있는 길"이 아니라 "Claude가 발견하지 않을 수 없는 길"이 된다. discoverability는 조건부여야 하는데, 우리가 본문에서 항상 호명하면 그 조건이 사라진다.
그래서 폴더를 미리 짜는 것은 단순히 "쓸데없는 빈 디렉토리"가 아니라, 발견 가능성의 자유도를 제약하는 행위다. 사용자의 직관 — "이거 다 빼두는 게 맞지 않을까?" — 와 정확히 같은 결론이다.
[ ] 1. SKILL.md 본문이 500줄에 가까워졌는가? (Yes → references 분리 검토)
[ ] 2. 적고 있는 내용이 Claude가 이미 아는 것은 아닌가? (Yes → 삭제)
[ ] 3. 이 확장이 *실제 실패*에서 나왔는가, 아니면 *상상한 요구*에서 나왔는가?
[ ] 4. description은 사람을 위한 마케팅 카피인가, Claude를 위한 트리거 키워드인가?
[ ] 5. 측정해봤는가? — 계층1과 계층3 비도메인 비교 6세션.
[ ] 6. 이 스킬은 *내 사이드 스킬*인가, *프로덕션 표준 스킬*인가?
[ ] 7. 새 폴더를 만들기 전에, 본문에서 그 폴더 경로를 *어떻게 부를지* 미리 적어봤는가?
("필요할 때만 읽어라"가 안 되고 "항상 X 실행"이 되면 progressive 원칙 위반)
이 일곱 개 중 하나라도 답이 명확하지 않으면, 확장은 한 박자 늦추는 게 안전하다.
스킬은 작을수록 Claude가 발견하기 쉽다. 작을수록 컨텍스트 윈도우를 덜 갉아먹는다. 작을수록 다른 스킬과 충돌하지 않는다.
자라야 할 때는 Claude가 알려준다. 어떤 요청에서 Claude가 실패하는지가 확장의 진짜 신호다. 우리가 미리 상상한 "이런 게 있으면 좋을 텐데"가 아니라.
이 글이 내내 같은 말을 반복했다.
폴더가 있어서 비싼 게 아니다. Claude가 실제로 읽은 context가 비용이다.
이 한 줄을 갖고 다음에 스킬을 만들 때, mkdir scripts templates references 대신 vim SKILL.md 한 줄로 시작하기를. 거기서 멈춰도 그건 스킬이다.