Harness Engineering · Foundation

Harness의 기본,
Hook Pipeline

AI 코딩 도구가 믿을 만한지는 '모델이 얼마나 똑똑한가'로 정해지지 않는다. 진짜로 그걸 굴리는 건 모델을 감싸 통제하는 장치, 곧 하네스(harness)다. 그리고 하네스의 힘은 모델의 지능이 아니라 그 주변을 채운 결정론적 인프라(입력이 같으면 결과도 늘 같은, AI 추론이 아닌 코드 장치 — 클로드 코드 소스에서 98.4%)에서 나온다. 규칙 문서(rule)·기능(skill)·작업 흐름(workflow)·기억(memory)·여러 AI를 지휘하는 조율기(orchestrator)가 전부 그 위에 얹히는 맨 아래 토대 — 그게 바로 hook pipeline(도구를 실행할 때마다 끼어드는 감시 스크립트들의 사슬)이다. 지시문이나 규칙은 문서에 적힌 '이렇게 해줘'라는 부탁(SOFT)이라, 모델이 입력만 살짝 바꾸면 피해 가고 대화가 길어져 자동 요약될 때(이른바 compaction)에 조용히 사라진다. 반대로 hook이 돌려주는 종료 신호 exit code(작업을 통과시킬지 막을지 알리는 숫자, HARD)는 모델이 손댈 수 없는 바깥 영역에서 작동해 아무도 무시하지 못하게 길을 막는다. 나는 13개의 순간(이벤트)마다 127개의 감시 스크립트를 걸어, 286개 규칙 중 핵심을 exit 2(차단 신호)로 강제한다.

근거 — github.com/jung-wan-kim/cc-sync · 내 개인 설정 폴더(~/.claude)를 그대로 공개한 저장소 — 설정 파일과 hook 스크립트, 규칙 문서가 담겨 있다

13
hook이 끼어드는 순간
127
감시 스크립트(hook)
286
규칙 문서 (강제화 원천)
exit 2
차단 신호 · 무시 불가
가장 아래 토대 — 나머지 전부가 이 위에 선다

결론부터 말하면, AI 에이전트가 믿을 만해지는 힘은 '똑똑한 추론'이 아니라 대부분 '정해진 대로만 도는 코드'에서 나온다. 클로드 코드(이 AI 코딩 도구)의 소스를 뜯어보면 에이전트 신뢰성의 98.4%가 결정론적 인프라(입력이 같으면 결과도 같은 코드 장치 — 권한을 막는 검문소, 대화 맥락 관리, 오류 복구 등)에서 나오고, AI 추론이 기여하는 몫은 1.6%뿐이다. 하네스를 건물처럼 층으로 그려 보면 맨 위가 모델의 추론이고, 그 아래가 여러 AI를 지휘하는 조율기(orchestrator)·기능(skill)·규칙(rule)·기억(memory) 같은 '운영 지식'이다. 그리고 이 모든 층이 실제로 지켜지도록 강제하는 맨 아래 토대가 바로 hook pipeline(도구를 실행할 때마다 끼어드는 감시 스크립트들)이다. 규칙(rule)이 '무엇을 하라'고 적어둔 종이라면, hook은 '안 하면 아예 못 지나간다'고 길을 막는 문지기다. 이 토대가 없으면 위층은 지켜도 그만 안 지켜도 그만인 부탁일 뿐이다.

하네스는 층으로 쌓인다 — 위층 전부가 맨 아래 hook 위에 선다 규칙(SOFT) · 우회 가능 hook(HARD) · 강제 모델 추론 (AI) 지능 · 전체의 1.6% Orchestrator · Skill · Rule · Memory 운영 지식 — '무엇을 하라' (SOFT) 위에 선다 HOOK PIPELINE 강제 차단 · 종료 신호 · 별도 프로그램 (HARD) 인프라의 98.4% · 모델 바깥에서 실행 · 우회 어려움 모든 도구 호출이 이 토대를 지난다 → 종료 신호
이 그림은 하네스가 건물처럼 층으로 쌓인다는 걸 보여준다. 맨 위가 모델의 추론, 그 아래가 조율기(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) vs 강제(HARD) · 규칙이 지켜지는 자리 SOFT · 모델이 읽는 영역 안 규칙 글 · 지시문 CLAUDE.md · rules/*.md 실행하는 순간 요약되며 소실 governance decay 입력으로 우회 모델이 무시 가능 2회+ 우회 규칙→강제 승격 HARD · 모델 바깥 (별도 프로그램) hook (PreToolUse/Stop) hooks/*.sh 조건 충족? 종료 신호로 판정 NO YES exit 2 도구 호출 차단 exit 0 통과 · 실행 진행
부탁(SOFT)은 모델이 읽는 영역 안에 있어서, 대화가 요약될 때 사라지거나 입력만 바꿔도 우회당한다. 같은 규칙이 반복해서 무시되는 게 감지되면, 그 규칙을 hook(별도 프로그램)으로 옮겨 종료 신호로 막는 문지기로 '승격'시킨다. 그러면 더는 무시할 수 없다.
토대의 구조 — AI가 일하는 모든 길목에 끼어든다

AI가 한 번 일하는 흐름(세션이라 부른다)은 아무 검문 없이 흘러가지 않는다. 세션 시작 → 사용자 입력 → 도구 실행(여러 번) → 종료로 이어지는 모든 길목마다 hook(감시 스크립트)이 끼어든다. 나는 이 13개의 길목마다 clawd-hook.js라는 공통 관측 스크립트(무슨 일이 일어났는지 기록하는 역할)를 깔고, 그 위에 길목별로 문지기와 정보 주입기를 얹었다. 역할은 네 가지다 — 차단(막기, 종료 신호 2) · 정보 주입(끼워 넣기, 맥락 보강) · 검사(확인·기록) · 관측(지켜보기).

hook 파이프라인 · 한 세션의 전 과정 (13개 길목) SessionStart 모델·권한 설정 · 관측 UserPromptSubmit 맥락 주입 · 정보 끼우기 prompt-enhancer · self-improve-check · reminders 주입되는 것 → mid-loop-question-reminder premature-completion-reminder feedback-detector · context-monitor 도구 호출 루프 — 매 도구마다 반복 PreToolUse 실행 전 검문 · 차단(종료 2) git-push-adversarial · no-env-commit · workflow-cost-gate 도구별 검문 → [Bash] no-env-commit [Write] qa-inventory-gate [Workflow] workflow-cost-gate exit 2 → 차단 도구 실행 안 됨 exit 0 → 통과 도구 실행 Bash · Edit · Write · Task · … PostToolUse 실행 후 검사·기록 formatter · sql-injection · dependency-audit · work-recheck-ledger 다음 도구 반복 루프 종료 Stop 종료 시도 — 마지막 재검증 · 차단 work-recheck-gate · premature/mid-loop detector · self-improve-trigger exit 2 → 종료 차단 미완성·거짓완료 재작업 SessionEnd clawd-hook · persist · observe PreCompact 압축 전 스냅샷 block (exit 2) inject check
흐름은 이렇다. 세션 시작(SessionStart) → 사용자 입력(UserPromptSubmit, 이때 맥락을 주입) → [도구 실행 전 검문(PreToolUse, 조건 안 맞으면 종료 신호 2로 차단) → 도구 실행 → 실행 후 검사(PostToolUse)]를 도구마다 반복 → 종료 시도(Stop, 마지막 재검증) → 세션 끝(SessionEnd). 대화를 자동 요약하기 직전(PreCompact) 같은 갈래 길목이 그 경계를 지킨다. 검문에서 종료 신호 2가 나오면 그 도구 호출이나 종료 자체가 아예 일어나지 않는다.
SessionStartobserve
ensure-default-modelauto-apply-permissions
UserPromptSubmitinject
prompt-enhancerself-improve-checkmid-loop-reminderpremature-reminder
PreToolUseblock
git-push-adversarialno-env-commitno-localstorageworkflow-cost-gate
PostToolUsecheck
formatter-checksql-injection-checkdependency-auditwork-recheck-ledger
Stopblock
work-recheck-gatepremature-detectormid-loop-detectorself-improve-trigger
SessionEndobserve
clawd-hookfact-extract
SubagentStart / Stopobserve
subagent-snapshotsubagent-qa-directive
PreCompact / PostCompactobserve
맥락 스냅샷상태 재주입
PostToolUseFailure · Notificationobserve
clawd-hook 분배기Elicitation · WorktreeCreate
토대가 실제로 막는 것들 — 차단 신호(exit 2)는 못 무시한다

exit 2(차단 신호)를 돌려주는 hook은 그 도구 호출 자체를 물리적으로 막아 버린다. 아래 문지기들은 '다 끝냈다'는 모델의 자기 보고를 믿지 않는다. 대신 실제 증거(증거 파일·지문 해시·합격 조건 통과 기록)를 요구한다. 증거가 없으면 코드 올리기(push)도, 저장(commit)도, 작업 흐름(workflow) 실행도, 세션 종료도 통과되지 않는다.

qa-gate-before-push.shPreToolUse[Bash]

코드를 고쳤는데 'QA(품질 검사)를 통과했다'는 증거 파일 .qa-cycle-passed(합격 표시·지문·1시간 이내 기록)이 없으면 코드 올리기(push) 차단. 지문이 지금 코드와 안 맞아도 막는다.

git-push-adversarial-review-gate.shPreToolUse[Bash]

다른 AI(Codex)가 트집 잡듯 교차 검토한 결과에서 치명적 문제(CRITICAL)가 남아 있으면 코드 올리기 차단. 한 모델이 스스로 검사할 때 놓치는 맹점을 다른 모델로 메운다.

workflow-cost-gate.shPreToolUse[Workflow]

여러 AI를 한꺼번에 돌리는 작업 흐름(workflow)을 부를 때, 정해진 6가지 방식 중 하나를 밝히지 않으면 차단. '검토자 5명 소집' 같은 값비싼 낭비를 구조로 막는다.

work-recheck-gate.shStop · exit 2 ×4

의미 있는 작업을 해놓고 '합격 조건을 실제로 통과했다'는 증거로 다시 확인하지 않으면 세션 종료 차단. 실제론 안 됐는데 '다 됐다'고 하는 거짓 완료를 끝나기 직전에 붙잡는다.

no-env-commit.shPreToolUse[Bash]

.env 파일이나 API 키, 비밀번호 같은 민감 정보를 저장(commit)하려 하면 차단. 암호처럼 인코딩해 숨긴 비밀 값까지 뒤져서 잡는다.

no-localstorage-universal.shPreToolUse[Edit|Write]

웹 파일에서 브라우저 저장소 localStorage를 쓰는 것을 차단 (로그인 토큰 저장만 예외). 사용자 데이터는 브라우저가 아니라 서버에 둬야 하기 때문이다.

scaffold-violation-check.shPreToolUse · exit 2 ×2

프로젝트마다 정해둔 뼈대 규칙에서 '절대 하지 마라(NEVER DO)' 항목을 어기면 즉시 차단한다. 그 프로젝트의 관례를 구조로 강제하는 것이다.

task-quality-gate.shproject · acceptance

화면이든 아니든 무언가 고치고 '합격(PASS)'이라고 판정했는데, verdict=PASS 옆에 acceptance_verified[] (합격 조건을 하나씩 실제로 통과했다는 증거)가 없거나 안 채워졌으면 차단 — 시험을 위한 시험으로 눈속임하는 걸 막는다.

코드 올리기(push) 검문 사슬 · 하나라도 미달이면 차단 코드 올리기 시도 저장(commit) 뒤 qa-gate-before-push .qa-cycle-passed? codex-review-gate .codex-review-passed? git-push-adversarial CRITICAL = 0? origin 올리기 허용 exit 2 · push 차단 증거(파일·지문·치명적 문제 0건) 중 하나라도 미달이면 차단
코드를 올리려면(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
토대 위에 얹히는 것 — 작업 흐름(workflow)도 hook 없이는 못 선다

맨 위층인 동적 작업 흐름(dynamic workflow, 상황에 따라 여러 AI를 즉석에서 짜서 돌리는 방식)조차 이 토대 위에서만 안전하다. 이 방식은 강력하지만 값비싸다 — 하위 AI 수십 개를 한꺼번에 띄우기 때문이다. 그래서 workflow-cost-gate.sh모든 작업 흐름 호출이 정해진 6가지 방식 중 하나를 meta.description 또는 // pattern: 주석에 분명히 밝히도록 요구한다. 안 밝히면 → exit 2 (차단). 통과한 호출은 workflow.jsonl 파일에 기록으로 남는다. 즉 위층의 값비싼 작업 흐름이 증거를 만들고, 맨 아래 hook이 그 증거를 검사한다 — 토대와 위층이 서로 맞물려 도는 순환 구조다.

작업 흐름(workflow) 강제 · workflow-cost-gate 작업 흐름 호출 dynamic workflow 6패턴 중 하나 선언됐나? meta.description / // pattern: NO exit 2 · 차단 "방식 못 밝히면 그냥 AI 병렬로 써라" YES exit 0 · 작업 흐름 실행 fan-out · verify · synthesize workflow.jsonl 모든 호출을 수치로 기록 우회: WORKFLOW_COST_GATE=0 기본값: 그냥 AI 병렬 실행 (작업 흐름은 필요할 때만)
작업 흐름을 호출하면 → workflow-cost-gate가 6가지 정해진 방식(분류 후 처리 / 펼쳐서 합치기 / 반박 검증 / 뽑아서 거르기 / 토너먼트 / 될 때까지 반복) 중 하나를 밝혔는지 확인한다 → 안 밝히면 차단 신호(exit 2)로 막고, 밝히면 실행한 뒤 workflow.jsonl 파일에 기록한다. '값비싼 건 함부로 쓰지 마라'는 원칙을 문지기의 종료 신호로 못박은 것이다.
classify-and-act fan-out-and-synthesize adversarial verification generate-and-filter tournament loop-until-done
바닥과 천장 — 늘 켜진 hook과 필요할 때만 도는 작업 흐름이 맞물린다

값비싼 위층(천장)인 작업 흐름이 증거를 만들고, 늘 켜져 있는 바닥인 hook이 그 증거를 검사한다. 예를 들어 team-deliver.js 작업 흐름이 항목마다 '합격 조건을 통과했다'는 기록 acceptance_verified[].qa-evidence.json 파일에 남기면, task-quality-gate.sh (강제 문지기)가 그 증거가 채워졌는지 종료 신호로 확인한다. 위층이 만들고 바닥이 잠그는 — 서로 맞물려 도는 강제의 순환이다.

이 토대와 이어진 글
raground@gmail.com 복사됨