Sandcorp샌드코프 · 웹·AI 제품 개발

새 프로젝트 받는 중 · 갱신 2026.11sandcorp.co.krGitHub

← 기록

AI 에이전트Claude Code훅운영자동화

글 규칙은 지켜지지 않는다 — AI 작업자의 컨텍스트 상한을 훅으로 강제하기

「250k를 넘으면 새 세션에서」를 지침에 적어 두었지만 AI 작업자는 400k까지 그대로 일했습니다. 세션 안의 훅이 대화 크기를 재서 두 단계로 막게 했고, 첫 실전에서는 배경 작업의 완료 알림까지 막아 작업자가 멈췄습니다. 강제 장치를 만들며 배운 것을 정리합니다.

글 규칙은 지켜지지 않는다

AI 작업자의 컨텍스트 상한을 훅으로 강제하기

배경 — 「250k면 새 세션」이라고 적어 두었습니다

샌드코프는 저장소마다 조율자 AI 세션을 두고, 조율자가 이슈 하나씩을 작업자 AI 세션에 맡깁니다. Codemit 같은 서비스의 이슈도 이렇게 작업자가 구현하고 시험하고 PR까지 올립니다. 작업자 하나가 티켓을 끝내면 조율자가 다음 티켓을 같은 작업자에게 이어서 줄 때가 많습니다.

작업자의 대화(컨텍스트)는 티켓을 거칠수록 길어집니다. 길어진 대화는 응답마다 다시 읽히므로 그만큼 비용이 들고 느려지며, 앞에서 읽은 것이 뒤에서 흐려지기도 합니다. 그래서 조율자 지침에 「작업자의 컨텍스트가 250k 토큰을 넘으면 새 세션을 띄워 넘겨라」라고 적어 두었습니다.

그런데 실제로는 작업자가 400k까지 그대로 일했습니다. 지침은 읽혔겠지만, 일이 한창일 때 「지금 내가 몇 k인가」를 스스로 재고 멈추는 판단은 일어나지 않았습니다.

무엇이 문제였나

글로 적은 규칙은 지키는 쪽이 기억하고 판단할 때만 지켜집니다. 이 규칙은 그 조건에 특히 약했습니다.

  • 재는 사람이 없습니다. 작업자도 조율자도 대화 크기를 매번 확인하지 않습니다. 넘었는지 알 길이 없으니 규칙이 발동할 순간이 오지 않습니다.
  • 넘는 순간이 바쁜 순간입니다. 대화가 커지는 때는 대개 티켓 한가운데입니다. 그때 「여기서 멈추고 넘긴다」는 하던 일보다 우선순위가 낮게 느껴집니다.
  • 새 지시가 계속 들어옵니다. 조율자는 작업자가 끝났다고 보고하면 다음 티켓을 줍니다. 받는 쪽이 거절하지 않으면 대화는 계속 길어집니다.

그래서 규칙을 지침에서 꺼내, 작업자가 기억하지 않아도 동작하는 장치로 옮기기로 했습니다.

고른 방법 — 세션 안의 훅이 재고, 두 단계로 막습니다

Claude Code는 정해진 사건마다 외부 명령(훅)을 부릅니다. 새 지시가 들어올 때(UserPromptSubmit), 도구를 쓰기 전(PreToolUse), 도구를 쓴 뒤(PostToolUse)에 같은 훅 하나가 돌게 했습니다.

재는 법

훅은 입력으로 대화 기록 파일의 경로를 받습니다. 이 파일을 끝에서부터 읽어 마지막 응답의 토큰 사용량(입력 + 캐시에서 읽은 것 + 캐시에 새로 쓴 것)을 더하면 지금 대화 크기가 나옵니다. 끝에서부터 1MB씩 읽어 가다 대화 정리(compact) 표시를 먼저 만나면 「정리 뒤 아직 응답 없음」으로 보고 0으로 칩니다. 서브에이전트의 응답은 본 대화 크기가 아니므로 건너뜁니다. 파일 전체를 읽지 않으니 도구를 쓸 때마다 돌아도 부담이 작습니다.

두 단계

  • SOFT(기본 250k): 넘은 뒤 처음 도구를 쓰면 한 번 알립니다. 「지금 티켓을 마치고, 보고에 새 세션 필요를 적고, 더 일을 받지 마라.」 동시에 새 지시는 거절합니다. 하던 도구 작업은 막지 않습니다.
  • HARD(기본 300k): 도구를 쓰기 전에 막습니다. 다만 넘김 문서 쓰기, git 커밋·푸시, PR·이슈 댓글, 조율자에게 보고 보내기, 띄워 둔 프로세스 끄기는 열어 둡니다.

두 단계로 나눈 이유는 「상한을 넘었다」와 「지금 당장 멈춘다」가 같지 않기 때문입니다. SOFT에서는 하던 티켓을 끝까지 마칠 수 있게 두고, 새 일만 받지 않게 했습니다. HARD는 그래도 계속 일할 때를 위한 바닥입니다. 거절 문구에는 「넘김 문서를 쓰라는 지시는 받는다」를 적어, 조율자가 넘김을 시킬 길은 남겼습니다.

버린 대안 — 「켤 때 환경변수를 넣는다」

처음에는 작업자를 띄울 때 상한 값을 환경변수로 넘기면 훅이 켜지게 했습니다. 조율자 세션이나 사람이 직접 쓰는 세션에는 걸리지 않게 하려는 뜻이었습니다.

그런데 이 방식은 지침에 적은 규칙과 같은 약점을 가졌습니다. 띄우는 쪽이 한 번만 빼먹어도 훅이 조용히 꺼집니다. 꺼졌다는 표시도 없으니, 규칙을 기억에 맡기지 않으려고 만든 장치가 다시 기억에 기대게 됩니다.

그래서 「기본으로 켜지고, 필요할 때만 끈다」로 뒤집었습니다. 훅이 받는 입력에는 이미 어떤 에이전트에서 왔는지가 들어 있습니다. 실제 입력을 받아 보니 작업자 에이전트로 띄운 본 세션은 agent_type만, 그 안에서 부른 서브에이전트의 도구 호출은 agent_type과 agent_id가 함께, 에이전트 없이 띄운 세션은 둘 다 없이 왔습니다. 「agent_type이 작업자이고 agent_id가 없다」로 작업자 본 세션만 골라 상한을 켜고, 환경변수는 값을 바꾸고 싶을 때만 쓰는 덮어쓰기로 남겼습니다.

첫 실전 — 기다리던 알림까지 막았습니다

기본으로 켜진 첫날, Codemit 이슈를 처리하던 작업자가 멈췄습니다. 작업자는 상한을 넘은 상태에서 하던 티켓을 마무리하는 중이었고, 시간이 걸리는 CI 검사를 배경으로 돌려 놓고 끝나기를 기다렸습니다. 검사는 끝났는데 작업자는 다시 움직이지 않았습니다.

원인은 하네스가 배경 작업의 끝을 알리는 방식이었습니다. 배경 명령이 끝나면 하네스는 그 사실을 <task-notification>으로 시작하는 사용자 프롬프트 한 건으로 세션에 넣습니다. 이 알림도 사람의 지시와 똑같이 UserPromptSubmit 훅을 탑니다. 훅은 SOFT를 넘은 세션에 들어온 프롬프트를 모두 「새 지시」로 보고 거절했고, 알림은 버려졌습니다. 배경 작업을 기다리던 작업자는 그 알림으로 깨어나게 되어 있었으니, 깨워 줄 것이 사라진 채 멈춘 것입니다.

SOFT 단계는 「하던 일은 마저 끝내라」는 단계였는데, 하던 일을 끝내는 데 필요한 신호를 막았습니다. 시험에서는 사람이 친 지시와 도구 호출 입력만 만들어 넣었기 때문에, 하네스가 스스로 넣는 프롬프트는 시험에 없었습니다.

훅은 하네스가 자동으로 넣는 알림은 통과시키고, 사람이 친 새 지시만 거절하도록 고쳤습니다.

결과

  • 작업자 세션은 이제 띄우는 쪽이 아무것도 넣지 않아도 상한이 켜집니다. 조율자와 사람이 쓰는 세션, 서브에이전트는 걸리지 않습니다.
  • 상한을 넘은 작업자는 새 티켓을 거절하고, 하던 티켓을 마친 뒤 「새 세션 필요」를 보고에 적습니다. 조율자는 그 보고로 새 작업자를 띄울 때를 압니다.
  • 첫 실전에서 드러난 배경 알림 문제는 하네스 알림을 통과시키는 것으로 고쳤습니다.

배운 것

  • 「규칙을 적었는데 안 지켜진다」에서 규칙을 더 굵게, 더 앞에 적는 것으로는 해결되지 않았습니다. 지키는 쪽이 판단하지 않아도 되는 자리로 규칙을 옮겨야 했습니다.
  • 강제 장치를 만들 때는 무엇을 막을지보다 무엇을 열어 둘지가 더 어려웠습니다. 막는 목록은 짧게 떠오르지만, 열어 둘 목록은 실제로 막아 보기 전까지 다 보이지 않습니다.
  • 하네스가 세션에 넣는 입력은 사람이 친 것만이 아닙니다. 배경 작업 알림처럼 사람 지시와 같은 통로로 들어오는 것이 있고, 그것을 모르면 훅은 둘을 구분하지 못합니다.

남은 한계

  • 가드레일이지 보안 경계가 아닙니다. 선의의 작업자가 상한을 넘겨 계속 일하는 것을 막는 장치이고, 작정하고 피해 가려는 쪽을 막도록 만들지는 않았습니다.
  • 크기는 마지막 응답 기준입니다. 큰 도구 결과가 막 들어온 직후에는 다음 응답이 나오기 전까지 그만큼이 잡히지 않습니다. 상한 근처에서는 한 걸음 늦게 걸릴 수 있습니다.
  • 하네스의 다른 자동 메시지는 아직 다 모릅니다. 배경 작업 알림은 실제 입력으로 확인했지만, 하네스가 같은 통로로 넣는 다른 꼴이 생기면 같은 일이 다시 생길 수 있습니다. 하네스 버전을 올릴 때 실제 입력을 다시 모아 보는 것으로 대응합니다.
  • 훅 입력의 필드는 하네스가 정합니다. 본 세션과 서브에이전트를 가르는 agent_type·agent_id의 모양이 바뀌면 판정도 함께 고쳐야 합니다.

문의 →