며칠 전에 fable-mode를 공개했다. Claude Code에서 Opus 4.8을 Fable 5처럼 행동하게 만드는 훅 묶음이다. 규범 블록을 매 턴 모델에게 다시 먹여서, 결론부터 말하고 시키지 않은 걸 건드리지 않게 만든다.

공개하고 나서 마음에 걸리는 게 두 개 있었다.

하나는 그 규범 블록이 1,377자짜리라는 점이다. 매 턴 들어간다. "git log 좀 보여줘" 같은 한 줄짜리 질문에도 860토큰을 앞에 붙여 넣고 있었다. 둘째는 비용 숫자가 너무 예뻤다는 점이다. 0.70배. 세 과제 평균이었는데, 과제별로 뜯어보니 그 평균 뒤에 훨씬 지저분한 그림이 있었다.

v1.4는 그 두 개를 고친 기록이다. 그리고 고치는 김에, 지난번엔 넘어갔던 회귀 하나를 신뢰가 아니라 코드로 봉인했다.

매 턴 같은 1,377자를 먹이는 건 낭비였다

규범을 매 턴 재주입하는 건 의도된 설계다. 긴 대화에서 시스템 프롬프트 맨 앞의 지시는 최근 대화에 묻힌다. Anthropic의 프롬프트 엔지니어링 문서도 지시를 컨텍스트에서 잘 보이는 위치에 두라고 말한다. 그래서 fable-mode는 규범을 "항상 가장 최근" 위치, 즉 사용자 메시지 바로 앞에 매 턴 새로 꽂는다.

문제는 그게 모든 턴에 필요하진 않다는 거였다.

일을 시키는 턴에서는 전체 규범이 다 필요하다. 스코프를 지켜라, 파괴적 행동 전에 실물을 확인해라, 완료 주장은 툴 결과로 증명해라 — 이런 조항들은 실제로 코드를 만지는 턴에서만 의미가 있다. 그런데 "rebase랑 merge 차이가 뭐야?" 같은 턴에는 그 조항들이 통째로 죽은 무게다. 그 턴에 필요한 건 "결론부터, 산문으로, 완전한 문장으로" 정도다.

그래서 v1.4는 턴 크기에 따라 주입량을 나눴다.

major 턴 (일 시키는 턴) · 세션 첫 턴 · 5턴마다  →  풀 블록 1,377자 (≈860토큰)
그 외 minor 턴 (짧은 질답)                      →  리마인더 214자 (≈134토큰)

리마인더는 규범 전체를 반복하지 않는다. "이전에 주입된 상시 규범 전체가 계속 유효하다"로 시작해서 핵심 여섯 개만 한 줄로 나열한다. 앵커를 유지하되 무게를 덜어내는 것이다. 5턴마다 한 번씩은 minor 턴이어도 풀 블록을 다시 넣어서, 긴 대화에서도 규범이 완전히 흐려지지 않게 잡아준다.

major 판정은 단순하다. 프롬프트가 80자를 넘거나, 작업 동사(만들·구현·고쳐·커밋·배포 같은)가 들어 있으면 major다. 짧은 영어 동사(run, test, make)는 일부러 뺐다. 다른 단어 안에 부분 문자열로 걸려서 오탐이 나기 때문이다. 대신 80자 규칙이 그런 프롬프트를 대부분 잡아준다.

효과는 계산으로 나온다. 짧은 질답이 전체 턴의 절반이면 세션당 주입 오버헤드가 34% 줄고, 70%면 47%, 80%면 54% 준다. minor 턴 하나만 놓고 보면 860토큰이 134토큰으로 84% 줄어든다.

내가 얼마를 쓰는지, 이제 세션이 직접 말해준다

숫자로 주장을 하려면 그 숫자를 관측할 수 있어야 한다. 그전엔 오버헤드가 "대충 이 정도"였다. v1.4는 매 주입을 파일에 기록한다.

# 매 주입마다 한 줄씩 쌓인다: 타입<TAB>문자수
full	1377
lite	214
lite	214
lite	214
full	1377

/fable-mode status를 치면 이 파일을 읽어서 이 세션에서 풀 블록을 몇 번, 리마인더를 몇 번 넣었는지, 토큰으로 환산하면 얼마인지 알려준다. 이 글에 쓴 84%, 34% 같은 숫자도 전부 여기서 나왔다. 추정이 아니라 로그다.

이게 별거 아닌 것 같아도, 프롬프트로 모델을 조종하는 도구를 만들 때 가장 흔한 실수가 "얼마나 먹이고 있는지 모르는 것"이다. 컨텍스트에 텍스트를 계속 밀어 넣으면서 그 비용을 안 재면, 어느 순간 배보다 배꼽이 커진다. 관측 가능하게 만들어두면 그 함정에 빠지지 않는다.

신뢰 대신 grep

v1.3에서 실제로 터졌던 회귀가 하나 있었다.

fable-mode는 major 턴이 끝나기 직전에 자가검증을 강제한다. Claude Code의 Stop 훅decision:block을 반환하면 모델이 턴을 못 끝내고 한 바퀴 더 돈다. 그때 "완료 주장에 근거 있나? 마지막 문단이 약속이면 지금 실행해라"를 점검시킨다. Fable이 훈련으로 갖고 있는 "끝내기 전 스스로 검증" 습관을, Opus에겐 외부 루프로 강제하는 셈이다.

그런데 그 점검 지시문이 사용자 출력에 새는 일이 있었다. 모델이 재마무리를 하면서 "자가검증 결과 문제 없었습니다" 같은 문장을 최종 답변에 그대로 남긴 것이다. 내부 지침이 사용자에게 노출되면 안 된다.

v1.3에서는 이걸 "출력에 이 점검 언급하지 마라"라는 지시 한 줄을 규범에 추가하는 것으로 막았다. 모델을 믿는 방식이다. 대부분은 지켜진다. 대부분은.

v1.4는 그걸 안 믿기로 했다. 턴이 끝날 때 최종 assistant 텍스트를 grep한다.

# 종료 직전, 최종 메시지에 내부 지침 용어가 남았는지 결정론적으로 스캔
if printf '%s' "$LAST_TEXT" | grep -qiE '자가검증|self-check|fable-mode|규범 블록'; then
  # 감지되면 .leakfix 마커를 남기고 딱 1회 재작성을 강제
  ...
fi

용어가 감지되면 재작성을 한 번 강제한다. .leakfix 마커로 두 번은 안 돌게 막아서 무한 루프가 안 생긴다. 재작성 지시에는 함정도 하나 넣었다. 그 용어가 사용자 과제의 주제 자체일 때 — 예를 들어 지금 이 글처럼 fable-mode 킷 자체를 개발하는 세션 — 는 그대로 다시 제출하라고 명시했다. 안 그러면 fable-mode를 논하는 세션에서 정상적인 답변까지 매번 재작성될 테니까.

핵심은 이거다. 프롬프트로 강제한 행동은 프롬프트로만 검증하면 안 된다. 결정론적으로 확인할 수 있는 건 결정론적으로 확인하는 게 맞다. 모델에게 "하지 마라"라고 부탁하는 것과, 했는지 코드로 검사하는 것은 신뢰 수준이 다르다.

예뻤던 0.70배의 진실

이제 불편한 부분이다. 지난 글에서 비용이 Fable의 0.70배라고 썼다. v1.4를 만들면서 과제를 네 종류로 늘리고 다시 측정했다. 합산은 0.72배로 비슷했다. 그런데 평균 뒤가 문제였다.

과제별로 스택(Opus+fable-mode)과 Fable의 비용을 나란히 놓으면 이렇다.

과제 순정 Opus +스택 Fable 스택/Fable
진단 $0.23 $0.28 $0.49 0.57×
구현 $0.34 $0.45 $0.49 0.91×
모호한 정리 $0.86 $1.13 $1.52 0.74×
단순 질문 $0.12 $0.13 $0.27 0.49×

구현 과제를 보라. 스택이 Fable보다 9%밖에 안 쌌다. 자가검증 턴을 돌면서 토큰을 더 쓰기 때문이다. 모호한 정리 과제는 더 노골적이다. 스택이 순정 Opus보다 31% 더 썼다. $0.86이 $1.13이 됐다. 규범을 지키게 하려고 교정 턴을 더 돌린 값이다.

정직하게 말하면, fable-mode는 공짜가 아니다. 품질이 아니라 턴으로 지불한다. Fable은 첫 시도에 제대로 하고, 스택은 몇 번 고쳐가며 도달한다. 그 교정 턴이 토큰을 먹는다.

그런데도 합산이 0.72배로 유지되는 건 단가 때문이다. Fable은 토큰 100만 개당 입력 $10, 출력 $50이고 Opus 4.8은 그 절반이다. Fable은 출력 토큰을 절반만 쓰는데도(이번 4과제 합 9,800토큰 대 스택 20,000토큰) 단가 2배가 그걸 역전시킨다. 재미있는 건 산출물이었다. slugify 구현 과제에서 순정·스택·Fable 셋이 만든 코드를 같은 테스트 케이스로 돌렸더니 출력이 전부 똑같았다. 진단 과제도 셋 다 같은 원인을 짚고 셋 다 수정 없이 진단만 했다. 결론은 같고, 갈리는 건 모호한 과제에서 각자 고른 "합리적 디폴트"뿐이었다.

이 벤치도 완벽하진 않다. 과제당 한 번씩만 돌렸고, 도중에 구독 세션 한도에 두 번 걸려서 재실행을 반복했다. 그 과정에서 웃픈 것도 하나 발견했다. "이 폴더 정리해줘" 벤치를 돌렸더니 순정 Opus와 Fable이 둘 다 실행 중이던 측정 파일(result.json)까지 아카이브 폴더로 옮겨버렸다. 정리를 시켰으니 정리를 한 건데, 측정 도구가 자기 자신을 치운 셈이다. 다음 벤치부터 측정 파일은 과제 폴더 밖에 두기로 했다.

남는 것

v1.4로 네 번째 훅도 자리를 잡았다. 서브에이전트는 매 턴 주입 이벤트를 못 받는다. 팬아웃 작업에서 부하 에이전트들만 규범 없이 순정으로 돌던 구멍이었는데, SubagentStart 훅이 스폰 순간에 규범을 끼워 넣어 막았다. v1.4에서는 그 중복 방지 검사를 플러그인 경로까지 확장했다.

정직하게 남길 것도 있다. 97%는 행동 점수지 지능 점수가 아니다. 얽힌 단일 패스 추론과 장기 자율 런은 웨이트의 영역이라 프롬프트로 못 닫는다. 그 일감은 그냥 Fable로 보내는 게 맞고, Opus로 가야 한다면 팬아웃과 적대 검증으로 보정하는 수밖에 없다.

만드는 원리와 실측을 각각 슬라이드 덱으로도 정리해뒀다. 킷은 Claude Code 세션에서 두 줄이면 깔린다.

이번 릴리스가 가르쳐준 건 도구 자체보다 태도였다. 프롬프트로 모델을 조종할 땐, 얼마를 먹이는지 재고, 강제한 걸 코드로 검증하고, 예쁜 평균 뒤의 지저분한 과제별 숫자를 숨기지 않는 것. 그게 신뢰를 만든다.

프롬프트로 강제한 행동은, 프롬프트로만 믿지 마라. 잴 수 있는 건 재고, grep할 수 있는 건 grep해라.