이 문제를 고치려고 sonmat을 만들었다. sonmat에도 같은 버그가 있었다.


또 한 번 고백

지난 글에서 Claude Code 디시플린 플러그인들이 다 똑같이 가지고 있는 버그를 짚었었다. 규칙은 메인 세션에 사는데, 일은 워커에서 일어나고, 그 사이를 규칙이 못 건너간다는 얘기. 이름까지 댔고 — superpowers 메인테이너가 관련 이슈를 “not planned”로 닫아버린 것까지 그대로 인용해놓고 — 그러면서 sonmat은 다르다고 썼다.

다르긴 뭐가 다르다. 그때는 그랬다.

그 시절 sonmat에는 잘 만든 훅이 하나 있었다. Claude Code를 열기만 하면 1,239자짜리 규칙 한 뭉치를 additionalContext에 박아 넣어주는 친구. “MANDATORY. Break it / Cross it / Ground it. 프로젝트 메모리 읽어라. novel trap 살펴라…” 매 세션, 매번, 모델이 첫 마디 떼기도 전에.

이게 강수라고 생각했다. 모델이 입을 떼기 전에 훅이 먼저 발동하니까, 명령은 컨텍스트에 박혀 있고, 그러니까 못 건너뛰는 거잖아. 그렇게 믿고 있었다.

근데 내가 비웃었던 바로 그 버그를, 같은 메커니즘으로 내 손으로 다시 만들고 있었다는 걸 한참 지나서야 알았다.

어떻게 알았나

이게 좀 어이없는 얘긴데, additionalContext는 메인 세션에만 간다. 서브에이전트로는 안 간다. 그러니까 규칙은 내가 잘 보이는 자리(메인)에 앉아 있고, 정작 일이 일어나는 자리(워커)엔 아무것도 없는 상태였던 거다.

그림이 그려진다. 메인 세션이 “Break/Cross/Ground 적용 중입니다” 하면서 충실하게 보고하고, 그러더니 워커를 띄운다. 워커는 작업이랑 깨끗한 컨텍스트만 받는다. 규칙은 없다. 워커가 “이건 간단해서 테스트 필요 없어요” 하고 뱉어낸다. 결과가 돌아오면 메인 세션은 — 여전히 1,239자를 든 채로 — 그걸 자신만만하게 정리해서 보여준다. 나는 끄덕이고 승인한다.

괜찮지 않았다.

그러니까 내가 이전 글 내내 superpowerskarpathy-skills 두고 비웃던 그 실패 모드를, 같은 메커니즘 그대로, 다른 이름표 달고 내가 하고 있었던 거다. 내 거.

알아챈 계기는 솔직히 좀 어쩌다였다. 같은 작업을 다른 CLI에 시켜보다가 뭔가 어긋나서 들여다봤다. CLI마다 훅 동작이 미묘하게 다르더라. 계약이 다르고, 떨어지는 시점도 다르고, 아예 없는 경우도 있었다. 그러다 보니 다른 CLI로 옮기려고 끙끙대는 그 와중에, Claude Code 안에서도 진작 보였어야 할 게 그제야 보였다. 한 군데에는 떨어지고 다른 군데에는 안 떨어지는 훅, 이건 보장이 아니다. 메인 세션에 우연히 잘 떨어진 운이었을 뿐.

문제는 멀티 CLI가 아니었다. 문제는 내가 그 운을 가드레일이라고 부르고 있었다는 거다.

v0.4.0에서 한 일

훅을 비웠다. additionalContext: 1,239 → 0.

규칙이 사라진 건 아니고 자리를 옮긴 거다. 이제 규칙은 CLAUDE.md → discipline/core.md에 산다. 에이전트가 자기 프롬프트 일부로 어차피 읽는 그 파일, 사용자도 다른 지시 적어두는 그 파일 말이다. 메인이 띄운 워커도 같은 CLAUDE.md 체인을 그대로 물려받는다. 그러니까 같은 규칙이 같은 자리에서 모든 층에 닿는 거다.

훅은 여전히 돈다. 다만 자기가 잘하는 일만 한다. .claude/sonmat/ 폴더 만들어주고, 글로벌 CLAUDE.md## sonmat 블록을 한 번 박아두고, 업데이트 있으면 받아온다. 딱 부수효과까지. 행동에는 손 안 댄다.

BEFORE                                 AFTER
hooks/session-start                    hooks/session-start
  └─ additionalContext: 1,239자          └─ 부수효과만
       "MANDATORY: sonmat..."                 ├─ .claude/sonmat/ 생성
       (메인 세션에만 떨어짐 —                ├─ ## sonmat 블록 한 번 박기
        워커는 본 적 없음)                    └─ 오래됐으면 git pull

                                       CLAUDE.md → discipline/core.md
                                         (메인이 읽고, 메인이 띄운 워커도
                                          같이 읽는다. 사용자도 본다.
                                          고칠 수도 있다.)

같은 규칙, 다른 길. 행동이 약해진 게 아니라, 자기가 어디 사는지에 대해서 정직해진 거다.

지금 믿는 네 가지

1. 워커한테 안 닿는 가드레일은 가짜 가드레일이다. “필수” 규칙이라고 박아놨는데, 그게 워커가 안 읽는 통로로 가고 있다면 그건 필수가 아니다. 장식이다. 메인 세션에서는 그게 잘 보이니까 더 무서운 거 — 보이니까 사용자도 확인을 멈춘다.

2. 보이는 게 진짜 계약이다. additionalContext에 박힌 규칙은 사용자 눈에 안 보인다. 못 읽고, 못 고치고, 동의도 못 한다. 반면에 CLAUDE.md → core.md에 박힌 규칙은 그냥 거기 있다. 에이전트가 읽고, 사용자도 읽는다. 그래서 사용자가 동의 안 할 수도 있다 — 좋은 일이다. 그래야 어긋남이 배포되기 전에 누군가 잡는다.

3. 훅은 부수효과 도구지, 행동을 빚는 도구가 아니다. 디렉토리 만들고, 마커 박고, 업데이트 풀고. 딱 거기까지가 훅이 잘하는 일이다. 훅한테 에이전트 행동까지 맡기려는 순간, “에이전트가 거치는 모든 길에서 이 훅이 발동할 거야”라는 데 도박을 거는 셈이 된다. 안 그렇다. 그럴 수도 없다.

4. “강한” 강제는 보통 약한 강제다. 1,239자 자동 주입이 강해 보였던 건 자동이라는 말 때문이었다. 근데 자동인데 불완전한 건, 수동인데 완전한 것보다 나쁘다. 사용자가 자동을 믿어버리면서 자기 눈으로 안 보게 되니까. 반대로 규칙을 사용자가 직접 고치고 무시할 수도 있는 파일로 옮기면 약해 보이지만, 사실은 거기서부터 사용자가 같은 루프 안에 들어오는 거다.

어려운 부분

훅을 비우는 건, 솔직히 통제권을 놓는 느낌이었다. 훅은 내가 “확실히 한다”고 보장할 수 있는 자리였으니까. 규칙이 CLAUDE.md에 살게 되면 사용자가 고칠 수도 있고, core.md를 통째로 덮어쓸 수도 있고, 마음 먹으면 무시할 수도 있다.

근데 그게 핵심이다.

사용자가 못 보는 규칙은 신뢰가 안 가고, 사용자가 못 고치는 규칙은 도구가 아니라 명령이다. sonmat은 도구여야 한다. 보이게 두는 게 신뢰의 대가다. 그리고 덤이 하나 있다 — 규칙이 워커한테까지 닿는다. 워커도 사용자가 보는 그 파일을 같이 읽으니까.

자기 환경 점검

Claude Code 플러그인 중에 “guardrail”을 약속하는 게 있다면, 한번 세 가지를 물어보자.

  1. 규칙이 어디에 사는가? additionalContext를 박는 훅인가? 모델이 호출해야 떠오르는 skill인가? CLAUDE.md에 적힌 한 줄인가?
  2. 누가 읽는가? 메인 세션만? 워커도? 워커가 띄운 서브에이전트까지?
  3. 내가 직접 볼 수 있는가? 에이전트를 다스린다는 그 규칙을 파일로 못 연다면, 그건 가드레일이 아니라 분위기다.

이 세 질문을 내 플러그인에 적용하고 나서야 답이 명확해졌다. 01편에서 진단하는 건 사실 쉬운 일이었다. 그걸 sonmat 자체에 적용하는 데 한참 걸렸다.

써보기

/plugin marketplace add jun0-ds/sonmat
/plugin install sonmat@sonmat

설치하면 규칙이 ~/.claude/plugins/marketplaces/sonmat/discipline/core.md에 산다. 한번 열어서 읽어보세요. 동의 안 되는 부분 있으면 고치셔도 되고요. 그래야 그 파일이 진짜로 뭔가를 하고 있다는 게 실감 난다.

GitHub: jun0-ds/sonmat


시리즈 손맛 (sonmat) 만들기 두 번째 글. 이전 글: AI가 자신있게 틀렸다. 나는 그걸 승인했다.

GitHub · LinkedIn