도입
Claude Fable 5는 현재 가장 똑똑한 모델이면서 가장 비싼 모델입니다. 입력 100만 토큰당 10달러, 출력 100만 토큰당 50달러로 Opus 4.8의 두 배입니다. 그래서 Fable 5에게 보일러플레이트 작성, 테스트 실행, 파일 검색 같은 실행 작업을 시키면 비싼 토큰이 싼 일에 녹아 없어집니다.
해결 방향은 명확합니다. Fable 5는 계획, 작업 분배, 중요한 결정, 결과 종합 같은 판단만 맡고, 실제 손발은 하위 모델이 움직이면 됩니다. 무거운 추론은 Opus 4.8이 최대 추론 예산으로 처리하고, 명령 실행이나 로그 확인 같은 잡무는 Haiku 4.5가 처리하는 구조입니다.
그런데 대부분의 셋업이 여기서 한 가지를 놓칩니다. CLAUDE.md에 "Fable은 지휘만 하고 구현은 위임한다"라고 적는 것만으로는 부족합니다. 이 지침은 어디까지나 권고라서, 모델이 마음만 먹으면 직접 코드를 고칠 수 있기 때문입니다. 토큰을 실제로 아끼려면 말로 설득하는 단계를 넘어 물리적으로 막아야 합니다.
이 가이드는 그 전체 구조를 다룹니다. 지침 선언, 서브에이전트 역할 배정, 모델 리매핑, 그리고 훅 기반 강제 게이트까지 4계층을 쌓고, 마지막에 이 모든 것을 명령 하나로 켜고 끄는 스위치로 묶습니다. 실제 환경에서 차단이 작동하는지 검증한 결과도 함께 싣습니다.
- Fable 5 지휘 + Opus 4.8 실행 + Haiku 4.5 잡무의 3단 역할 분리비싼 모델이 싼 일을 하지 않게 만드는 기본 구도
- CLAUDE.md 권고를 물리 차단으로 바꾸는 PreToolUse 강제 게이트메인 에이전트의 직접 코드 수정을 턴당 2개 파일로 제한
- 서브에이전트는 차단하지 않는 안전한 게이트 설계훅 페이로드의 agent_id 필드로 메인과 서브를 구분
- fable on/off 명령 하나로 전체 설정을 켜고 끄는 스위치 구조설정 본체는 한 폴더, 각 설정 파일에는 얇은 로딩 코드만
전체 구조 훑어보기
본격적인 설치에 들어가기 전에 구조부터 잡겠습니다. 각 계층이 무엇을 담당하는지 알아야 문제가 생겼을 때 어디를 봐야 하는지 판단할 수 있기 때문입니다.
| 계층 | 파일 | 역할 |
|---|---|---|
| 선언 | `~/.claude/fable/fable.md` | 오케스트레이션 지침. CLAUDE.md가 임포트해서 세션에 로드 |
| 역할 배정 | `~/.claude/fable/agents/` | deep-reasoner(Opus 4.8, 심층 추론)와 runner(Haiku 4.5, 잡무) 정의 |
| 모델 리매핑 | `~/.claude/fable/env.sh` | Sonnet으로 지정된 서브에이전트를 Opus 4.8로 승격하는 환경변수 주입 |
| 강제 장치 | `~/.claude/fable/hooks/orchestration-gate.py` | 메인 에이전트의 직접 코드 수정을 물리적으로 제한하는 PreToolUse 훅 |
이 구조의 핵심 설계 원칙은 설정 본체와 로딩 코드의 분리입니다. 실제 내용은 전부 ~/.claude/fable/ 한 폴더에만 있고, Claude Code가 읽는 설정 파일에는 그 폴더를 가리키는 얇은 로딩 코드 한 줄씩만 들어갑니다. 켜고 끌 때는 설정 파일을 전혀 건드리지 않고 폴더 안의 심링크 하나만 바꿉니다. 덕분에 나중에 완전히 제거하고 싶어도 로딩 코드 몇 줄만 지우면 끝납니다.
설정 본체 구조
📁~/.claude/fable
📁agents
📁hooks
로딩 코드는 네 군데에 한 번씩만 설치합니다.
| 위치 | 로딩 코드 | 담당 계층 |
|---|---|---|
| `~/.claude/CLAUDE.md` 끝 | `@/home/사용자명/.claude/fable/active.md` 한 줄 | 선언 |
| `~/.claude/agents/` | 에이전트 파일 심링크 2개 | 역할 배정 |
| `~/.bashrc` | `source ~/.claude/fable/env.sh` 한 줄 | 모델 리매핑 |
| `~/.claude/settings.json` | PreToolUse 훅 등록 블록 | 강제 장치 |
시작 전 준비
아래 조건만 갖추면 이 가이드를 그대로 따라갈 수 있습니다. 전체 과정은 WSL 터미널 기준입니다.
사전 준비
0/4 완료
준비 확인은 터미널에서 바로 할 수 있습니다.
$ claude --version $ python3 --version Python 3.12.3 $ echo $PATH | grep -o '.local/bin' .local/bin
1단계: 오케스트레이션 지침과 스위치
첫 단계에서는 Fable 5에게 줄 지휘 지침을 파일로 만들고, 그 지침을 켜고 끄는 스위치 구조를 세웁니다. 이 단계만 끝나도 "지휘만 하고 위임하라"는 선언 계층이 작동합니다.
먼저 설정 본체 폴더를 만들고 지침 파일을 작성합니다.
$ mkdir -p ~/.claude/fable/agents ~/.claude/fable/hooks
지침 파일에는 역할 정의, 라우팅 기준, 직접 처리 허용 범위를 담습니다. 직접 처리 범위를 명시하는 부분이 중요합니다. 작은 작업까지 전부 위임하면 오히려 왕복 비용이 더 커지기 때문입니다.
## Fable 오케스트레이션 모드
당신은 오케스트레이터입니다. 코드 작성·수정·테스트·디버깅·리팩터링 같은
실행 작업은 직접 하지 말고, 명확한 지침과 함께 서브에이전트에게 위임하세요.
당신의 역할은 계획·분배·진행 파악·결과 종합·중요한 판단입니다.
라우팅: 무거운 추론·설계·근본원인 분석 -> deep-reasoner(Opus, effort max) /
일반 구현·수정·테스트·디버깅·리뷰 -> 실행 에이전트(Opus로 실행) /
단순 명령 실행·빌드·조회·로그 확인 같은 잡무 -> runner(Haiku).
독립 작업은 병렬로 위임하고 위임 중에도 계속 진행하세요. 단, 1~2개 파일의
사소한 수정·오타·설정/문서 변경·단순 조회는 직접 처리해도 됩니다.
강제 게이트(PreToolUse 훅)가 직접 코드 수정을 턴당 2개 파일로 제한합니다.
차단 메시지를 받으면 재시도하지 말고 즉시 그 작업을 서브에이전트에 위임하세요.이 지침을 CLAUDE.md에 직접 붙여넣지 않는 이유가 스위치 구조의 핵심입니다. 대신 빈 파일 하나와 심링크 하나를 추가로 만듭니다.
$ echo '<!-- fable off -->' > ~/.claude/fable/empty.md $ ln -sfn ~/.claude/fable/fable.md ~/.claude/fable/active.md $ readlink ~/.claude/fable/active.md /home/사용자명/.claude/fable/fable.md
그리고 ~/.claude/CLAUDE.md 맨 끝에 임포트 한 줄만 추가합니다. Claude Code는 CLAUDE.md 안의 @경로 문법으로 다른 파일을 함께 로드합니다.
<!-- fable-loader: 토글은 fable on/off, 이 줄은 그대로 둡니다 -->
@/home/사용자명/.claude/fable/active.md이제 CLAUDE.md는 항상 active.md를 로드하지만, 그 심링크가 어디를 가리키느냐에 따라 내용이 달라집니다. fable.md를 가리키면 지침이 로드되고, empty.md를 가리키면 아무것도 로드되지 않습니다. 켜고 끌 때 CLAUDE.md를 건드릴 필요가 없어지는 구조입니다.
이 심링크 전환을 매번 손으로 하지 않도록 토글 명령을 만듭니다. ~/.local/bin/fable로 저장하고 실행 권한을 주면 어느 터미널에서든 fable on, fable off, fable status로 조작할 수 있습니다. 설정 파일은 전혀 건드리지 않고 상태 파일과 심링크만 전환하며, status는 로딩 코드 4종의 설치 여부까지 한 번에 점검합니다. 전체 스크립트는 다음과 같습니다.
#!/usr/bin/env bash
# ~/.local/bin/fable — Fable 오케스트레이션 스위치 (런처가 아니라 토글)
#
# 본체는 ~/.claude/fable/ 한 곳에만 있고, 각 설정 파일에는 얇은 로딩 코드만 있다:
# 1. ~/.claude/CLAUDE.md 끝의 @active.md 한 줄 (선언)
# 2. ~/.claude/agents/ 의 deep-reasoner/runner 심링크 (역할 배정)
# 3. ~/.bashrc 의 source ~/.claude/fable/env.sh 한 줄 (sonnet->Opus 리매핑)
# 4. ~/.claude/settings.json 의 PreToolUse 게이트 훅 (강제 장치)
#
# 토글은 설정 파일을 건드리지 않는다:
# fable on .fable-state=on, active.md -> fable.md (지침 로드)
# fable off .fable-state=off, active.md -> empty.md (지침 없음)
# fable status 상태와 로딩 코드 설치 여부 점검
#
# 적용 시점: 다음에 새로 시작하는 claude 세션부터 (떠 있는 세션은 영향 없음).
set -u
FABLE_DIR="$HOME/.claude/fable"
STATE="$HOME/.claude/.fable-state"
case "${1:-status}" in
on)
echo on > "$STATE"
ln -sfn "$FABLE_DIR/fable.md" "$FABLE_DIR/active.md"
echo "Fable 오케스트레이션 ON — 다음 claude 세션부터 적용됩니다."
;;
off)
echo off > "$STATE"
ln -sfn "$FABLE_DIR/empty.md" "$FABLE_DIR/active.md"
echo "Fable 오케스트레이션 OFF — 다음 claude 세션부터 일반 모드입니다."
;;
status)
s=$(cat "$STATE" 2>/dev/null || echo off)
echo "Fable 오케스트레이션: ${s^^}"
echo " active.md -> $(readlink "$FABLE_DIR/active.md" 2>/dev/null || echo '(없음)')"
grep -q 'fable/active.md' "$HOME/.claude/CLAUDE.md" 2>/dev/null && ok='[OK]' || ok='[--]'
echo " $ok @import ~/.claude/CLAUDE.md"
grep -q 'fable/env.sh' "$HOME/.bashrc" 2>/dev/null && ok='[OK]' || ok='[--]'
echo " $ok env 래퍼 ~/.bashrc"
[ -L "$HOME/.claude/agents/deep-reasoner.md" ] && [ -L "$HOME/.claude/agents/runner.md" ] && ok='[OK]' || ok='[--]'
echo " $ok agents ~/.claude/agents"
grep -q 'orchestration-gate' "$HOME/.claude/settings.json" 2>/dev/null && ok='[OK]' || ok='[--]'
echo " $ok 게이트 훅 ~/.claude/settings.json"
[ -x "$FABLE_DIR/hooks/orchestration-gate.py" ] && ok='[OK]' || ok='[--]'
echo " $ok 게이트 스크립트 $FABLE_DIR/hooks/orchestration-gate.py"
;;
*)
echo "사용법: fable on | off | status" >&2
exit 1
;;
esac2단계: 서브에이전트 역할 배정
두 번째 단계에서는 Fable 5가 위임할 대상을 만듭니다. Claude Code는 ~/.claude/agents/ 폴더의 마크다운 파일을 커스텀 서브에이전트로 인식하고, 파일 상단의 frontmatter로 모델과 도구를 지정할 수 있습니다.
먼저 무거운 추론 전담인 deep-reasoner입니다. 어려운 문제를 풀어서 실행 지침으로 돌려주는 역할이라 Opus 4.8에 추론 예산을 최대로 줍니다.
---
name: deep-reasoner
description: 무거운 추론·시스템 설계·아키텍처 결정·복잡한 근본원인 분석 전담.
어려운 사고가 필요한 문제만 위임하고 단순 구현에는 쓰지 말 것.
model: claude-opus-4-8
effort: max
---
당신은 심층 추론 전문가입니다. 최대 추론 예산으로 끝까지 생각하세요.
검토한 대안과 버린 이유, 리스크와 엣지케이스를 함께 제시하고 증거 기반으로
판단하며 확인하지 못한 것은 추측이라고 명시합니다. 대량 구현은 하지 말고
핵심 난제만 풀어 실행 지침으로 정리해 오케스트레이터에게 돌려주세요.다음은 잡무 전담인 runner입니다. 판단이 필요 없는 명령 실행과 조회만 맡기 때문에 Haiku 4.5로 충분하고, 도구도 읽기 계열로만 제한합니다.
---
name: runner
description: 판단이 필요 없는 잡무 전담(Haiku). 명령 실행·빌드/테스트·
파일/로그 확인·검색만 위임하고 코드 로직 변경·설계 판단에는 쓰지 말 것.
model: claude-haiku-4-5-20251001
effort: low
tools: Bash, Read, Grep, Glob
---
당신은 잡무 실행자입니다. 주어진 명령과 조회를 그대로 실행하고 결과만
간결히 보고하세요. 코드 로직 변경이나 설계 판단은 하지 않습니다.
애매하면 멈추고 그대로 오케스트레이터에게 되돌립니다.frontmatter에서 자주 쓰는 필드는 아래와 같습니다.
| 필드 | 값 예시 | 설명 |
|---|---|---|
| `name` | `deep-reasoner` | 에이전트 이름. 지침에서 이 이름으로 라우팅 |
| `description` | 위임 기준 설명 | 메인 에이전트가 언제 위임할지 판단하는 근거 |
| `model` | `claude-opus-4-8` | 이 에이전트가 사용할 모델. 생략하면 세션 모델 상속 |
| `effort` | `max`, `low` | 추론 예산. 생략하면 세션 설정 상속 |
| `tools` | `Bash, Read, Grep, Glob` | 허용 도구 목록. 생략하면 전체 도구 |
두 파일을 본체 폴더에 두고, 에이전트 폴더에는 심링크만 겁니다. 스위치 구조의 원칙대로 실제 내용은 한 곳에만 두기 위해서입니다.
$ ln -sfn ~/.claude/fable/agents/deep-reasoner.md ~/.claude/agents/deep-reasoner.md $ ln -sfn ~/.claude/fable/agents/runner.md ~/.claude/agents/runner.md $ ls -la ~/.claude/agents/ deep-reasoner.md -> /home/사용자명/.claude/fable/agents/deep-reasoner.md runner.md -> /home/사용자명/.claude/fable/agents/runner.md
3단계: Sonnet 요청을 Opus로 리매핑
세 번째 단계는 선택 사항이지만, 실행 품질을 Opus 4.8로 끌어올리고 싶다면 필요합니다. oh-my-claudecode 같은 오케스트레이션 플러그인의 실행 에이전트 다수가 frontmatter에 model: sonnet으로 고정되어 있는데, Claude Code는 ANTHROPIC_DEFAULT_SONNET_MODEL 환경변수로 이 sonnet 등급이 실제로 어떤 모델을 가리킬지 바꿀 수 있습니다.
문제는 이 환경변수를 어디에 두느냐입니다. .bashrc에 그냥 export하면 스위치를 꺼도 계속 적용됩니다. 그래서 스위치 상태를 실행 시점마다 읽는 래퍼 함수로 만듭니다.
# fable 스위치 로더 (.bashrc에서 source 됨)
# 스위치가 on이면 claude 실행 시 sonnet->Opus 리매핑을 주입한다.
_fable_on() { [ "$(cat "$HOME/.claude/.fable-state" 2>/dev/null)" = "on" ]; }
_fable_run() {
if _fable_on; then
ANTHROPIC_DEFAULT_SONNET_MODEL="claude-opus-4-8" "$@"
else
"$@"
fi
}
claude() { _fable_run command claude "$@"; }.bashrc 맨 끝에 로딩 한 줄을 추가하면 설치가 끝납니다.
# fable 오케스트레이션 스위치 로더
[ -f "$HOME/.claude/fable/env.sh" ] && . "$HOME/.claude/fable/env.sh"이제 터미널에서 claude를 치는 순간마다 래퍼가 스위치 상태를 확인하고, on일 때만 환경변수를 주입합니다. 이미 열려 있는 터미널에는 source ~/.bashrc를 한 번 실행해 줍니다.
실행 품질보다 비용이 우선이라면 이 단계를 건너뛰면 됩니다. 그 경우 sonnet 고정 에이전트는 그대로 Sonnet으로 실행되고, 나머지 계층(지침·역할 배정·강제 게이트)은 동일하게 작동합니다. 반대로 이 리매핑을 적용하면 실행은 Opus 4.8, 잡무는 Haiku 4.5로 Sonnet 등급을 건너뛰는 구성이 됩니다.
4단계: 강제 게이트 - 권고를 물리 차단으로
마지막 계층이 이 가이드의 핵심입니다. 앞의 세 단계는 전부 권고입니다. 지침이 로드되어 있어도 모델이 스스로 판단해서 직접 코드를 고치는 일은 언제든 일어날 수 있습니다. 이걸 막는 장치가 PreToolUse 훅입니다.
PreToolUse 훅은 Claude Code가 도구를 실행하기 직전에 호출되는 스크립트입니다. 스크립트가 종료 코드 2로 끝나면 그 도구 호출이 차단되고, stderr에 적은 메시지가 모델에게 전달됩니다. 게이트의 규칙은 다음과 같습니다.
- 메인 에이전트는 한 턴에 코드 파일을 2개까지만 직접 수정할 수 있습니다. 3개째부터 차단되고, 위임하라는 지시가 모델에게 돌아갑니다.
- 같은 파일을 다시 고치는 것은 개수에 세지 않습니다. 마크다운, JSON, YAML 같은 설정·문서 파일도 제한하지 않습니다.
- Bash로 코드 파일을 고치는 우회 경로(sed -i, perl -i, 리다이렉트, tee)는 개수와 무관하게 차단합니다.
- 스위치가 off면 모든 검사를 건너뛰고 통과시킵니다.
서브에이전트를 구분하는 방법
게이트 설계에서 가장 조심해야 할 부분이 서브에이전트 처리입니다. PreToolUse 훅은 서브에이전트의 도구 호출에도 발화되기 때문에, 구분 없이 차단하면 위임받은 실행 에이전트까지 막혀서 오케스트레이션 전체가 죽습니다.
다행히 공식적으로 지원되는 구분 방법이 있습니다. 훅이 서브에이전트 안에서 발화되면 stdin으로 들어오는 JSON에 agent_id와 agent_type 필드가 추가됩니다. 메인 에이전트의 호출에는 이 필드가 없습니다. 실제 페이로드를 비교하면 차이가 명확합니다.
// 메인 에이전트의 Write 호출 - agent 필드 없음
{
"session_id": "75f02f85-...",
"prompt_id": "6d225c46-...",
"tool_name": "Write",
"tool_input": { "file_path": "/project/a.ts" }
}
// 서브에이전트의 Write 호출 - agent_id, agent_type 존재
{
"session_id": "75f02f85-...",
"prompt_id": "6d225c46-...",
"agent_id": "a513e1e15cbfd1608",
"agent_type": "general-purpose",
"tool_name": "Write",
"tool_input": { "file_path": "/project/b.ts" }
}prompt_id도 함께 눈여겨볼 필드입니다. 사용자가 프롬프트를 보낼 때마다 새 값이 부여되기 때문에, 이 값이 바뀌면 "새 턴이 시작됐다"로 보고 파일 카운터를 리셋할 수 있습니다. 별도의 턴 감지 훅 없이 PreToolUse 훅 하나로 턴당 제한이 구현되는 이유입니다.
게이트 스크립트의 핵심 판별 로직은 아래와 같습니다.
data = json.load(sys.stdin)
# 스위치가 off면 즉시 통과
if state != "on":
allow()
# 서브에이전트는 게이트 대상이 아님 - 위임이 실행 경로다
if data.get("agent_id") or data.get("agent_type"):
allow()
# 같은 턴(prompt_id)에서 수정한 코드 파일이 이미 2개면 차단
if len(gate["files"]) >= LIMIT:
deny("추가 코드 수정은 서브에이전트에 위임하세요: ...")전체 스크립트에는 코드 파일 확장자 판별, Bash 우회 패턴 감지, 턴 카운터 저장과 리셋, 오류 시 통과(fail-open) 처리까지 포함됩니다. 게이트가 버그로 죽어도 세션이 마비되지 않게 하는 안전장치까지 갖춘 전체 스크립트는 다음과 같습니다.
#!/usr/bin/env python3
# ~/.claude/fable/hooks/orchestration-gate.py — fable 강제 게이트 (PreToolUse)
#
# 메인 에이전트가 한 턴(prompt_id)에 코드 파일을 2개까지만 직접 수정하게 허용하고,
# 3개째부터 차단하며 위임 지시를 돌려준다. Bash 를 통한 코드 파일 수정은 항상 차단.
# 서브에이전트(payload 에 agent_id/agent_type 존재)는 전부 통과 — 위임이 실행 경로다.
#
# fable off 상태(~/.claude/.fable-state != on)면 즉시 통과. 등록은 settings.json 에
# 1회(로딩 코드), 동작 여부는 스위치가 지배한다.
# 오류 시 fail-open(통과) — 게이트 버그가 세션을 마비시키지 않게 한다.
import json
import os
import re
import sys
import time
LIMIT = 2
STATE_FILE = os.path.expanduser("~/.claude/.fable-state")
GATE_STATE_DIR = os.path.expanduser("~/.claude/fable/state")
CODE_EXTS = {
"ts", "tsx", "js", "jsx", "mjs", "cjs", "py", "go", "rs", "java", "kt",
"kts", "swift", "c", "h", "cc", "cpp", "hpp", "cs", "rb", "php", "vue",
"svelte", "astro", "css", "scss", "sass", "less", "html", "sh", "bash",
"zsh", "sql", "lua", "dart", "scala", "ex", "exs", "zig", "ipynb",
}
EXT_RE = "|".join(sorted(CODE_EXTS))
def allow():
sys.exit(0)
def deny(msg):
sys.stderr.write(msg)
sys.exit(2)
def is_code(path):
return path.rsplit(".", 1)[-1].lower() in CODE_EXTS if "." in path else False
def main():
try:
state = open(STATE_FILE).read().strip()
except OSError:
state = "off"
if state != "on":
allow()
data = json.load(sys.stdin)
if data.get("agent_id") or data.get("agent_type"):
allow() # 서브에이전트는 게이트 대상이 아님
tool = data.get("tool_name", "")
tool_input = data.get("tool_input") or {}
if tool == "Bash":
cmd = tool_input.get("command", "")
writes_inplace = re.search(r"\b(sed|perl)\s+[^|;&]*-\w*i", cmd)
writes_redirect = re.search(
r"(?:>>?|\btee\b(?:\s+-\w+)*)\s*['\"]?[^\s'\"|;&<>]+\.(?:%s)\b" % EXT_RE, cmd
)
touches_code = re.search(r"\.(?:%s)\b" % EXT_RE, cmd)
if writes_redirect or (writes_inplace and touches_code):
deny(
"[fable 게이트] 메인 에이전트는 Bash로 코드 파일을 수정할 수 없습니다. "
"코드 수정은 Edit/Write 도구(턴당 %d개 파일까지)를 쓰거나 서브에이전트에 위임하세요: "
"일반 구현·수정 → 실행 에이전트(Opus), 심층 추론·설계 → deep-reasoner, 단순 잡무 → runner." % LIMIT
)
allow()
if tool not in ("Edit", "Write", "NotebookEdit", "MultiEdit"):
allow()
path = tool_input.get("file_path") or tool_input.get("notebook_path") or ""
if not path or not is_code(path):
allow() # 설정·문서 등 비코드 파일은 직접 수정 허용
session = re.sub(r"[^a-zA-Z0-9-]", "", data.get("session_id", "nosession"))
prompt = data.get("prompt_id", "")
os.makedirs(GATE_STATE_DIR, exist_ok=True)
gate_file = os.path.join(GATE_STATE_DIR, session + ".json")
gate = {"prompt_id": prompt, "files": []}
try:
saved = json.load(open(gate_file))
if saved.get("prompt_id") == prompt:
gate = saved
else: # 새 턴 — 카운터 리셋 + 오래된 세션 상태 정리
now = time.time()
for fn in os.listdir(GATE_STATE_DIR):
p = os.path.join(GATE_STATE_DIR, fn)
if now - os.path.getmtime(p) > 86400:
os.remove(p)
except (OSError, ValueError):
pass
if path in gate["files"]:
allow() # 같은 파일 재수정은 개수에 안 침
if len(gate["files"]) < LIMIT:
gate["files"].append(path)
json.dump(gate, open(gate_file, "w"))
allow()
deny(
"[fable 게이트] 이번 턴에 메인 에이전트가 직접 수정한 코드 파일이 이미 %d개입니다 (%s). "
"추가 코드 수정은 반드시 서브에이전트에 위임하세요: 일반 구현·수정·테스트·리뷰 → 실행 에이전트(Opus), "
"심층 추론·설계·근본원인 분석 → deep-reasoner, 단순 명령·조회 → runner. "
"지금 하려던 수정(%s)을 명확한 지침과 함께 Agent 도구로 위임하면 됩니다."
% (LIMIT, ", ".join(gate["files"]), path)
)
if __name__ == "__main__":
try:
main()
except SystemExit:
raise
except Exception:
sys.exit(0) # fail-open스크립트를 ~/.claude/fable/hooks/orchestration-gate.py로 저장하고 실행 권한을 준 다음, ~/.claude/settings.json에 훅을 등록합니다. 이 등록은 로딩 코드라서 한 번만 하면 되고, 이후 켜고 끄는 것은 스크립트 안의 스위치 확인이 처리합니다.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit|NotebookEdit|MultiEdit|Bash",
"hooks": [
{
"type": "command",
"command": "/home/사용자명/.claude/fable/hooks/orchestration-gate.py"
}
]
}
]
}
}실제로 차단되는지 검증하기
강제 게이트는 만들어놓는 것보다 실제로 차단되는지 증명하는 것이 중요합니다. 규칙을 적는 것과 규칙을 어겼을 때 실제로 막히는 것은 다른 문제이기 때문입니다.
CLAUDE.md에 위임 지침이 로드되어 있어도 모델이 판단에 따라 코드 파일 3개, 4개를 직접 수정하는 일이 일어난다. 지침은 권고라서 어긴다고 막히지 않는다.
3번째 코드 파일 수정 시도가 도구 호출 단계에서 차단되고, 모델은 위임 지시 메시지를 받는다. 파일은 디스크에 생성되지 않는다. 서브에이전트의 수정은 개수 제한 없이 통과한다.
검증은 새 터미널에서 헤드리스 세션으로 진행합니다. 먼저 메인 에이전트에게 코드 파일 3개를 만들게 시키고, 이어서 같은 작업을 서브에이전트에게 위임시켜 결과를 비교합니다.
$ claude -p "c1.ts, c2.ts, c3.ts 세 파일을 순서대로 생성해라. 차단되면 BLOCKED: 뒤에 사유를 출력하고 멈춰라." BLOCKED: [fable 게이트] 이번 턴에 메인 에이전트가 직접 수정한 코드 파일이 이미 2개입니다 $ ls *.ts c1.ts c2.ts $ claude -p "서브에이전트를 띄워서 d1.ts, d2.ts, d3.ts 세 파일을 생성하게 해라." SUB_DONE $ ls d*.ts d1.ts d2.ts d3.ts
기대한 대로 메인 에이전트는 2개까지만 만들고 3개째에서 차단됐습니다. c3.ts는 디스크에 존재하지 않습니다. 반면 서브에이전트는 3개를 전부 만들었습니다. 위임 경로는 손상되지 않았다는 뜻입니다. 이 두 결과가 함께 확인되어야 게이트가 완성된 것입니다.
일상 사용법
설치가 끝나면 일상 조작은 명령 세 개가 전부입니다.
| 명령 | 동작 |
|---|---|
| `fable on` | 지침 로드 + 리매핑 + 게이트 활성. 다음 세션부터 적용 |
| `fable off` | 전부 비활성. 로딩 코드는 그대로 두고 심링크와 상태만 전환 |
| `fable status` | 현재 상태와 로딩 코드 4종의 설치 여부 점검 |
$ fable status Fable 오케스트레이션: ON active.md -> /home/사용자명/.claude/fable/fable.md [OK] @import ~/.claude/CLAUDE.md [OK] env 래퍼 ~/.bashrc [OK] agents ~/.claude/agents [OK] 게이트 훅 ~/.claude/settings.json [OK] 게이트 스크립트 ~/.claude/fable/hooks/orchestration-gate.py
CLAUDE.md와 훅은 세션 시작 시점에 로드됩니다. fable on/off를 실행해도 이미 떠 있는 세션에는 적용되지 않고, 다음에 새로 시작하는 세션부터 반영됩니다. env.sh의 래퍼 함수는 claude를 실행하는 순간마다 상태를 읽기 때문에 터미널을 새로 열 필요까지는 없습니다.
지휘자 모델 자체는 스위치가 건드리지 않습니다. Claude Code 안에서 /model 명령으로 Fable 5를 기본 모델로 저장해 두면, 그 위에 이 오케스트레이션 계층이 얹히는 구조입니다.
주의할 점
운영하면서 알아둬야 할 한계와 설계 의도를 정리합니다. 게이트를 과신하지 않고 쓰기 위해 필요한 내용입니다.
- 게이트는 완벽한 차단막이 아니라 폭주 방지 장치입니다. Bash 우회 패턴 감지는 sed -i, perl -i, 리다이렉트, tee 같은 대표 경로를 막지만, 이론상 다른 우회가 불가능하지는 않습니다. 그래서 지침에도 "Bash로 코드를 수정하지 않는다"를 함께 적어 운영 규칙과 물리 장치를 겹으로 둡니다.
- 게이트 스크립트는 오류가 나면 차단이 아니라 통과를 선택합니다(fail-open). 게이트 버그 하나가 모든 도구 호출을 막아 세션을 마비시키는 것보다, 그 턴을 놓치는 쪽이 낫다는 설계입니다.
- 제한 대상은 코드 파일뿐입니다. 마크다운 문서나 설정 파일 정리는 지휘자가 직접 해도 토큰 손해가 크지 않고, 이것까지 막으면 사소한 작업마다 왕복 비용이 생기기 때문입니다.
- 셸 래퍼를 거치지 않는 실행 경로(IDE 확장 등)에서는 3단계의 Sonnet 리매핑이 적용되지 않습니다. 지침에 "래퍼 없는 세션에서는 위임 시 model을 명시하라"는 문구를 넣어 보완합니다.
- 다른 플러그인의 훅과는 충돌하지 않습니다. Claude Code는 등록된 PreToolUse 훅을 전부 실행하고 그중 하나라도 차단하면 막는 구조라서, 조언을 주입하는 기존 훅과 차단 게이트가 나란히 작동합니다.
- 턴 경계 감지에 쓰는 prompt_id 필드는 Claude Code v2.1.196 이상에서 제공됩니다. 그 이전 버전에는 이 필드가 없어 턴마다 카운터가 리셋되지 않으므로, 게이트를 쓰기 전 claude --version으로 버전을 확인하는 편이 안전합니다.
advisor 전략 - 같은 원리를 SDK에서 쓰는 법
지금까지 만든 구조는 Fable 5가 지휘석에 앉아 실행을 아래로 위임하는 방식입니다. Anthropic이 공식으로 제안하는 advisor 전략도 뿌리는 똑같습니다. 비싼 모델은 판단에만 쓰고 실제 실행은 싼 모델에 맡겨서, 최상위 품질에 가까운 결과를 훨씬 낮은 비용으로 얻는 것입니다. 달라지는 건 Fable 5를 어디에 앉히느냐 하나입니다.
이 가이드에서는 Fable 5가 대화를 주도하며 서브에이전트로 일을 내려보냅니다. advisor 전략에서는 싼 실행자가 작업을 처음부터 끝까지 주도하고, 스스로 풀기 어려운 결정에 부딪혔을 때만 Fable 5에게 물어봅니다. Fable 5는 계획, 교정, 중단 신호만 돌려주고 실행자가 다시 이어갑니다. 판단은 늘 Fable 5가 쥐고 실행은 싼 모델이 맡는다는 점은 어느 쪽이든 같습니다.
| 구분 | 이 가이드 (지휘-위임) | advisor 전략 (자율실행-상담) |
|---|---|---|
| 메인 루프 | Fable 5 | Opus 4.8 또는 Haiku 4.5 |
| 판단·설계 | Fable 5가 직접, 실행만 위임 | 어려울 때만 Fable 5에 상담 |
| 구현 표면 | Claude Code CLI (서브에이전트·훅) | Claude API/SDK (advisor 툴) |
| 강제 방식 | PreToolUse 게이트로 물리 차단 | max_uses로 상담 횟수 제한 |
블로그의 예시는 advisor에 Opus, 실행자에 Sonnet 4.6이나 Haiku 4.5를 놓습니다. 우리 셋업에서는 최상위 모델이 Fable 5라서 advisor 자리에 Fable 5를 놓고, 실행자로는 Opus 4.8이나 Haiku 4.5를 둡니다. advisor 전략은 Claude API의 공식 기능이라 CLI 스위치가 아니라 코드에서 툴로 선언합니다.
response = client.messages.create(
model="claude-opus-4-8", # executor: 대부분의 실행을 담당
tools=[
{
"type": "advisor_20260301",
"name": "advisor",
"model": "claude-fable-5", # advisor: 어려운 판단만 최상위 모델에
"max_uses": 3,
},
],
messages=[...],
extra_headers={"anthropic-beta": "advisor-tool-2026-03-01"},
)실행자와 advisor가 주고받는 모든 과정은 단일 /v1/messages 요청 안에서 끝나 추가 왕복이 없습니다. advisor 토큰만 최상위 모델 요금으로, 실행자 토큰은 실행자 모델 요금으로 따로 청구됩니다. 비용을 더 아끼려면 실행자를 Haiku 4.5로 내리면 됩니다.
효과는 Anthropic이 공개한 측정치로 가늠할 수 있습니다. 실행자 Sonnet에 advisor를 붙인 구성은 SWE-bench Multilingual 점수가 2.7% 오르면서 비용은 11.9% 줄었고, 실행자 Haiku에 advisor를 붙인 구성은 BrowseComp 점수가 41.2%로 Haiku 단독의 19.7%보다 두 배 넘게 올랐습니다. 이 수치는 advisor로 Opus를 쓴 Anthropic 공식 블로그 "The advisor strategy" 기준이며, advisor를 Fable 5로 올리면 판단 품질은 더 높아지고 advisor 토큰 단가도 그만큼 올라갑니다.
두 방식은 상황에 맞춰 갈라 씁니다. Claude Code 안에서 복잡한 멀티스텝 작업을 통째로 맡길 때는 이 가이드의 지휘-위임이 맞고, SDK로 에이전트를 직접 짜면서 대부분 싼 모델로 돌리되 가끔 최상위 판단만 빌리고 싶을 때는 advisor 전략이 맞습니다.
FAQ
Sonnet을 아예 안 쓰는 이유가 있습니까?
fable on을 했는데 지금 세션에 적용이 안 됩니다.
게이트가 정당한 작업까지 막으면 어떻게 합니까?
서브에이전트까지 차단되는 사고는 없습니까?
완전히 제거하려면 어떻게 합니까?
oh-my-claudecode 같은 오케스트레이션 플러그인과 같이 써도 됩니까?
CLAUDE.md 작성법 — AI 에이전트를 내 프로젝트에 맞추는 방법
Claude Code가 모든 세션에서 읽는 프로젝트 지침 파일 CLAUDE.md의 위치 계층, 작성 전략, 실전 예시, 팀 공유 패턴, AGENTS.md·GEMINI.md와의 차이를 한 문서에 정리했습니다.
AI 자동화 하네스 완전 가이드 — 프롬프팅과 컨텍스트 엔지니어링으로 AI 워크플로우 구축하기 (2026년 3월 기준)
AI 하네스는 단순 프롬프트를 넘어, 프롬프팅·컨텍스트·도구·검증을 하나의 파이프라인으로 묶은 자동화 시스템입니다. Claude Code와 CLAUDE.md를 중심으로 실전 하네스를 직접 설계하고 운영하는 방법을 처음부터 끝까지 정리했습니다.
Claude Code 플러그인·스킬 만들기 튜토리얼
Claude Code의 스킬(Skill)을 처음부터 만들어봅니다. SKILL.md 작성법, 프론트매터 옵션 전체, 동적 인자와 컨텍스트 주입, 서브에이전트 연동, 플러그인 패키징까지 단계별로 따라 할 수 있습니다.
