AI 코딩 도구가 믿을 만한지는 '모델이 얼마나 똑똑한가'로 정해지지 않는다. 진짜로 그걸 굴리는 건 모델을 감싸 통제하는 장치, 곧 하네스(harness)다. 그리고 하네스의 힘은 모델의 지능이 아니라 그 주변을 채운 결정론적 인프라(입력이 같으면 결과도 늘 같은, AI 추론이 아닌 코드 장치 — 클로드 코드 소스에서 98.4%)에서 나온다. 규칙 문서(rule)·기능(skill)·작업 흐름(workflow)·기억(memory)·여러 AI를 지휘하는 조율기(orchestrator)가 전부 그 위에 얹히는 맨 아래 토대 — 그게 바로 hook pipeline(도구를 실행할 때마다 끼어드는 감시 스크립트들의 사슬)이다. 지시문이나 규칙은 문서에 적힌 '이렇게 해줘'라는 부탁(SOFT)이라, 모델이 입력만 살짝 바꾸면 피해 가고 대화가 길어져 자동 요약될 때(이른바 compaction)에 조용히 사라진다. 반대로 hook이 돌려주는 종료 신호 exit code(작업을 통과시킬지 막을지 알리는 숫자, HARD)는 모델이 손댈 수 없는 바깥 영역에서 작동해 아무도 무시하지 못하게 길을 막는다. 나는 13개의 순간(이벤트)마다 127개의 감시 스크립트를 걸어, 286개 규칙 중 핵심을 exit 2(차단 신호)로 강제한다.
결론부터 말하면, AI 에이전트가 믿을 만해지는 힘은 '똑똑한 추론'이 아니라 대부분 '정해진 대로만 도는 코드'에서 나온다. 클로드 코드(이 AI 코딩 도구)의 소스를 뜯어보면 에이전트 신뢰성의 98.4%가 결정론적 인프라(입력이 같으면 결과도 같은 코드 장치 — 권한을 막는 검문소, 대화 맥락 관리, 오류 복구 등)에서 나오고, AI 추론이 기여하는 몫은 1.6%뿐이다. 하네스를 건물처럼 층으로 그려 보면 맨 위가 모델의 추론이고, 그 아래가 여러 AI를 지휘하는 조율기(orchestrator)·기능(skill)·규칙(rule)·기억(memory) 같은 '운영 지식'이다. 그리고 이 모든 층이 실제로 지켜지도록 강제하는 맨 아래 토대가 바로 hook pipeline(도구를 실행할 때마다 끼어드는 감시 스크립트들)이다. 규칙(rule)이 '무엇을 하라'고 적어둔 종이라면, hook은 '안 하면 아예 못 지나간다'고 길을 막는 문지기다. 이 토대가 없으면 위층은 지켜도 그만 안 지켜도 그만인 부탁일 뿐이다.
이 그림은 하네스가 건물처럼 층으로 쌓인다는 걸 보여준다. 맨 위가 모델의 추론, 그 아래가 조율기(orchestrator)·기능(skill)·규칙(rule)·기억(memory)이다. 이 위층은 전부 '이렇게 해줘'라는 부탁(SOFT)이라 스스로는 강제력이 없다. 실제로 막고 통과시키는 힘은 맨 아래 토대인 hook의 종료 신호(exit code)에서만 나온다. 모델이 쓰는 모든 도구 호출은 반드시 이 토대를 거쳐 간다.
그렇다면 왜 규칙(SOFT)은 토대가 못 되고 hook(HARD)만 토대가 될까? 똑같은 내용의 규칙이라도, 그것을 어디에 두느냐가 강제력을 가른다. 문서에 적으면 부탁이 되고, 감시 스크립트에 넣으면 문지기가 된다.
SOFT — prose
규칙 문서 · 시스템 지시문
"이렇게 해주세요" — 지켜주길 바라는 부탁
설정 문서(CLAUDE.md·규칙 파일)에 그냥 글로 적혀 있다
모델이 읽는 영역 안이라 → 입력만 바꿔도 피해 간다
대화가 길어져 자동 요약되면 조용히 사라진다
지켜질지는 오로지 모델의 "협조"에 달렸다
무시해도 아무 일도 일어나지 않는다
HARD — hook exit code
PreToolUse · Stop hook
"조건 안 맞으면 → 차단" — 구조로 막는 강제
감시 스크립트(hooks 폴더의 .sh 파일)가 별도 프로그램으로 실행된다
모델 바깥에서 돌아 → 입력으로 피할 수 없다
정해진 순간마다 늘 실행된다 (대화 요약과 무관)
종료 신호 0 = 통과 · 2 = 도구 실행 차단
증거 파일이 없으면 통과 자체가 안 된다
부탁(SOFT)은 모델이 읽는 영역 안에 있어서, 대화가 요약될 때 사라지거나 입력만 바꿔도 우회당한다. 같은 규칙이 반복해서 무시되는 게 감지되면, 그 규칙을 hook(별도 프로그램)으로 옮겨 종료 신호로 막는 문지기로 '승격'시킨다. 그러면 더는 무시할 수 없다.
Structure · Lifecycle
토대의 구조 — AI가 일하는 모든 길목에 끼어든다
AI가 한 번 일하는 흐름(세션이라 부른다)은 아무 검문 없이 흘러가지 않는다. 세션 시작 → 사용자 입력 → 도구 실행(여러 번) → 종료로 이어지는 모든 길목마다 hook(감시 스크립트)이 끼어든다. 나는 이 13개의 길목마다 clawd-hook.js라는 공통 관측 스크립트(무슨 일이 일어났는지 기록하는 역할)를 깔고, 그 위에 길목별로 문지기와 정보 주입기를 얹었다. 역할은 네 가지다 — 차단(막기, 종료 신호 2) · 정보 주입(끼워 넣기, 맥락 보강) · 검사(확인·기록) · 관측(지켜보기).
흐름은 이렇다. 세션 시작(SessionStart) → 사용자 입력(UserPromptSubmit, 이때 맥락을 주입) → [도구 실행 전 검문(PreToolUse, 조건 안 맞으면 종료 신호 2로 차단) → 도구 실행 → 실행 후 검사(PostToolUse)]를 도구마다 반복 → 종료 시도(Stop, 마지막 재검증) → 세션 끝(SessionEnd). 대화를 자동 요약하기 직전(PreCompact) 같은 갈래 길목이 그 경계를 지킨다. 검문에서 종료 신호 2가 나오면 그 도구 호출이나 종료 자체가 아예 일어나지 않는다.
exit 2(차단 신호)를 돌려주는 hook은 그 도구 호출 자체를 물리적으로 막아 버린다. 아래 문지기들은 '다 끝냈다'는 모델의 자기 보고를 믿지 않는다. 대신 실제 증거(증거 파일·지문 해시·합격 조건 통과 기록)를 요구한다. 증거가 없으면 코드 올리기(push)도, 저장(commit)도, 작업 흐름(workflow) 실행도, 세션 종료도 통과되지 않는다.
qa-gate-before-push.shPreToolUse[Bash]
코드를 고쳤는데 'QA(품질 검사)를 통과했다'는 증거 파일 .qa-cycle-passed(합격 표시·지문·1시간 이내 기록)이 없으면 코드 올리기(push) 차단. 지문이 지금 코드와 안 맞아도 막는다.
웹 파일에서 브라우저 저장소 localStorage를 쓰는 것을 차단 (로그인 토큰 저장만 예외). 사용자 데이터는 브라우저가 아니라 서버에 둬야 하기 때문이다.
scaffold-violation-check.shPreToolUse · exit 2 ×2
프로젝트마다 정해둔 뼈대 규칙에서 '절대 하지 마라(NEVER DO)' 항목을 어기면 즉시 차단한다. 그 프로젝트의 관례를 구조로 강제하는 것이다.
task-quality-gate.shproject · acceptance
화면이든 아니든 무언가 고치고 '합격(PASS)'이라고 판정했는데, verdict=PASS 옆에 acceptance_verified[] (합격 조건을 하나씩 실제로 통과했다는 증거)가 없거나 안 채워졌으면 차단 — 시험을 위한 시험으로 눈속임하는 걸 막는다.
코드를 올리려면(push) 세 개의 문지기를 차례로 통과해야 원격 저장소(origin)에 도달한다 — 품질 검사 증거, 다른 AI(Codex)의 리뷰, 트집 검토에서 치명적 문제 0건. 이 중 하나라도 안 맞으면 차단 신호(exit 2)로 막는다. 조건이 안 맞으면 무조건 잠그는 방식이다. 통과 조건은 '다 했다'는 말이 아니라 실제 증거 파일과 지문이다.
실증 — 이 게이트가 방금 나를 막았다
이 페이지를 조사하다가, 아무것도 바꾸지 않는 단순 읽기 명령을 하나 실행했는데 그게 문지기 스크립트 qa-gate-before-push.sh에게 HARD BLOCK 당했다. 내 명령 안에 grep '…|git push|…'처럼 "git push"라는 글자가 들어 있었기 때문이다. 문지기는 명령 글자 속에서 그 조각을 발견하자마자, 안전을 위해 무조건 막았다.
이게 바로 hook 강제의 본질이다 — 문지기는 내 의도를 헤아려 주지 않는다. 정해진 조건(글자나 증거)이 안 맞으면 그냥 막는다. 그 바람에 멀쩡한 명령까지 가끔 잘못 막히는 대가가 따르지만, '무시할 수 있는 규칙'보다 '가끔 지나치게 막는 규칙'이 시스템 신뢰에는 훨씬 이득이다.
# hooks/workflow-cost-gate.sh — 작업 흐름 강제 규칙PreToolUse[Workflow]. 문서로만 적혀 있던 두 가지 부탁
(필요할 때만 켜기 / 사용량 측정)을 HARD exit code 로 못박아 닫는다.exit 0 = 통과 (방식을 밝힘)exit 2 = 차단 (방식을 안 밝힘)
우회 스위치: WORKFLOW_COST_GATE=0
On the Foundation
토대 위에 얹히는 것 — 작업 흐름(workflow)도 hook 없이는 못 선다
맨 위층인 동적 작업 흐름(dynamic workflow, 상황에 따라 여러 AI를 즉석에서 짜서 돌리는 방식)조차 이 토대 위에서만 안전하다. 이 방식은 강력하지만 값비싸다 — 하위 AI 수십 개를 한꺼번에 띄우기 때문이다. 그래서 workflow-cost-gate.sh는 모든 작업 흐름 호출이 정해진 6가지 방식 중 하나를 meta.description 또는 // pattern: 주석에 분명히 밝히도록 요구한다. 안 밝히면 → exit 2 (차단). 통과한 호출은 workflow.jsonl 파일에 기록으로 남는다. 즉 위층의 값비싼 작업 흐름이 증거를 만들고, 맨 아래 hook이 그 증거를 검사한다 — 토대와 위층이 서로 맞물려 도는 순환 구조다.
작업 흐름을 호출하면 → workflow-cost-gate가 6가지 정해진 방식(분류 후 처리 / 펼쳐서 합치기 / 반박 검증 / 뽑아서 거르기 / 토너먼트 / 될 때까지 반복) 중 하나를 밝혔는지 확인한다 → 안 밝히면 차단 신호(exit 2)로 막고, 밝히면 실행한 뒤 workflow.jsonl 파일에 기록한다. '값비싼 건 함부로 쓰지 마라'는 원칙을 문지기의 종료 신호로 못박은 것이다.
값비싼 위층(천장)인 작업 흐름이 증거를 만들고, 늘 켜져 있는 바닥인 hook이 그 증거를 검사한다. 예를 들어 team-deliver.js 작업 흐름이 항목마다 '합격 조건을 통과했다'는 기록 acceptance_verified[] 를 .qa-evidence.json 파일에 남기면, task-quality-gate.sh (강제 문지기)가 그 증거가 채워졌는지 종료 신호로 확인한다. 위층이 만들고 바닥이 잠그는 — 서로 맞물려 도는 강제의 순환이다.