내가 짠 건 하네스가 아니라 아웃터 루프였다
- 공유 링크 만들기
- X
- 이메일
- 기타 앱

타임라인에 루프 엔지니어링(loop engineering)이라는 말이 지나가는 걸 봤을 때 저는 반사적으로 하네스 얘기를 다시 하는 거라고 생각했습니다. 프롬프트 엔지니어링, 컨텍스트 엔지니어링, 하네스 엔지니어링을 순서대로 따라온 사람이면 그렇게 생각하기 쉬울 것 같습니다. 뒤에 붙는 말이 똑같으니까요. 하나 더 붙었으니 앞 단어의 변형이겠거니 했습니다.
그래서 대충 넘기려다가 어디서 온 말인지, 앞의 세 단어랑 뭐가 다른지를 한 번 따라가 봤습니다. 몇 달에 한 번씩 뒤에 붙는 말만 바꾼 유행어가 도는 판이라, 이것도 또 누가 지어낸 말이겠지 하는 마음이 반쯤 있었습니다. 그런데 읽다 보니 하네스의 변형이 아니었습니다. 하네스 위에 한 층 더 얹히는 다른 층이었습니다. 옆으로 나란한 단어인 줄 알았는데 위아래로 쌓이는 관계였습니다.
그렇게 보고 나서 제 blog-studio repo 를 다시 열었는데 좀 이상한 기분이 들었습니다. 반년 넘게 이름도 모르고 짜둔 것들, 오케스트레이터랑 발행 게이트랑 세션 훅 같은 게 사실 다 아웃터 루프였습니다. verifier 도 stop rule 도 이미 코드로 돌고 있었습니다. 저는 그걸 자동화 스크립트라고만 불렀지 루프라고 생각해 본 적이 없었습니다. 그리고 따라가다 보니 질문이 하나 새로 생겼습니다. 제가 얹은 아웃터 루프와 Claude Code 가 원래 돌리고 있던 이너 루프 사이의 경계가 생각보다 흐렸습니다. 어디까지가 제가 만든 거고 어디부터가 하네스가 그냥 준 건지가 잘 안 나뉘었습니다.
네 단어는 쌓입니다
프롬프트, 컨텍스트, 하네스, 루프. 이 넷은 서로를 밀어내지 않고 위에 한 층씩 쌓이는 것 같습니다.
프롬프트 엔지니어링은 모델한테 한 번 잘 말하는 기술이었습니다. 같은 질문도 어떻게 묻느냐에 따라 답이 달라지니 묻는 문장을 다듬는 데 공을 들였습니다. 컨텍스트 엔지니어링은 그 한 번의 말에 뭘 얼마나 넣어줄지를 설계하는 일로 넓어졌습니다. 관련 파일을 어디까지 넣고, 어떤 배경을 앞에 깔고, 필요 없는 건 어떻게 쳐낼지. 여기까지는 결국 모델한테 뭘 어떻게 주느냐의 문제라서 둘은 같은 계열로 보입니다.
하네스 엔지니어링에서 한 층이 올라갑니다. 모델을 감싸는 구조 전체를 짜는 일이 됩니다. 하네스를 CLAUDE.md 파일 하나로 생각하면 안 될 것 같습니다. 에이전트 정의와 스킬, 훅, 그리고 CLAUDE.md 를 포함한 규칙 문서 전체가 하네스입니다. 에이전트가 무슨 역할을 맡고, 어떤 스킬을 언제 부르고, 어떤 이벤트에 어떤 훅이 걸리고, 어떤 규칙을 늘 지키는지. 이 시리즈에서 하네스가 뭔지 다룬 글에서도 파일 하나가 아니라 모델을 일하게 만드는 장치 전체라고 썼던 기억이 납니다.
하네스 얘기에서는 한동안 에이전트는 모델 더하기 하네스라는 말이 돌았습니다. 모델은 갈아 끼울 수 있는 부품이고 진짜 자산은 그 모델을 감싸는 하네스에 쌓인다는 얘기입니다. GPT 를 쓰든 Claude 를 쓰든 하네스가 잘 짜여 있으면 성능의 꽤 많은 부분이 거기서 나옵니다. 이 시리즈 1편에서 프롬프트 엔지니어링 다음이 뭘까 물었을 때부터 어렴풋하게 잡혀 있던 감각이기도 합니다.
루프는 올여름에 Addy Osmani 가 "Own the Outer Loop"라는 글에서 모양을 잡아준 말이었습니다. AI Engineer World's Fair 클로징 키노트를 글로 옮긴 건데, 에이전트는 이미 이너 루프를 돌고 있다는 얘기였습니다. 조사하고, 구현하고, 테스트하고, 보고합니다. 사람이 쥐고 있어야 하는 건 그 바깥 루프라고 했습니다. 이 일이 할 만한 일인지 판단하고, 나온 결과의 증거를 확인하고, 승인하거나 막고, 그 결정의 결과를 책임지는 층입니다.
그 글에서 오래 남은 건 이미 커밋되는 코드의 상당 부분이 AI 가 짰거나 AI 도움을 크게 받은 코드라는 얘기, 그리고 AI 가 틀렸을 때도 사람들 대부분이 그 답을 그대로 받아들였다는 연구였습니다. 만드는 건 싸졌는데 검토하고 이해하고 유지하는 건 여전히 비쌉니다. 그 차이가 벌어지면서 아웃터 루프라는 말이 나온 것 같습니다. 사람이 쥐고 있어야 하는 게 바깥 루프라는 말이 저는 좀 위안이 되면서도 무겁게 들렸습니다. 코드를 덜 짜도 되는 대신, 나온 걸 받아들일지 말지는 끝까지 제가 정해야 한다는 얘기니까요. 그 판단을 미루는 순간 저도 AI 가 틀린 답을 그대로 받아들였다는 그 사람들과 다를 게 없어집니다. 그 연구 얘기를 읽으면서 저는 그 사람들이 게을렀다기보다 피곤했던 게 아닐까 싶었습니다. 매번 의심하는 건 생각보다 힘든 일이고, 답이 그럴듯하게 생겼으면 더 그렇습니다. 저라고 다를 것 같지 않습니다.
하네스가 모델을 어떻게 일하게 만드느냐라면, 루프는 그 일하는 모델을 어떤 리듬으로 돌리고 언제 멈추고 누가 결과를 책임지느냐인 것 같습니다. 층이 다릅니다.
이렇게 쌓아놓고 보니 다음 단어가 또 나올지도 궁금해졌습니다. 만약 루프 위에 한 층이 더 얹힌다면 그건 아마 여러 루프를 누가 언제 돌릴지 정하는 일, 그러니까 사람 한 명이 아니라 팀이나 조직 단위로 루프를 묶는 쪽이 아닐까 하는 생각이 듭니다. 근거가 있는 생각은 아닙니다. 그냥 층이 올라갈 때마다 모델에서 한 걸음씩 멀어지고 사람 쪽으로 한 걸음씩 가까워졌으니, 다음 층도 그 방향이겠거니 하는 정도입니다. 프롬프트는 문장이었고, 컨텍스트는 문서 묶음이었고, 하네스는 설정과 코드였고, 루프는 일하는 리듬이었습니다. 다음은 아마 사람끼리의 약속 같은 게 아닐까 싶은데, 그게 엔지니어링이라는 말을 붙일 만한 건지는 잘 모르겠습니다.
이너 루프는 제가 짠 게 아니었습니다
루프라는 말을 처음 들었을 때는 그럼 내가 루프를 짜야 하나 싶었습니다. 보고, 생각하고, 행동하고, 결과를 보고, 다시 생각하는 그 순환 말입니다. 그런데 그건 제가 짜는 게 아니었습니다. 이미 돌고 있었습니다.
Claude Code 에 이 파일 고쳐달라고 한마디 하면, 파일을 읽고, 뭘 바꿀지 정하고, 편집하고, 결과를 확인하고, 어긋나면 다시 손대는 순환이 알아서 돕니다. 천천히 뜯어보면 편집을 시켰을 때 먼저 대상 파일을 읽어서 지금 상태를 봅니다. 어디를 어떻게 바꿔야 요청에 맞는지 생각합니다. 편집을 실행하고 결과를 봅니다. 테스트가 실패했거나 타입 에러가 났으면 그게 다시 생각하는 데 들어갑니다. 뭐가 틀렸는지 다시 보고, 다시 고치고, 다시 확인합니다. 이게 제가 끼어들지 않아도 몇 바퀴씩 돕니다. 도구를 언제 부를지, 본 결과를 다음 생각에 어떻게 넣을지는 다 하네스가 이미 만들어 둔 이너 루프입니다. 저는 그 안쪽에 손을 대본 적이 없습니다. 이 시리즈 4편에서 Anthropic 의 하네스 설계와 Ralph Loop 를 다뤘을 때도 사실 이 이너 루프의 실물을 구경한 건데, 그때는 그게 루프의 안쪽이라는 이름을 못 붙였습니다.
이걸 인정하고 나니 제가 뭘 만들었는지가 보이기 시작했습니다. 보고 생각하고 행동하고 확인하는 회로는 하네스가 줍니다. 제가 손댈 데가 별로 없습니다. 그럼 저한테 남는 건 그 회로를 언제 켜고, 무슨 일을 맡기고, 결과를 어떻게 확인하고, 어떤 조건에서 멈추느냐입니다. 그게 바깥 루프였습니다. 하네스가 이너 루프를 돌려주고, 저는 그걸 재료 삼아 아웃터 루프를 얹은 겁니다. 이 시리즈 5편에서 CLAUDE.md 와 훅, 서브에이전트로 실제로 적용해 본 얘기를 했을 때 만졌던 것들이 바로 이 재료였습니다. 그때는 하네스 적용법이라고 불렀는데 지금 다시 보면 절반은 아웃터 루프 얘기였습니다.
그러면 제가 잘해야 하는 일이 뭔지도 좀 다르게 보입니다. 안쪽 회로를 더 똑똑하게 만드는 건 제 몫이 아니고, 그건 모델이 바뀌고 하네스가 바뀌면서 알아서 좋아질 겁니다. 제가 붙들고 있어야 하는 건 그 회로를 켜고 끄는 조건, 맡기는 일의 크기, 나온 걸 받아들이는 기준 같은 것들입니다. 이게 코딩 실력이랑은 좀 다른 종류의 일 같습니다. 오히려 일을 어떻게 나누고 누구한테 뭘 맡기고 결과를 어떻게 받을지 정하는, 관리하는 사람의 일에 더 가깝게 느껴집니다. 제가 그걸 잘하는지는 솔직히 모르겠습니다. 코드는 틀리면 에러가 나는데 이런 조건은 틀려도 아무 소리 없이 그냥 돌아가니까요.
제 repo 에서 네 가지를 짚어봤습니다
아웃터 루프를 판단하고 확인하는 층이라고만 해두면 손에 잘 안 잡혀서, 제 식대로 트리거, 토폴로지, verifier, stop rule 네 가지로 나눠봤습니다. Osmani 는 Quality, Verdict, Answerability 세 가지로 나눴는데, 저는 그걸 그대로 가져오기보다 제가 실제로 짠 코드에 맞는 이름을 골랐습니다. 대봤더니 트리거는 .claude/hooks/session-start.sh, 토폴로지는 blog-orchestrator 스킬, verifier 는 scripts/deploy_to_blogger.py 의 게이트, stop rule 은 .claude/hooks/pre-commit-check.sh 에 맞아떨어졌습니다.
트리거는 세션이 시작될 때 상태를 넣어줍니다. session-start.sh 는 세션이 열리는 순간 알아서 돌면서, 지난 세션에서 어디까지 했는지 적어둔 active.md, 블로그 정체성 문서, 프로젝트 메모리 인덱스를 읽어서 컨텍스트 앞에 붙입니다. 이게 아웃터 루프인 건 이너 루프가 지금 이 대화 안에서만 돌기 때문입니다. 세션이 끝나면 안쪽 회로의 기억은 날아갑니다. 세션과 세션 사이를 이어서 지난번에 여기까지 했으니 이어서 하라고 일을 넘겨주는 건 이너 루프 바깥의 일이고, 그게 제가 훅으로 짠 부분이었습니다.
이건 실제로 겪어보고 알았습니다. 며칠 만에 프로젝트를 다시 열면 보통은 지난번에 뭘 하다 말았는지부터 떠올려야 합니다. 그런데 세션을 켜자마자 active.md 에 적어둔 여기까지 했고 다음은 이거라는 메모가 컨텍스트 맨 앞에 붙어서 올라왔습니다. 기억을 더듬을 필요 없이 바로 다음 일로 넘어갔습니다. 이너 루프한테 이번 판은 여기서 시작하라고 말없이 좌표를 찍어준 셈입니다. 트리거가 없었으면 세션마다 백지에서 시작했을 거고, 그건 이어지는 루프가 아니라 매번 처음부터 하는 단발 실행이었을 겁니다.
생각해 보면 사람도 비슷합니다. 오래 쉬었다가 돌아온 일은 어디까지 했는지 떠올리는 데만 한참 걸리고, 그 사이에 같은 실수를 한 번 더 하기도 합니다. 트리거는 그 떠올리는 일을 파일 하나에 맡겨둔 겁니다. 다만 그 파일이 붙여주는 게 정말 지금 상태인지, 아니면 제가 마지막으로 적어둔 과거의 상태인지는 트리거가 따지지 않습니다.
토폴로지는 이너 루프들을 순서대로 잇습니다. blog-orchestrator 는 글 한 편을 리서치, 작성, 편집, 게이트, 발행 순서로 보냅니다. 단계마다 그 안에서 이너 루프가 하나씩 돕니다. 리서치는 조사하고 정리하는 순환을, 작성은 초안 쓰고 고치는 순환을 돕니다. 제가 짠 건 그 루프들 사이의 배선입니다. 어느 루프에서 나온 게 어느 루프로 들어가는지, 순서가 어떻게 되는지.
순서가 결과를 바꾼다는 건 뻔한 말 같은데 실제로 겪으면 안 뻔했습니다. 게이트를 발행 뒤에 두면 이미 올라간 글을 내리는 일이 되고, 앞에 두면 처음부터 못 올라가게 막는 일이 됩니다. 같은 검사인데 어디에 꽂느냐에 따라 뒷수습이 되기도 하고 미리 막는 게 되기도 합니다. 리서치를 작성 뒤에 붙이면 이미 쓴 글에 근거를 억지로 끼워 맞추게 되고, 앞에 붙이면 근거 위에서 글이 나옵니다. 이너 루프는 똑같이 도는데 그걸 어떤 순서로 잇느냐가 전혀 다른 물건을 만듭니다. 1인 AI 팀을 슬래시 커맨드로 엮은 gstack 사례도 결국 이 순서를 어떻게 그리느냐의 문제였던 것 같습니다.
순서를 그리는 건 제가 했지만, 그 순서가 맞는지는 사실 한 번도 따로 따져본 적이 없습니다. 리서치 다음에 작성, 작성 다음에 편집이라는 건 사람이 글 쓰는 순서를 그대로 옮긴 거라 당연하다고 여겼습니다. 그런데 만약 이너 루프가 사람과 다르게 일하는 거라면, 사람한테 자연스러운 순서가 모델한테도 제일 나은 순서라는 보장은 없습니다. 작성 중에 리서치를 다시 부르게 하는 편이 나을 수도 있고, 편집을 따로 떼지 않는 편이 나을 수도 있습니다. 저는 그걸 시험해 보지 않았고, 아마 당분간도 안 할 것 같습니다. 지금 순서로 글이 나오고 있으니 굳이 바꿀 이유를 못 찾는 겁니다. 그게 게으름인지 판단인지는 저도 잘 모르겠습니다.
verifier 는 코드로 들어가 있어서 건너뛸 수가 없습니다. 저는 이게 제일 마음에 들었습니다. deploy_to_blogger.py 의 360행에서 490행 사이에 발행 게이트가 함수로 들어가 있습니다. AI 문체 검사 게이트와 품질 게이트 두 개입니다. 권고가 아니라 코드라는 게 중요했습니다.
def check_ai_gate(slug: str) -> tuple[bool, str]:
rel = f"_workspace/briefs/{slug}-ai-check.md"
f = WORKSPACE / "briefs" / f"{slug}-ai-check.md"
if not f.exists():
return False, f"{rel} 없음 (ai-pattern-check 미실행)"
...
if verdict == "불합격":
return False, f"AI 게이트 불합격 → 리라이트 필요 ({rel})"
return True, f"AI 게이트 {verdict}"
검사 파일이 아예 없어도 return False, 판정이 불합격이어도 return False 입니다. 발행 함수는 이 게이트를 못 넘은 slug 를 발행 대상에서 그냥 빼버리고, 다 걸리면 이렇게 찍고 멈춥니다.
발행 가능한 slug 가 없습니다. --skip-gate 로 우회하거나 게이트를 통과시키세요.
게이트를 우회하는 방법은 하나뿐입니다. 소스 365행 주석에 우회는 --skip-gate 로만 된다고 적혀 있습니다. 사람이 명령줄에 --skip-gate 를 직접 붙이지 않는 한, 에이전트가 아무리 이 정도면 됐다고 판단해도 게이트를 넘길 방법이 없습니다. AI 가 확인을 건너뛰자고 정하는 것 자체가 코드에서 막혀 있는 겁니다.
이게 왜 큰지는 게이트에 실제로 막혀보고 나서 알았습니다. 한번은 글을 다 쓰고 발행하려는데 AI 문체 검사 파일이 아직 안 만들어져 있었습니다. 저는 검사를 돌린 셈 치고 있었는데 파일이 없으니 게이트가 그냥 return False 를 냈습니다. 발행 대기 목록에서 그 글이 통째로 빠지고 발행 가능한 slug 가 없다는 말만 떴습니다. 순간 좀 짜증이 났습니다. 다 됐는데 왜. 곱씹어 보니 그게 맞았습니다. 검사를 안 돌렸으면 발행하면 안 되는 거고, 그 판단을 제 기분이 아니라 파일이 있느냐 없느냐라는 딱딱한 조건에 맡겨둔 거였습니다. 가급적 검사하세요 정도의 권고였으면 저는 그날 그냥 넘어갔을 겁니다.
그런데 이 게이트를 마음에 들어 했던 건 나중에 한 번 틀린 걸로 드러납니다. 벤치마크 1위와 2위 차이를 태스크 개수로 바꿔보다가 모델 말고 제 하네스 쪽을 세어본 적이 있는데, 여기 적은 이 게이트가 두 달 반 동안 아무것도 검증하지 않은 채 돌고 있었습니다. return False 조건이 딱딱한 건 맞았는데, 그 조건이 읽는 판정 파일을 검사받는 쪽이 직접 쓰고 있었습니다. verifier 를 코드로 넣어뒀다고 verifier 가 있는 게 아니었습니다.
그래도 Osmani 가 말한 증거를 확인하고 승인하거나 막는 verifier 를 저는 하필 파이썬 함수의 반환값으로 만들어 뒀던 셈입니다. 반년 전에는 그냥 발행 실수 막는 장치라고만 생각했습니다. 판단의 근거를 그때그때 사람 기분에서 떼어서 코드로 옮겨두는 게 verifier 의 핵심이라는 건 막히고 나서야 알았고, 그 코드가 뭘 보고 있는지까지 봐야 한다는 건 그보다 한참 뒤에 알았습니다.
검사받는 쪽이 판정을 직접 쓰고 있었다는 건 지금 생각해도 좀 민망합니다. 사람 조직이었으면 바로 눈에 띄었을 구조입니다. 자기가 낸 보고서를 자기가 결재하는 셈이니까요. 그런데 코드로 짜면 그게 잘 안 보였습니다. 함수가 있고, 조건이 있고, return False 가 있으니 뭔가 엄격하게 막고 있다는 느낌만 남았습니다. 코드라는 형태가 오히려 그 안을 들여다볼 마음을 없앤 것 같기도 합니다. 사람이 하는 확인이면 저 사람이 제대로 봤나 하고 의심이라도 하는데, 함수는 그냥 믿게 됩니다. 아웃터 루프를 사람이 쥐어야 한다는 말이 여기서 좀 다르게 들렸습니다. 쥐고 있다고 생각하는 것과 실제로 쥐고 있는 건 다른 일이었습니다.
stop rule 은 커밋 직전에 손을 붙잡습니다. pre-commit-check.sh 는 PreToolUse 훅이라서 git commit 이 실행되기 바로 전에 끼어듭니다. 스테이징된 파일에 OAuth 시크릿이 들어 있거나, 커밋하면 안 되는 _workspace/ 산출물이 딸려 들어가면 exit 1 로 커밋을 막습니다.
커밋 차단. 위 항목 정리 후 다시 시도하세요.
이너 루프는 커밋해달라는 요청을 받으면 그냥 커밋하려고 합니다. 그게 자기 일이니까요. 하지 말아야 할 때를 아는 건 바깥에서 조건을 걸어둔 쪽입니다. verifier 가 결과물이 기준에 맞느냐를 본다면 stop rule 은 이걸 지금 해도 되느냐를 봅니다. 보는 게 다릅니다.
이 둘을 나눠놓고 보니 stop rule 쪽이 훨씬 적다는 게 눈에 띄었습니다. 결과를 확인하는 장치는 여러 개 만들었는데, 지금 하면 안 된다고 말해주는 장치는 커밋 훅 하나뿐입니다. 아마 결과는 눈에 보이니까 확인하고 싶은 마음이 쉽게 드는데, 하지 말아야 할 때는 대개 일이 터지고 나서야 알게 되기 때문인 것 같습니다. 만약 발행 직전이나 글을 덮어쓰기 직전에도 비슷한 조건을 걸어두면 어땠을까 싶기도 합니다. 그런데 막는 조건이 많아지면 그만큼 제가 우회하고 싶어지는 순간도 많아질 거라, 몇 개가 적당한지는 감이 잘 안 옵니다.
이 훅도 한 번 실제로 저를 붙잡았습니다. 작업하다가 별생각 없이 전부 스테이징하고 커밋을 걸었는데 _workspace/ 안의 글 초안이 딸려 들어가 있었습니다. 산출물은 레포에 안 넣는 게 원칙이라 이건 커밋하면 안 되는 거였습니다. 평소 같으면 한참 뒤에야 이게 왜 히스토리에 있지 하고 발견했을 텐데, 훅이 커밋 직전에 exit 1 로 막고 뭐가 스테이징됐는지까지 찍어줬습니다. 바로 스테이징을 풀고 다시 했습니다. 이너 루프는 제가 시킨 커밋을 충실히 하려던 것뿐이고, 그 커밋을 지금 하면 안 된다는 판단은 순전히 바깥에 걸어둔 조건이 해준 겁니다.
이 stop rule 도 나중에 한 번 더 뜯어보게 됐습니다. 이 훅이 뭘 읽는지 봤더니 커밋에 실제로 들어가는 스테이지된 블롭이 아니라 작업 트리의 파일을 검사하고 있었습니다. 붙잡으려던 거랑 실제로 붙잡고 있던 게 달랐습니다. 조건을 어디에 거느냐만 신경 썼지, 그 조건이 뭘 보고 판정하는지는 이 글을 쓸 때까지 물어본 적이 없었습니다. verifier 랑 똑같은 데서 걸린 겁니다.
네 가지 말고 하나가 더 있었습니다. 루프와 루프를 잇는 이음새입니다. stop-reminder.sh 는 세션이 끝날 때 도는 Stop 훅인데, active.md 가 비어 있으면 다음 세션을 위해 몇 줄 적어두라고 알려줍니다. 이번 세션의 끝이 다음 세션의 트리거로 넘어가야 루프가 이어지니까요. 끝에서 상태를 남겨두면 시작할 때 session-start.sh 가 그걸 주워서 다음 루프에 넣습니다. 트리거와 짝을 이루는 반대쪽 고리입니다. 이 두 훅이 세션 경계를 넘어서 상태를 넘겨주는 걸 보고 나서야 아 이게 하나의 루프구나 싶었습니다.
그런데 이 이음새는 제일 약한 고리이기도 합니다. Stop 훅은 적어두라고 알려줄 뿐이지 대신 적어주지는 않습니다. 결국 active.md 에 무엇을 남길지는 그 세션을 닫는 순간의 제 기분과 피로도에 달려 있습니다. 바쁘게 끝낸 날은 한 줄도 제대로 안 남기고 닫았을 것이고, 그러면 다음 세션의 트리거는 낡은 메모를 성실하게 붙여 넣었을 겁니다. 루프 전체에서 사람 손이 직접 들어가는 데가 하필 세션과 세션을 잇는 이음새라는 게 좀 묘합니다. 어쩌면 여기가 아웃터 루프에서 사람이 쥐고 있어야 하는 손잡이 같은 건지도 모르겠습니다. 아니면 그냥 아직 자동화를 못 한 빈틈이거나요.
경계가 흐립니다
여기까지 정리하고 나니 처음엔 좀 뿌듯했습니다. 이름도 모르고 짠 게 알고 보니 딱 아웃터 루프였다니요. 그런데 그 기분은 오래 안 갔습니다. 들여다볼수록 경계가 흐려졌습니다.
게이트만 해도 그렇습니다. 저는 이걸 verifier 로 설계했습니다. 함수를 짜고, 판정 규칙을 정하고, 우회 조건을 하나로 좁혔습니다. 여기까지는 분명 제 아웃터 루프입니다. 그런데 이 게이트가 발행하는 과정 어디에서 언제 불리는지, 그 실행 타이밍의 꽤 많은 부분은 하네스가 준 실행 모델을 따릅니다. 훅이 걸리는 이벤트도 마찬가지입니다. pre-commit-check.sh 코드는 제가 짰지만 PreToolUse 라는 이벤트가 언제 발생하는지는 런타임이 정합니다. 저는 언제 붙잡을지 조건만 걸었지 붙잡는 순간 자체를 만들지는 않았습니다.
훅 파일은 제 거고 이벤트 타이밍은 하네스 겁니다. 게이트 함수는 제 거고 그걸 부르는 발행 루프의 뼈대는 절반쯤 프레임워크가 준 겁니다. 어디까지가 제가 만든 아웃터 루프이고 어디부터가 하네스가 그냥 깔아준 바닥인지가 칼로 자르듯 나뉘지 않습니다.
집을 지을 때로 치면 저는 방 배치를 정하고 문에 자물쇠를 달았는데, 전기가 언제 들어오고 물이 어디로 흐르는지는 건물이 정해둔 것 같은 느낌입니다. 이 비유가 딱 맞는지는 모르겠습니다. 다만 아웃터 루프를 사람이 쥐고 있어야 한다는 말을 처음 들었을 때 상상한 건 훨씬 독립적인 무언가였습니다. 하네스와 상관없이 제가 위에서 내려다보면서 조종하는 층 같은 거요. 실제로 짜본 건 그것보다 훨씬 하네스에 기대 있었습니다.
한 발 더 들어가면 더 애매해집니다. 오케스트레이터의 순서는 분명히 제가 그렸습니다. 그런데 단계마다 그 안에서 에이전트가 도는 이너 루프는 하네스 겁니다. 리서치 다음에 작성이라는 순서는 제 아웃터 루프인데, 그 작성 단계가 실제로 몇 번 보고 고치기를 반복할지는 이너 루프가 알아서 정합니다. 제 결정과 하네스의 결정이 한 파이프라인 안에서 번갈아 나옵니다. 이걸 제 루프라고 불러도 되나 싶어집니다.
트리거도 비슷합니다. active.md 를 다음 세션에 넘겨주기로 한 건 제 설계입니다. 그런데 그 파일이 실제로 컨텍스트 앞에 붙는 순간, 그러니까 SessionStart 라는 이벤트가 발생하는 타이밍은 제가 만든 게 아닙니다. 저는 세션이 시작되면 이걸 읽으라고 조건만 걸었고, 세션이 시작된다는 사건 자체는 런타임이 정의합니다. 제 아웃터 루프는 하네스가 열어둔 이벤트 구멍에 손을 넣어서 돌아가는 셈입니다. 구멍이 없으면 제 훅도 걸 데가 없습니다.
만약 내일 Claude Code 말고 다른 도구로 이 repo 를 옮겨야 한다면 뭐가 남을지 생각해 봤습니다. 오케스트레이터의 순서는 문서로 적혀 있으니 따라갈 것 같습니다. 게이트 함수도 파이썬이니 그대로 돌 겁니다. 그런데 세션이 열릴 때 상태를 붙여주는 훅, 커밋 직전에 끼어드는 훅은 그 도구가 같은 이벤트를 열어두지 않으면 그냥 사라집니다. 제가 제 것이라고 생각한 아웃터 루프 가운데 상당 부분이 사실은 특정 하네스한테 빌린 것이었다는 얘기가 됩니다. 그게 꼭 나쁜 건 아닌 것 같은데, 이걸 제 자산이라고 부르기는 좀 머쓱해집니다. 에이전트는 모델 더하기 하네스라는 말에서 진짜 자산은 하네스에 쌓인다고 했는데, 제 경우에는 그 하네스도 절반은 남의 땅 위에 지은 집 같습니다.
이 모호함을 억지로 정리하고 싶지는 않습니다. 경계가 흐린 게 문제라기보다는, 아웃터 루프가 원래 하네스 위에 딱 떨어지게 얹히는 별도 블록이 아니라 하네스랑 맞물려서 도는 층이라서 그런 것 같습니다. 모델과 하네스의 과적합을 다룬 6편에서 기본 설정을 의심하라고 썼던 것도 여기서 걸립니다. 제가 아웃터 루프라고 부르는 것의 많은 부분이 하네스의 기본 실행 모델에 얹혀 있어서, 그 기본값이 바뀌면 제 루프 모양도 따라서 흔들립니다. 제 것과 받은 것 사이의 선이 그래서 계속 바뀌는 것 같습니다.
루프 엔지니어링이 뭔지 정의를 하나 더 얹고 싶은 마음은 별로 없습니다. 그런 글은 이미 많을 것 같습니다. 저한테 쓸모 있었던 건 정의보다 제 repo 를 열고 뭐가 세션을 시작시키는지, 단계들을 어떤 순서로 이었는지, 결과를 뭘로 확인하는지, 어떤 조건에서 멈추게 해뒀는지를 하나씩 짚어본 거였습니다. 짚어보니 이미 짜놓고 이름만 몰랐던 게 들어 있었고, 짜놨다고 생각한 것 중에 실제로는 아무것도 안 보고 있던 것도 있었습니다. verifier 가 없으면 결과를 매번 눈으로 봐야 하고, stop rule 이 없으면 하지 말아야 할 때를 사람이 매번 기억해야 합니다. 저는 게이트에 막혀보기 전까지 그게 비어 있을 수 있다는 생각을 못 했습니다.
그리고 이름을 알고 나니까 오히려 조심스러워진 것도 있습니다. 전에는 그냥 자동화 스크립트였으니 고장 나면 고치면 되는 물건이었는데, 아웃터 루프라고 부르기 시작하니 이게 제 판단을 대신하는 장치라는 게 더 잘 보입니다. 판단을 대신하는 장치가 틀리면 제 판단이 틀린 겁니다. 게이트가 두 달 반 동안 아무것도 안 봤다는 건 그 두 달 반 동안 제가 아무것도 안 본 거랑 같은 얘기입니다. 이름이 붙으니 책임도 같이 붙는 느낌입니다. 이게 Osmani 가 말한 답할 책임 같은 건지는 모르겠지만, 적어도 저한테는 이름을 알기 전보다 루프가 좀 더 무겁게 느껴집니다.
네 가지를 다 짚고 나니 결국 같은 질문으로 돌아왔습니다. 이 중에 제가 정말 만든 건 어디까지고 어디부터가 하네스가 준 건가. 그 선이 어디쯤 그어지는지는 아직 잘 모르겠고, 볼 때마다 조금씩 다른 데 있는 것 같습니다.
댓글
댓글 쓰기