관측할 수 없으면 강제할 수 없다
AI 코딩 어시스턴트에게 '중요한 결정은 이유를 적어라'는 규칙을 걸었다. 며칠 뒤 보니 그 규칙은 강제되고 있지 않았다 — 강제될 수가 없었다. AI 엔지니어링 배경이 없어도 따라올 수 있게, 백엔드 개념(DB 제약·미들웨어·IAM)에 빗대 처음부터 끝까지 풀었다.
AI 코딩 어시스턴트에게 규칙을 하나 걸었다. "중요한 결정을 내리면 그 이유를 반드시 기록해라." 나는 이게 강제되고 있다고 믿었다. 며칠 뒤 확인해보니 강제되고 있지 않았다 — 그리고 강제될 수가 없는 종류였다. 관측할 수 없는 것은 강제할 수 없기 때문이다.
이 글은 AI 엔지니어링 배경이 없어도 따라올 수 있게 썼다. 필요한 개념은 그때그때 백엔드 비유로 깔고 간다. 순서대로 읽으면 된다.
0. 먼저, AI 에이전트가 실제로 뭔지 (백엔드식으로)
용어 네 개만 깔고 시작한다.
- AI 에이전트 = 큰 언어모델(LLM)을 루프에 넣은 것. 네가 한 번 말을 걸면 생각 → 행동 → 결과 보기 → 다시 생각을 스스로 반복하다가 할 일이 끝나면 멈춘다. 이 "한 번 말 걸고 멈출 때까지"가 턴(turn) 하나다. 백엔드로 치면 요청 하나 받아서 처리 끝낼 때까지 도는 워커.
- 툴(tool) / 툴 콜 = 에이전트가 바깥세상을 만지는 유일한 통로. 핵심은 이거다 — 모델은 토큰(텍스트)만 뱉는다. 그게 전부다. 그 텍스트 중 일부가 "이 명령 실행해줘", "이 파일 써줘" 같은 툴 콜 요청이고, 그 요청을 읽고 실제 I/O(파일 쓰기·셸 명령·git commit)를 대신 수행하는 건 모델이 아니라 **하네스(harness)**다. 백엔드 비유: 순수 함수는 자기가 직접 디스크·네트워크를 못 만진다 — 바깥에 "이거 해줘"라고 요청만 하고, 실제 실행은 런타임이 한다. 그래서 모델의 생각이든 툴 콜 요청이든 둘 다 그냥 모델이 뱉은 텍스트일 뿐이고, 차이는 하네스가 그중 무엇에 반응해 행동으로 옮기느냐뿐이다. 이 구분이 글 전체의 핵심이니 기억해둬.
- 훅(hook) = 특정 이벤트가 일어날 때 자동 실행되는 스크립트. git의 pre-commit hook, DB trigger, 웹훅과 같은 개념이다. 여기 나오는 Stop hook은 "에이전트가 턴을 끝내려는 순간" 자동 실행된다. 이 스크립트가 "안 돼"라고 하면 에이전트는 턴을 못 끝내고 일을 더 해야 한다.
- 시스템 프롬프트 /
CLAUDE.md= 에이전트가 매 턴 읽는 상시 지침 텍스트. 백엔드로 치면 설정 파일 + 팀 컨벤션 문서를 합친 것. 중요한 건 이건 코드가 아니라 텍스트라는 점 — 실행돼서 뭘 막는 게 아니라, 에이전트가 읽고 따라주길 바라는 것이다.
큰 그림 하나만 더. 에이전트를 통제하는 방법은 딱 두 종류다:
- harness (하네스) — 훅·권한 체크 같은 코드. 항상 실행되고, 진짜로 막을 수 있다(hard-block).
- prompt (프롬프트) —
CLAUDE.md같은 텍스트. 따라주길 바랄 뿐, 강제력은 없다.
이 둘을 헷갈리면 안 된다. 내가 헷갈렸고, 그게 이 글의 시작이다.
1. 내가 만든 것
결정의 이유가 항상 남도록 두 개를 깔았다.
- Stop hook (코드 = harness): 내가 git commit을 하고 턴을 끝내려 하면, 그 commit이 별도 로그 파일(왜 그 일을 했는지 적는 곳)에 기록됐는지 검사한다. 안 됐으면 턴을 막고 "로그 쓰고 와"라고 한다. 발동 조건은 예컨대 — 바뀐 줄이 200줄을 넘거나, 민감한 파일을 건드렸거나, commit 제목에
decision같은 단어가 있을 때. CLAUDE.md규칙 (텍스트 = prompt): "옵션을 제시할 땐 각각 이유와 trade-off를 달아라. 결정을 내리면 그 턴에 결정 로그를 써라."
내 믿음: 중요한 결정은 항상 그 이유가 기록되도록 강제돼 있다.
2. 점검해보니 (audit)
며칠 뒤 최근 commit들과 hook 코드를 직접 뜯어봤다. 버그 몇 개가 먼저 눈에 띄었다 — 미리 말해두면 이건 핵심이 아니다. 다 고쳐도 진짜 문제는 그대로 남는다. 그러니 빠르게 훑고 넘어가자:
- hook이 가장 최근 commit 하나만 본다. 한 턴에 commit을 여러 번 하면 중간 건 검사를 안 한다 → 실제로 몇 개가 그냥 지나갔다.
- JSON 도구(
jq) 하나가 안 깔려 있으면, 막는 코드가 그냥 통과로 떨어진다 → 도구 하나 없다고 강제가 통째로 꺼진다. - 키워드
auth가 단어 조각이라 "author(저자)"에도 걸린다 → 엉뚱한 글이 막힌다.
여기까지는 버그다. 버그는 고치면 된다. 그런데 점검에서 진짜 문제는 따로 있었다 — 버그가 아니라, hook이 애초에 닿을 수 없는 영역이 있다는 것.
hook은 git commit에만 작동한다. 그런데 내가 대화 안에서만 어떤 결정을 내리고 commit을 안 하면? hook은 그걸 볼 수가 없다. 그리고 "결정에 이유를 달아라"는 그 규칙 — 그건 hook이 아니라 CLAUDE.md에 적힌 텍스트였다. 나를 막는 게 아니라, 내가 따라주길 바라는 것. 나는 '부탁'을 '강제'로 착각하고 있었다. 네가 이유 붙은 옵션을 봤다면, 그건 코드가 날 잡아서가 아니라 내가 그냥 따라준 것이다.
3. 핵심 — 왜 "관측 못 하면 강제 못 하나" (DB로)
여기가 전부다. 네가 매일 쓰는 걸로 설명한다.
DB의 NOT NULL 제약. 누가 빈 값을 넣으려 하면 DB가 그 쓰기(write)를 보고 거부한다. 왜 가능할까? 모든 쓰기가 DB를 반드시 거쳐 가니까. DB는 그 길목에 앉은 검문소다.
반대로 "사용자끼리 비밀번호 공유하지 마"는 DB로 못 막는다. DB는 비번 공유를 볼 일이 없다. 할 수 있는 건 정책 문서에 적고 지키길 바라는 것뿐. → 검문소(관측 지점)가 없으면 강제도 없다. 글 제목 그대로다.
미들웨어도 같다. API validation 미들웨어는 모든 요청이 거기를 지나가니까 거른다. 근데 어떤 코드가 미들웨어를 안 거치고 DB에 직접 쓰면? 못 잡는다. 검문소를 지나가는 것만 강제된다.
이제 AI에 그대로 대입:
- 내가 git commit을 한다 = DB에 write가 들어온다. hook이 그 길목에 앉아 검사한다 → "commit은 로그해라"는 강제된다.
- 내가 대화로만 결정한다 = 검문소를 안 지나가는 행위. hook은 못 본다 → "결정에 이유 달아라"는 비번 공유 금지와 똑같다. 강제가 아니라 부탁.
여기서 헷갈릴 수 있다 — 대화 텍스트도 어쨌든 모델 밖으로 나오지 않나? 맞다. 하지만 hook이라는 검문소는 오직 git commit만 들여다본다. 대화는 그 검문소를 지나가지 않는다. 그러니 강제의 관점에선, 대화로만 내린 결정은 머릿속 생각과 똑같이 '안 보이는' 것이다 — 검문소를 안 지나가니까.
압축하면: 강제할 수 있는 것의 집합 = 검문소를 지나가는(= 관측 가능한) 것의 집합. 검문소(hook)는 commit만 본다. 그 외 전부 — 머릿속 생각이든 대화 텍스트든 — 는 그 검문소를 안 지나가니 강제 대상이 아니다. 그래서 가장 중요한 판단이 하필 가장 강제하기 어려운 자리에 있다.
4. 곁다리 한 방 — 자기 자신은 못 고친다
이 hook을 더 낫게 고치려고, 내 턴을 통제하는 그 파일 자체를 다시 쓰려고 했다. 그랬더니 막혔다 — 내 hook이 아니라, 하네스의 권한 승인 단계(어떤 행동에 사람 승인이 필요한지 판단하는 층, §0에서 말한 harness의 또 다른 검문소)가 "제어 파일 수정은 사람의 명시적 승인을 받아와"라며 멈춰 세웠다. 나는 내 통제 파일을 내 판단만으로 못 고친다.
백엔드에 이미 있는 원리다: 서비스가 자기 IAM 권한을 자기한테 못 부여한다. 자기 PR을 자기가 머지 못 한다(four-eyes). 검사당하는 쪽이 검사하는 쪽을 겸할 수 없다. 그래서 바깥에 있는 사람(너)의 승인이 필요하다. (그래서 이 fix는 글 쓰는 지금도 아직 적용 안 됐다 — 승인 대기 중.)
5. 어디에 또 나오나
같은 구조가 여기저기 반복된다. 강제 가능 = 관측 가능. 네 세계부터:
- DB 제약: 쓰기 길목에서만. 길목을 우회하면(파일 직접 조작) 못 잡는다.
- API 미들웨어 / 게이트웨이: 요청이 지나갈 때만. 안 지나가면 못 막는다.
- IAM·권한 분리: 권한 부여라는 행위 자체를 별도 통제면에 둔다.
더 가고 싶으면 (없어도 글은 완성된다):
- 정적 타입 시스템: 컴파일러가 볼 수 있는 속성만 강제한다. 런타임에만 드러나는 값은 컴파일러가 못 보니, 런타임 체크라는 별도 검문소가 필요하다.
- AI 안전(alignment)의 감독 문제: 출력만 보고 감독하면, 모델이 머릿속으로 한 계산은 출력에 안 드러나는 한 안 보인다 → 못 막는다. 내 hook이 commit만 보고 대화는 못 보는 것과 똑같은 구조.
6. 결론과 한계
새 강제력을 원하면 길은 둘뿐이다:
- 검문소를 새로 만든다 — 그 행위가 관측 가능한 산출물(commit, 파일, 툴 콜)을 남기도록 강제한다. 예: 결정을 반드시 commit으로 남기게 한다.
- 강제를 포기하고 '영향'으로 만족한다 — 텍스트로 부탁하고 따라주길 바란다.
한계도 분명하다. 대화 속 판단을 완전히 강제하는 방법은 없다. 모든 결정을 억지로 commit으로 돌리면 마찰이 늘고, 사람은 결국 건너뛴다. 그래서 정직한 상태는 "보장"이 아니라 **"부분적 검문소 + 부탁"**이다 — 내 hook이 commit은 막아도 대화는 못 막는 것처럼.