하네스 엔지니어링 (3) 역할별 레시피와 안티패턴: 바로 쓰는 에이전트 정의 5종
하네스 엔지니어링 시리즈 1편 개념과 가드레일 · 2편 데이터 거버넌스와 피드백 루프 · 3편 역할별 레시피와 안티패턴 (현재 글) 선행 글: Claude Code 에이전트 팀 입문 · 레거시 프로젝트에 에이전트 팀 투입하기
1·2편에서 다룬 원리를 파일로 옮깁니다. 이 글의 정의는 그대로 복사해 .claude/agents/ 에 넣으면 동작합니다.
먼저 프론트매터 필드가 3대 구성요소 중 무엇에 해당하는지 짚어두면 각 레시피를 읽기 쉽습니다.
| 필드 | 구성요소 | 하는 일 |
|---|---|---|
tools, disallowedTools |
가드레일 | 쓸 수 있는 도구를 좁힘 |
permissionMode |
가드레일 | 권한 프롬프트 처리 방식 |
maxTurns |
가드레일 | 폭주 차단 |
isolation |
가드레일 | 작업 공간 격리 |
hooks |
가드레일 + 피드백 루프 | 실행 시점 차단, 완료 강제 |
mcpServers |
데이터 거버넌스 | 볼 수 있는 외부 데이터 |
skills |
데이터 거버넌스 | 시작 시 주입할 절차 지식 |
memory |
데이터 거버넌스 | 대화를 넘는 지식 축적 |
model, effort |
— | 비용·성능 |
여기서 hooks 필드가 이 글의 핵심 도구입니다. 훅을 settings.json이 아니라 에이전트 정의 파일 안에 두면, 정의 하나가 자기 검증까지 들고 다니는 자기완결 단위가 됩니다. 그 에이전트가 도는 동안만 유효하고, 끝나면 자동으로 정리되며, settings.json의 훅을 대체하지 않고 추가됩니다.
5. 역할별 하네스 레시피
5-1. 조사자 (Explorer)
코드베이스를 파악하고 결과를 문서로 남기는 역할입니다. 코드를 고치지 않습니다.
---
name: explorer
description: 코드베이스의 특정 영역을 조사해 구조와 흐름을 문서로 정리. 코드를 수정하지 않음
tools: Read, Grep, Glob, Bash
permissionMode: plan
model: sonnet
memory: project
color: cyan
---
지정된 영역을 조사하고 결과를 문서로 남기세요.
## 절차
1. `docs/findings/<주제>.md` 가 이미 있으면 **먼저 읽으세요.**
2. 조사 후, 새로 알게 된 것만 그 문서에 추가하세요. 기존 내용을 덮어쓰지 마세요.
3. 확인하지 못한 것은 "미확인"으로 명시하세요. 추측을 사실처럼 쓰지 마세요.
## 문서에 남길 것
- 주요 흐름 (진입점 → 처리 → 저장, 파일:라인 번호 포함)
- 코드를 봐도 알 수 없는 배경과 함정
- 외부 의존 (호출하는 API, 읽는 테이블)
## 남기지 말 것
- 디렉터리 구조 나열 (`ls` 로 알 수 있음)
- 의존성 목록 (`package.json` 으로 알 수 있음)
조사 중 발견한 재사용 가능한 패턴은 메모리에도 기록하세요.
필드별 근거
| 필드 | 이유 |
|---|---|
tools: Read, Grep, Glob, Bash |
화이트리스트. Write·Edit가 없으므로 파일 수정 도구 자체가 없음 (1편 2-2) |
permissionMode: plan |
Bash가 있으면 echo > file 로 우회 수정이 가능하므로 필수. 도구 목록만으로는 읽기 전용이 완성되지 않음 |
model: sonnet |
조사는 넓게 읽는 작업. 최고 성능 모델이 필요한 판단 작업이 아님 |
memory: project |
반복 조사에서 이전 발견을 재사용. 커밋되므로 팀 공유 (2편 3-5) |
| 본문의 “먼저 읽으세요” | 이게 없으면 매번 처음부터 조사하고 덮어씀 |
막는 실패
- 조사하다 고치기 시작하는 것 — 도구가 없으니 불가능합니다. 1편 2-2에서 다룬 “권한을 좁히면 품질이 올라간다”의 사례입니다. 고칠 방법이 없으면 컨텍스트를 전부 조사에 씁니다.
- 같은 조사의 반복 — 문서와 메모리에 축적됩니다.
- 추측을 사실로 보고하는 것 — “미확인 명시” 지시로 완화합니다(advisory이므로 완전하지는 않습니다).
내장 Explore를 쓰지 않는 이유
Claude Code에는 내장 Explore 에이전트가 있습니다. 굳이 커스텀 조사자를 만드는 이유가 있습니다.
내장
Explore와Plan은CLAUDE.md와 git status를 로드하지 않습니다. 컨텍스트를 작게 유지하려는 의도이고, 프론트매터나 설정으로 바꿀 수 없습니다.
즉 내장 Explore는 팀 컨벤션과 프로젝트 규칙을 모르는 상태로 조사합니다. 가벼운 파일 찾기에는 최적이지만, “우리 팀 규칙에 비추어 이 영역이 어떤 상태인가”를 물으려면 커스텀 정의가 필요합니다. 커스텀 서브에이전트는 CLAUDE.md 계층 전체를 로드합니다.
5-2. 구현자 (Implementer)
기능을 구현하는 역할입니다. 5종 중 가장 위험하므로 가드레일이 가장 두껍습니다.
---
name: implementer
description: 지정된 모듈 범위 안에서 기능을 구현. 테스트 통과 없이는 종료할 수 없음
tools: Read, Grep, Glob, Bash, Edit, Write
model: opus
maxTurns: 60
hooks:
PreToolUse:
- matcher: "Write|Edit"
hooks:
- type: command
command: "${CLAUDE_PROJECT_DIR}/.claude/hooks/guard-write-scope.sh"
statusMessage: "쓰기 범위 확인 중..."
Stop:
- hooks:
- type: command
command: "${CLAUDE_PROJECT_DIR}/.claude/hooks/require-tests.sh"
statusMessage: "테스트 확인 중..."
---
지정된 범위 안에서 요구사항을 구현하세요.
## 제약
- 스폰 프롬프트에 명시된 디렉터리 밖의 파일은 **읽기만** 하세요.
- 공통 모듈 수정이 필요하면 직접 고치지 말고 **보고하세요.**
- 마이그레이션 파일은 만들지 마세요. 스키마 변경이 필요하면 보고하세요.
## 완료 조건
테스트가 전부 통과해야 합니다. 실패한 테스트를 "기존 이슈"로 판단해 넘기지 마세요.
판단이 필요하면 넘기지 말고 보고하세요.
두 훅 스크립트입니다.
#!/bin/bash
# .claude/hooks/guard-write-scope.sh
# 보호 경로에 대한 쓰기를 차단
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
case "$FILE_PATH" in
*migrations/*|*schema.prisma|*/.github/workflows/*)
echo "보호 경로입니다: $FILE_PATH" >&2
echo "이 파일은 사람이 직접 처리합니다. 필요한 변경 내용을 보고하세요." >&2
exit 2
;;
esac
exit 0
#!/bin/bash
# .claude/hooks/require-tests.sh
# 소스가 변경됐는데 테스트가 실패하면 종료를 막는다
CHANGED=$(git diff --name-only HEAD -- 'src/**' | wc -l)
if [ "$CHANGED" -eq 0 ]; then
exit 0
fi
if ! OUTPUT=$(npm test 2>&1); then
echo "테스트가 실패했습니다. 종료할 수 없습니다:" >&2
echo "$OUTPUT" | tail -30 >&2
exit 2
fi
exit 0
chmod +x .claude/hooks/guard-write-scope.sh .claude/hooks/require-tests.sh
필드별 근거
| 필드 | 이유 |
|---|---|
tools (Edit, Write 포함) |
구현자이므로 쓰기가 필요. 대신 범위를 훅으로 제한 |
model: opus |
설계 판단이 들어가는 작업. 여기서 아끼면 나중에 더 씀 |
maxTurns: 60 |
테스트 통과까지 반복할 여유는 주되, 무한 루프는 차단 (1편 2-3) |
hooks.PreToolUse |
tools로는 표현할 수 없는 조건부 규칙. “쓰기는 되지만 이 경로는 안 됨” |
hooks.Stop |
검증 강제력 3단계. 판정을 모델에서 코드로 옮김 (2편 4-1) |
Stop이 SubagentStop으로 변환된다
프론트매터에 Stop 훅을 쓰면 자동으로 SubagentStop으로 변환됩니다. 서브에이전트가 완료될 때 실제로 발동하는 이벤트가 그것이기 때문입니다. 그대로 Stop으로 써도 됩니다.
여기서 TaskCompleted 훅을 쓰지 않은 이유가 있습니다.
| 특성 | |
|---|---|
TaskCompleted |
matcher 미지원 — 모든 태스크 완료에 발동. 특정 에이전트에만 걸 수 없음. 에이전트 팀의 태스크 리스트 이벤트 |
SubagentStop |
agent_type으로 matcher 지원 — 특정 에이전트에만 검증을 걸 수 있음 |
역할별로 다른 검증을 걸어야 하므로 SubagentStop 쪽이 맞습니다. TaskCompleted는 팀 단위 공통 검증에 씁니다(5-6).
막는 실패
- 범위 밖 파일 수정 —
PreToolUse가 차단합니다. 프롬프트로 “auth 디렉터리만 고쳐라”라고 하는 것과 달리, 이건 어길 수 없습니다. - 테스트 실패 상태로 완료 보고 —
Stop훅이 종료를 막습니다. 2편 4-1에서 다룬 “실패한 테스트는 기존 이슈로 보입니다” 유형의 보고가 구조적으로 불가능해집니다. - 무한 수정 루프 —
maxTurns가 끊습니다.
병렬로 여러 개 돌릴 때
구현자를 여러 개 동시에 돌린다면 guard-write-scope.sh를 에이전트별 범위로 확장하거나, 파일 충돌을 아예 없애려면 5-4처럼 isolation: worktree를 붙입니다. 다만 worktree는 DB·포트를 격리하지 않으므로, 테스트가 DB를 쓰면 여전히 충돌합니다(1편 2-4).
5-3. 검증자 (Verifier)
구현자의 산출물을 채점하는 역할입니다. 구현 과정을 모르는 것이 이 역할의 장점입니다.
---
name: verifier
description: 완료 보고된 작업이 요구사항을 실제로 충족했는지 검증. 코드를 수정하지 않음
tools: Read, Grep, Glob, Bash
permissionMode: plan
model: opus
effort: high
color: yellow
---
요구사항과 산출물을 비교해 검증하세요.
## 검증 범위 (이것만)
1. **요구사항 충족** — 요청된 동작이 실제로 구현됐는가
2. **정확성** — 로직 오류, 경계값 처리, 에러 케이스
3. **회귀** — 기존 동작을 깨뜨렸는가
## 검증 범위 밖 (지적하지 마세요)
- 스타일 취향, 네이밍 선호, 포맷
- "더 나은 방법이 있다" 류의 개선 제안
- 호출부가 보장하는 조건에 대한 방어 코드 요구
- 발생 불가능한 시나리오에 대한 테스트 요구
## 보고 형식
각 발견 사항에 대해:
- **근거**: 파일:라인 + 실패하는 구체적 입력
- **심각도**: 치명 / 중요 / 사소
근거로 실패 케이스를 제시할 수 없다면 그것은 발견 사항이 아닙니다. 보고하지 마세요.
**요구사항을 충족했다면 "충족함"이라고 보고하세요. 억지로 문제를 찾지 마세요.**
필드별 근거
| 필드 | 이유 |
|---|---|
tools + permissionMode: plan |
5-1과 같은 읽기 전용 조합. 검증자가 코드를 고치면 채점자와 작업자가 다시 합쳐짐 |
model: opus, effort: high |
5종 중 가장 어려운 판단 작업. 여기가 아낄 곳이 아님 |
| 본문의 “검증 범위 밖” 목록 | 본문 전체가 하나의 목적을 위해 존재함 ↓ |
본문 절반이 “지적하지 말 것”인 이유
2편 4-2에서 다룬 문제입니다. 리뷰어는 문제가 없어도 뭔가를 지적합니다. 리뷰를 요청받았으니 찾아야 한다고 판단하고, 없으면 사소한 것을 만들어냅니다. 그리고 그 지적에 대한 대응이 코드를 부풀립니다.
검증자: "이 함수는 null 입력을 처리하지 않습니다"
구현자: 방어 코드 추가
검증자: "이 방어 코드는 테스트가 없습니다"
구현자: 불가능한 케이스의 테스트 추가
검증자: "이 로직은 추상화하면 재사용 가능합니다"
구현자: 단일 사용처에 인터페이스 도입
─────────────────────────────────
50줄로 될 일이 200줄. 전부 "리뷰 반영"이라는 명분을 가짐
세 장치로 막습니다.
- 범위를 좁힌다 — 정확성·요구사항·회귀만. 스타일과 개선 제안 배제
- 근거를 요구한다 — “실패하는 구체적 입력을 제시할 수 없으면 발견 사항이 아니다”
- “문제 없음”을 허용한다 — 명시적으로 그렇게 보고하라고 지시
그리고 하네스 밖의 원칙이 하나 더 필요합니다. 검증자의 출력은 판결이 아니라 의견입니다. 반영 여부는 사람이 고릅니다. 리뷰 결과를 자동 반영하면 위 루프가 사람 없이 돌아갑니다.
심화: 서로 반박시키기
관점을 나눠 여러 검증자를 돌린 뒤, 발견 사항을 서로 반박하게 하면 근거가 약한 지적이 걸러집니다.
verifier 에이전트 3명을 만들어 각각 정확성 / 회귀 / 요구사항 관점으로 검증하게 하세요.
그 다음, 각 발견 사항에 대해 "실제 문제가 아닌 이유"를 찾게 하세요.
반박에 성공한 항목은 목록에서 제거하세요.
5-4. 마이그레이터 (Migrator)
기계적인 변경을 여러 파일에 일괄 적용하는 역할입니다. 변경 범위가 넓어서 격리가 필요합니다.
---
name: migrator
description: 기계적·반복적 변경을 여러 파일에 일괄 적용 (API 교체, 임포트 경로 변경 등)
tools: Read, Grep, Glob, Bash, Edit
model: sonnet
maxTurns: 40
isolation: worktree
hooks:
Stop:
- hooks:
- type: command
command: "${CLAUDE_PROJECT_DIR}/.claude/hooks/require-build.sh"
statusMessage: "빌드 확인 중..."
---
요청된 변경을 해당하는 모든 파일에 적용하세요.
## 절차
1. `Grep` 으로 대상 파일 목록을 **먼저 전부 찾으세요.** 목록을 보고하세요.
2. 변경을 적용하세요.
3. 빌드와 테스트를 실행하고 결과를 보고하세요.
4. 변경한 파일 수와 목록을 보고하세요.
## 제약
- **패턴에서 벗어난 판단이 필요한 파일은 건드리지 말고 목록으로 보고하세요.**
기계적으로 처리 가능한 것만 처리합니다.
- 변경 중 기존 동작을 바꾸는 리팩토링을 함께 하지 마세요. 요청된 변경만 하세요.
#!/bin/bash
# .claude/hooks/require-build.sh
if ! OUTPUT=$(npm run build 2>&1); then
echo "빌드가 실패했습니다. 종료할 수 없습니다:" >&2
echo "$OUTPUT" | tail -30 >&2
exit 2
fi
exit 0
필드별 근거
| 필드 | 이유 |
|---|---|
isolation: worktree |
수십 개 파일을 동시에 고치므로 메인 체크아웃과 분리. 변경이 없으면 자동 정리됨 |
maxTurns: 40 |
파일이 많으면 턴을 많이 쓰지만, 같은 파일을 계속 왕복하는 상황은 차단 |
model: sonnet |
기계적 변경. 판단이 거의 없으므로 최고 성능 모델이 불필요 |
tools에 Write 없음 |
기존 파일 수정(Edit)만 필요. 새 파일 생성은 이 역할이 아님 |
hooks.Stop (빌드) |
테스트보다 빌드가 적합 — 임포트 경로 실수 같은 기계적 오류를 빠르게 잡음 |
| 본문의 “먼저 전부 찾으세요” | 찾으면서 고치면 누락이 생김. 목록을 확정한 뒤 적용 |
worktree 사용 시 확인할 것
isolation: worktree를 붙이면 1편 2-4의 주의사항이 그대로 적용됩니다.
.env와node_modules가 없습니다. 새 체크아웃이기 때문입니다. 빌드·테스트를 돌리려면.worktreeinclude에 필요한 파일을 적어야 합니다.- 기본 브랜치에서 분기합니다. 진행 중인 작업 위에 적용해야 하면
worktree.baseRef를"head"로 설정합니다. - DB·포트는 공유됩니다. 마이그레이터를 여러 개 병렬로 돌리면서 각자 마이그레이션을 실행하면 충돌합니다.
.env
.env.test
막는 실패
- 일괄 변경 중 판단이 필요한 파일을 임의로 처리하는 것 — “보고하고 넘어가라”로 분리합니다. 이게 없으면 애매한 파일에서 에이전트가 임의 판단을 하고, 그게 diff에 섞여 리뷰가 어려워집니다.
- 범위 확장 — “요청된 변경만”이 명시되지 않으면 지나가는 길에 다른 개선을 합니다.
- 메인 체크아웃 오염 — worktree가 막습니다.
5-5. 문서작성자 (Documenter)
코드를 읽고 문서를 쓰는 역할입니다. 문서 경로에만 쓸 수 있습니다.
---
name: documenter
description: 코드를 읽고 문서를 작성·갱신. 코드 파일은 수정하지 않음
tools: Read, Grep, Glob, Bash, Edit, Write
model: sonnet
hooks:
PreToolUse:
- matcher: "Write|Edit"
hooks:
- type: command
command: "${CLAUDE_PROJECT_DIR}/.claude/hooks/docs-only.sh"
statusMessage: "문서 경로 확인 중..."
---
코드를 읽고 문서를 작성하거나 갱신하세요.
## 원칙
- **코드에서 바로 알 수 있는 것은 쓰지 마세요.** 디렉터리 구조 나열, 함수 시그니처
복사, 의존성 목록은 코드가 바뀌면 거짓말이 됩니다.
- 대신 이것을 쓰세요: 왜 그렇게 되어 있는지, 함정, 도구 기본값과 다른 우리 선택.
- 기존 문서를 갱신할 때는 **먼저 읽고**, 달라진 부분만 고치세요. 전면 재작성하지 마세요.
- 확인하지 못한 내용은 쓰지 마세요. 추측으로 문서를 채우면 없는 문서보다 나쁩니다.
## 코드 수정이 필요해 보이면
문서를 쓰다가 코드의 버그나 개선점을 발견하면 **문서에 쓰지 말고 별도로 보고하세요.**
코드는 수정할 수 없습니다.
#!/bin/bash
# .claude/hooks/docs-only.sh
# 문서 경로 외의 쓰기를 차단
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
case "$FILE_PATH" in
*.md|*docs/*|*README*|*CHANGELOG*)
exit 0
;;
esac
echo "문서 파일만 수정할 수 있습니다: $FILE_PATH" >&2
echo "코드 변경이 필요하면 수정하지 말고 보고하세요." >&2
exit 2
필드별 근거
| 필드 | 이유 |
|---|---|
tools에 Write 포함 |
새 문서 파일을 만들어야 하므로 필요 |
hooks.PreToolUse (화이트리스트 방식) |
5-2의 guard-write-scope.sh와 반대 구조 — 거기는 위험 경로를 나열해 막았고, 여기는 허용 경로를 나열해 그 밖을 전부 막음 |
model: sonnet |
읽고 정리하는 작업 |
훅 스크립트의 방향이 다른 점이 중요합니다. 1편 2-2의 화이트리스트 vs 블랙리스트 판단이 훅에도 그대로 적용됩니다. 문서작성자는 허용 범위가 명확하므로 화이트리스트가 맞습니다. 새 코드 디렉터리가 생겨도 자동으로 차단됩니다.
막는 실패
- 문서 쓰다가 코드 고치기 — 훅이 차단합니다. “발견하면 보고”로 출구를 만들어 줍니다.
- 코드에서 알 수 있는 내용을 문서에 복사하는 것 — 2편 3-2에서 다룬 “틀린 문서는 없는 문서보다 나쁘다”의 예방입니다. 코드가 바뀌면 거짓말이 되는 내용은 애초에 쓰지 않게 합니다.
- 전면 재작성 — 사람이 손으로 다듬은 부분이 날아가는 문제를 “먼저 읽고 달라진 부분만”으로 막습니다.
5-6. 서브에이전트 정의를 팀원으로 재사용하기
위 5종은 서브에이전트 정의지만, 에이전트 팀의 팀원으로도 쓸 수 있습니다. 역할을 한 번 정의해 두고 양쪽에서 재사용하는 방식입니다.
verifier 에이전트 타입으로 팀원을 만들어 인증 모듈을 검증하게 해주세요.
여기서 반드시 알아야 할 것이 어떤 필드가 적용되고 어떤 필드가 무시되는지입니다.
| 구분 | 팀원으로 쓸 때 |
|---|---|
tools |
○ 적용 (allowlist 존중) |
model |
○ 적용 |
| 정의 본문 | ○ 적용 — 시스템 프롬프트에 추가됨 (대체 아님) |
skills |
✗ 미적용 |
mcpServers |
✗ 미적용 |
permissionMode |
✗ 미적용 — 팀원은 리드의 권한 모드로 시작 |
isolation: worktree |
✗ 미적용 — 팀원은 리드와 작업 디렉터리 공유 |
미적용 항목이 실무에서 사고를 만듭니다.
① permissionMode가 무시된다
5-3의 검증자를 팀원으로 띄우면 permissionMode: plan이 적용되지 않습니다. 팀원은 리드의 권한 설정으로 시작하고, 스폰 시점에 팀원별 권한을 줄 수 없습니다.
tools는 적용되므로 Write·Edit는 여전히 없지만, Bash가 있으므로 echo > file 로 파일을 쓸 수 있는 상태가 됩니다. 5-1에서 “도구 목록만으로는 읽기 전용이 완성되지 않는다”고 했던 그 구멍이 팀원 모드에서 다시 열립니다.
대응은 두 가지입니다.
- 리드 자체를 안전한 권한 모드로 띄운다 (1편 2-2의 “가장 바깥 세션에서 가장 좁게”)
- 스폰 후 개별 팀원의 모드를 바꾼다 (스폰 시점에는 불가, 이후에는 가능)
② isolation: worktree가 무시된다
5-4의 마이그레이터를 팀원으로 여러 개 띄우면 전부 같은 디렉터리에서 같은 파일을 고칩니다. 팀으로 병렬 구현을 할 때 파일 충돌을 구조적으로 막는 수단이 없다는 뜻입니다.
그래서 판단이 이렇게 갈립니다.
| 목적 | 선택 |
|---|---|
| 병렬로 파일을 고쳐야 한다 | 서브에이전트 + isolation: worktree |
| 병렬로 논의·조사해야 한다 | 에이전트 팀 (읽기 전용이므로 충돌 없음) |
| 팀으로 병렬 구현해야 한다 | 프롬프트로 파일 소유권을 엄격히 분리 + 사람이 감시 |
레거시 프로젝트에 에이전트 팀 투입하기에서 코드 파악 단계를 “★★★ 최적”, 리팩토링 단계를 “★★ 관리 필요”로 분류한 근거가 이것입니다.
③ mcpServers·skills가 무시된다
팀원은 스킬과 MCP 서버를 프로젝트·유저 설정에서 로드합니다(일반 세션과 동일). 즉 2편 3-3에서 다룬 “에이전트별로 볼 수 있는 데이터를 제한하는” 설계가 팀원에는 적용되지 않습니다. 팀원은 프로젝트에 설정된 모든 MCP 서버를 봅니다.
팀 조율 도구는 항상 사용 가능
반대 방향의 예외가 하나 있습니다. tools가 도구를 제한해도 팀 조율 도구는 항상 사용 가능합니다.
SendMessage(팀원 간 메시지)TaskCreate,TaskGet,TaskList,TaskUpdate(태스크 관리)
tools: Read, Grep, Glob, Bash로 좁힌 검증자를 팀원으로 띄워도 다른 팀원과 대화하고 태스크를 집어갈 수 있습니다. 이건 의도된 설계입니다 — 조율 능력이 없으면 팀원으로서 동작할 수 없습니다.
팀 단위 공통 검증
5-2에서 개별 에이전트 검증에 SubagentStop을 썼습니다. 팀에서는 태스크 단위 검증을 걸 수 있습니다.
{
"hooks": {
"TaskCompleted": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/require-tests.sh"
}
]
}
]
}
}
TaskCompleted는 matcher를 지원하지 않으므로 모든 태스크 완료에 발동합니다. 팀 공통 기준(예: 테스트 통과)에는 적합하고, 역할별로 다른 검증에는 부적합합니다.
TeammateIdle도 같이 쓸 만합니다. 팀원이 할 일을 남기고 유휴로 넘어가려 할 때 종료 코드 2로 계속 일하게 만듭니다.
6. 하네스 설계 안티패턴 5가지
| 안티패턴 | 증상 |
|---|---|
| 과잉 하네스 | 훅과 규칙이 너무 많아 에이전트가 아무것도 못 함 |
비대한 CLAUDE.md |
길어져서 정작 중요한 규칙이 무시됨 |
| 검증 없는 자율성 | 권한만 넓히고 확인 수단은 안 만듦 |
| 지시로 강제하려는 시도 | 반드시 지켜야 할 것을 advisory로 둠 |
| 하네스 복사 | 조사자 설정을 구현자에 그대로 재사용 |
6-1. 과잉 하네스
증상: 훅이 계속 차단하고, 에이전트가 “권한이 없어 진행할 수 없습니다”를 반복합니다. 매 작업마다 사람이 개입해 예외를 열어줍니다.
왜 생기는가: 사고가 한 번 나면 규칙을 추가하는데, 그 규칙이 정당한 작업까지 막는 경우입니다. 그리고 규칙은 추가할 때만 검토되고 제거는 아무도 하지 않습니다.
교정: 2편 4-4의 제거 루프를 돌립니다. 그리고 규모에 대한 감각을 기준값으로 잡습니다.
| 항목 | 기준 |
|---|---|
| 세션당 서브에이전트 | 기본 상한 200개 (CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION) |
| 동시 실행 서브에이전트 | 기본 상한 20개 (CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS) |
| 팀원 수 | 권장 3~5명, 팀원당 태스크 5~6개 |
CLAUDE.md 길이 |
파일당 200줄 이하 |
이 숫자들에 한참 못 미치는 규모인데 하네스 때문에 일이 막힌다면 과잉입니다. 하네스는 사고를 막는 장치이지 작업을 막는 장치가 아닙니다.
판단 질문: “지난 한 달간 이 규칙이 실제 사고를 막은 적이 있는가?” 없다면 후보입니다.
6-2. 비대한 CLAUDE.md
증상: CLAUDE.md가 300줄이고, 그 안의 규칙이 잘 안 지켜집니다. 규칙을 추가해도 준수율이 오르지 않습니다.
왜 생기는가: 문제가 생길 때마다 한 줄씩 추가한 결과입니다. 각 추가는 합리적이었지만 총합이 컨텍스트를 잡아먹고, 새 규칙을 넣을 때마다 기존 규칙의 준수율이 떨어집니다.
교정: 2편 3-1·3-2의 분류를 적용합니다.
절차인가? → 스킬로
특정 영역만인가? → .claude/rules/ + paths 로
어겨지면 안 되는가? → 훅으로 (그리고 CLAUDE.md 에서 삭제)
코드로 알 수 있나? → 삭제
/doctor가 이 정리를 도와줍니다. /context로 실제 로드 상태를 확인하는 것부터 시작합니다.
함정: @path 임포트로 파일을 쪼개는 것은 정리에는 도움이 되지만 컨텍스트는 줄지 않습니다. 임포트된 파일은 전부 로드됩니다.
6-3. 검증 없는 자율성
증상: --dangerously-skip-permissions로 띄워놓고 에이전트를 여러 개 돌립니다. 빠르지만, 나중에 diff를 보면 뭘 했는지 파악이 안 됩니다.
왜 생기는가: 권한 프롬프트가 귀찮아서 전부 열었는데, 검증 수단을 만드는 비용은 지불하지 않은 상태입니다. 권한을 넓히는 건 5초, 검증을 만드는 건 30분이라 비대칭이 생깁니다.
교정: 권한을 넓힐 때는 같은 크기의 검증을 함께 만듭니다.
| 넓힌 것 | 함께 만들 것 |
|---|---|
| 파일 쓰기 허용 | 보호 경로 PreToolUse 훅 |
| 무인 실행 | Stop 훅으로 테스트 통과 강제 |
| 병렬 3개 이상 | 검증 서브에이전트 또는 TaskCompleted 훅 |
무인 실행에서 특히 중요한 선택이 하나 있습니다. bypassPermissions가 아니라 dontAsk 를 쓰는 것입니다. dontAsk는 미리 허용한 것만 하고 나머지는 멈춥니다. 무인 실행의 기본값은 이쪽이어야 합니다(1편 2-2).
6-4. 지시로 강제하려는 시도
증상: CLAUDE.md의 문장이 점점 강해집니다. **절대**, ⚠️, 대문자 경고가 늘어납니다. 그런데 재발률은 그대로입니다.
왜 생기는가: 규칙이 안 지켜질 때 표현의 문제로 진단하기 때문입니다. 자연스러운 반응이지만 틀린 진단입니다.
교정: 이 시리즈의 결론입니다.
advisory는 강조해도 advisory입니다.
<!-- 이렇게 세 번 강화하는 대신 -->
- ⚠️ **절대 금지** ⚠️: 마이그레이션 파일을 어떤 경우에도 수정하지 마세요.
{
"permissions": {
"deny": ["Edit(migrations/**)", "Write(migrations/**)"]
}
}
한 줄이고, 완전하고, 컨텍스트를 쓰지 않습니다.
판단 기준: “이 규칙이 어겨졌을 때, 나중에 발견해도 괜찮은가?” 괜찮다면 CLAUDE.md, 안 괜찮다면 훅입니다.
6-5. 하네스 복사
증상: 잘 동작하는 에이전트 정의를 복사해 새 역할을 만듭니다. 조사자를 복사해 구현자를 만들고, 구현자를 복사해 검증자를 만듭니다.
왜 생기는가: 프론트매터 필드가 많아서 처음부터 쓰기 부담스럽고, 이미 검증된 설정을 재사용하는 게 안전해 보입니다.
문제가 되는 지점: 5장의 5종을 다시 보면, 각 필드가 그 역할의 목적을 위해 선택되었습니다. 복사하면 그 대응이 깨집니다.
| 잘못된 재사용 | 결과 |
|---|---|
조사자 → 구현자 (permissionMode: plan 유지) |
구현자가 파일을 못 씀. 그래서 plan을 지우다가 다른 가드레일도 함께 지움 |
구현자 → 검증자 (Write·Edit 유지) |
검증자가 코드를 고침. 채점자와 작업자가 다시 합쳐짐 |
구현자 → 마이그레이터 (maxTurns: 60 유지) |
파일 수십 개 처리에 턴이 부족하거나 과함 |
조사자 → 문서작성자 (model: sonnet + 읽기 전용 유지) |
문서를 쓸 수 없음 |
아무거나 → 팀원 (permissionMode·isolation 기대) |
두 필드가 무시됨 (5-6) |
교정: 복사한 뒤 필드를 하나씩 다시 판단합니다. 다섯 개 질문으로 충분합니다.
1. 이 역할은 파일을 써야 하나? → tools, permissionMode
2. 판단이 필요한 작업인가? → model, effort
3. 완료를 어떻게 확인하나? → hooks (Stop/SubagentStop)
4. 다른 에이전트와 동시에 파일을 고치나? → isolation
5. 어떤 외부 데이터가 필요한가? → mcpServers
각 답이 어느 필드로 이어지는지가 이 글 도입부의 표입니다. 필드를 채우는 게 아니라 질문에 답하는 순서로 쓰면 복사 사고가 줄어듭니다.
7. 하네스 성숙도 자기진단
3대 구성요소별 체크리스트입니다. 해당하는 항목에 표시해 보세요.
가드레일
.env, 자격증명 파일 읽기가 설정으로 차단되어 있다 (문서 경고가 아니라)- 읽기 전용 역할의 에이전트 정의가 있다 (
tools+permissionMode: plan) - 절대 수정되면 안 되는 경로가
permissions.deny또는PreToolUse훅으로 막혀 있다 - 무인 실행 시
bypassPermissions가 아니라dontAsk를 쓴다 - 병렬로 파일을 고칠 때
isolation: worktree를 쓴다
데이터 거버넌스
CLAUDE.md가 파일당 200줄 이하다- 절차성 지식이
CLAUDE.md가 아니라 스킬에 있다 - 영역 한정 규칙이
.claude/rules/+paths로 분리되어 있다 /context로 컨텍스트 사용량을 확인한 적이 있다- 조사 결과가 문서로 축적되고, 다음 에이전트가 그걸 읽는다
피드백 루프
- 에이전트가 스스로 완료를 확인할 수단이 있다 (테스트, 빌드, lint)
- 검증이 훅으로 강제된다 (프롬프트 지시가 아니라)
- 채점자와 작업자가 분리되어 있다
- 같은 실수가 두 번 나오면 하네스를 고친다 (프롬프트가 아니라)
- 커밋·PR 최종 승인은 사람이 한다
단계 진단
0단계 규칙 없음 — 매번 프롬프트로 지시
체크: 0~3개
1단계 CLAUDE.md 로 공통 규칙 문서화
체크: 4~7개
2단계 역할별 에이전트 정의 + 권한 분리
체크: 8~12개
3단계 훅으로 검증 강제 + 실패가 하네스에 자동 반영
체크: 13개 이상
단계별 다음 액션입니다.
| 현재 | 다음에 할 일 |
|---|---|
| 0단계 | /init로 CLAUDE.md를 만들고, 반복 교정하는 내용을 옮긴다. 200줄을 넘기지 않는다 |
| 1단계 | 5-1 조사자와 5-3 검증자를 만든다. 읽기 전용이므로 위험이 없고, 효과가 바로 보인다 |
| 2단계 | PreToolUse 훅 하나(보호 경로)와 Stop 훅 하나(테스트 강제)를 만든다. 이 둘이 훅의 80%다 |
| 3단계 | 제거 루프를 돌린다. 훅으로 올린 규칙을 CLAUDE.md에서 지워 컨텍스트를 되찾는다 |
주의: 단계를 건너뛰지 않는 게 낫습니다. 0단계에서 바로 훅을 잔뜩 만들면 6-1의 과잉 하네스가 됩니다. 실제로 겪은 실패에 대응해서 하네스를 세우는 순서가 맞습니다.
핵심요약
3편 (이 글)
- 에이전트 정의 파일의 각 필드는 3대 구성요소 중 하나에 대응한다. 필드를 채우는 게 아니라 다섯 질문(파일을 쓰나 / 판단이 필요한가 / 완료를 어떻게 확인하나 / 동시에 파일을 고치나 / 어떤 데이터가 필요한가)에 답하는 순서로 쓴다.
- 프론트매터
hooks필드를 쓰면 정의 하나가 자기 검증까지 들고 다니는 자기완결 단위가 된다.settings.json의 훅을 대체하지 않고 추가되며, 그 에이전트가 도는 동안만 유효하다. 프론트매터의Stop은 자동으로SubagentStop으로 변환된다. - 역할별 요점: 조사자는 읽기 전용 +
memory+ 문서화 지시 / 구현자는 쓰기 범위 훅 +Stop훅 / 검증자는 읽기 전용 + “지적하지 말 것” 목록 / 마이그레이터는worktree+maxTurns/ 문서작성자는 문서 경로 화이트리스트 훅. SubagentStop은 matcher를 지원하고TaskCompleted는 지원하지 않는다. 역할별로 다른 검증은SubagentStop, 팀 공통 검증은TaskCompleted.- 팀원으로 재사용할 때
permissionMode·isolation·skills·mcpServers가 무시된다. 특히permissionMode미적용 때문에 “읽기 전용으로 만든 검증자”가 팀원 모드에서는Bash로 파일을 쓸 수 있다. 대신 팀 조율 도구는tools가 제한해도 항상 사용 가능하다. - 안티패턴 5가지: 과잉 하네스 / 비대한
CLAUDE.md/ 검증 없는 자율성 / 지시로 강제하려는 시도 / 하네스 복사. 권한을 넓힐 때는 같은 크기의 검증을 함께 만든다. - 성숙도는 실제 겪은 실패에 대응해서 올린다. 0단계에서 바로 훅을 잔뜩 만들면 과잉 하네스가 된다.
시리즈 전체 총정리
세 편을 한 문단으로 압축하면 이렇습니다.
AI 에이전트를 프롬프트로 통제하려는 시도는 에이전트가 늘어나는 만큼 확장되지 않습니다. 지시는 권고이고 하네스는 강제이며, 이 격차의 비용은 에이전트 수에 비례해 커집니다. 그래서 할 수 있는 일을 좁히고(가드레일), 볼 것을 정하고(데이터 거버넌스), 완료의 판정을 코드에 넘깁니다(피드백 루프). 그리고 실수를 지적하는 대신 재발이 불가능하게 만듭니다.
각 편의 판단 기준을 모으면 실무 체크리스트가 됩니다.
| 상황 | 판단 기준 |
|---|---|
| 규칙을 어디에 둘까 | 어겨졌을 때 나중에 발견해도 괜찮은가? → 괜찮으면 CLAUDE.md, 안 괜찮으면 훅 |
CLAUDE.md인가 스킬인가 |
항상 참인 사실인가, 때때로 하는 절차인가 |
| 이 줄을 지울까 | 지우면 에이전트가 실수할까? 모르겠으면 지운다 |
| 검증을 어디까지 올릴까 | 에이전트 3개 이상 병렬이면 훅이 필수 |
| worktree를 쓸까 | 두 에이전트 이상이 동시에 파일을 쓸 때만 |
| 같은 실수가 또 났다 | 1회 교정 / 2회 CLAUDE.md / 3회 훅 |
마지막으로, 하네스 엔지니어링의 핵심 문장 하나만 남긴다면 이것입니다.
실수를 지적하지 말고, 재발이 불가능하게 만드세요.