Claude Code Agent Loop Deep Dive (1): 도구 선언부터 실행 전 승인까지
요약
본 기사는 LLM 기반 에이전트 루프의 작동 원리를 심층 분석합니다. 특히, 모델이 어떤 도구(Tool)를 호출할 수 있는지 알게 되는 과정과 그 실행 전 승인 메커니즘에 초점을 맞춥니다. LLM은 `tools` 섹션에 선언된 도구 메뉴를 기반으로만 요청하며, 파괴적이거나 민감한 작업은 사용자 승인을 거쳐야 합니다.
핵심 포인트
- LLM은 API의 `tools` 섹션을 통해 사용 가능한 도구를 인지합니다.
- 도구는 이름(name), 설명(description), 입력 스키마(input_schema)로 정의됩니다.
- 모델이 도구를 호출하는 것은 요청일 뿐, 실제 실행 전에는 권한 승인 단계가 필요합니다.
- 파괴적/민감한 작업은 사용자에게 명시적인 승인을 받아야 합니다.
이전 개요 기사에서 저는 에이전트 루프의 5줄 골격(skeleton)을 소개했습니다. 즉, LLM 호출 → tool_use 확인 → 요청된 도구 실행 → 도구 호출이 없을 때 중지하는 과정입니다.
본 기사에서는 모든 반복 주기 내에서 발생하는 첫 번째 질문, 즉 **LLM은 어떤 도구를 호출할 수 있는지 어떻게 알게 되며, 한 번 호출하라고 요청한 후에는 무엇이 또 일어나는가?**를 다룹니다.
구체적으로는 다음과 같습니다:
- LLM은 현재 세션에서
Read라는 도구가 존재한다는 것을 왜 아는가? - LLM이
Read를 요청하면 즉시 실행되는가? - 그렇지 않다면, 그 사이에 어떤 일이 발생하는가?
- 호출이 허용될지 여부를 누가 결정하는가?
도구(Tools): API 요청의 세 번째 섹션
루프는 모든 LLM 호출에서 messages 배열을 전송합니다. 하지만 완전한 Messages API 요청은 세 가지 중요한 섹션으로 구성됩니다:
POST /messages
{
system: "...", ← system prompt
...
이 세 가지는 모두 LLM에게 함께 전송됩니다. messages는 매 반복마다 내용이 늘어나지만, tools와 system은 세션 동안 비교적 안정적입니다.
모델은 tools에 나열된 도구만 호출합니다. 학습 과정에서 모델은 tool_use.name이 선언된 메뉴 중에서 선택되어야 한다는 것을 배웁니다. 만약 Read가 목록에 없다면, 모델은 그것이 존재한다는 이유를 알 수 없습니다.
도구 선언(Tool Declaration)의 내용물
tools는 배열이며, 각 항목은 세 가지 필드를 가진 하나의 도구를 정의합니다:
name:tool_use.name에 나타나는 문자열입니다.description: 해당 도구가 무엇을 하는지, 언제 사용해야 하는지, 언제 사용하지 말아야 하는지, 그리고 그 경계(boundaries)를 설명합니다.input_schema: 허용되는 매개변수에 대한 JSON Schema이며,tool_use.input은 이에 맞춰야 합니다.
모델이 도구를 호출할지 여부를 선택하는 것은 description에 크게 의존합니다. 명확한 설명은 적절한 상황을 선택하도록 돕고, 모호한 설명은 오용이나 누락을 초래할 수 있습니다.
요약하자면, tools 섹션은 LLM의 도구 메뉴입니다. 한번 구성되면 모든 API 호출에 포함됩니다.
Anthropic의 API 레벨 포맷은 공식 Tool use documentation을 참조하세요.
도구 요청이 아직 도구 실행은 아니다
LLM이 메뉴를 보고 “auth.py를 검사해 주세요”라는 내용을 받으면, 다음과 같은 tool-use 블록으로 응답할 수 있습니다:
{
role: "assistant",
content: [
...
최소한의 루프는 도구를 실행하고, 그 결과를 추가하며, 계속 진행하라고 지시합니다. 실제 제품에서는 그 사이에 한 단계가 더 있을 수 있습니다:
권한 승인 (Permission approval)
일반 파일을 읽는 것은 종종 자동으로 통과될 수 있습니다. 그러나 다음과 같은 LLM 요청은 결정 없이 실행되어서는 안 됩니다:
Bash rm -rf /some/dir: 파괴적인 삭제;Edit /etc/passwd: 민감한 시스템 파일 수정;- 사용자가 아직 승인하지 않은 새로운 종류의 Bash 명령어.
이러한 상황에서 Claude Code는 도구를 직접 실행하지 않습니다. 대신, 사용자에게 승인 프롬프트를 제시하고 사용자가 해당 작업을 허용하거나 거부할 때까지 기다립니다.
이는 사용자가 루프 중간에 부재하는 규칙에 대한 유일하고 의도적인 예외입니다. 이것이 없으면, 사용자가 반응하기 전에 루프가 파괴적인 요청에 대해 자율적으로 행동할 수 있습니다.
세 가지 메커니즘은 자율 실행에 필요한 세 가지 다른 예외를 다룹니다:
| 메커니즘 | 루프에 미치는 영향 |
|---|---|
| 권한 승인 (Permission approval) | 사용자가 결정할 때까지 루프를 차단함 |
| ... |
승인 규칙의 6가지 출처
모든 Read 작업마다 프롬프트를 하는 것은 견디기 어려우므로, Claude Code는 정책을 기억해야 합니다. 무엇이 자동으로 진행될 수 있는지, 무엇은 항상 물어봐야 하는지, 그리고 무엇은 절대 허용되지 않는지를 말입니다.
| 출처 | 우선순위 | 의미 | 예시 |
|---|---|---|---|
abortController 이미 중단됨 (already aborted) | 가장 높음 | 즉시 거부 | Ctrl-C 후, 남은 승인은 건너뜀 |
| ... |
- CLI arguments: 해당 인수로 시작된 세션에 유효함;
- session state: 현재 대화에서 이루어진 “항상 허용(always allow)” 결정;
.claude/settings.local.json: 이 프로젝트를 위한 사용자별 설정으로, Git에 커밋되지 않음;.claude/settings.json: Git을 통해 공유되는 프로젝트 설정;~/.claude/settings.json: 사용자 전체 범위의 설정;- managed settings: 개별 사용자가 재정의할 수 없는 조직 전체 정책.
모든 도구 실행 전에 런타임은 규칙들을 순서대로 평가합니다. 한 계층이 결과를 결정하면, 더 낮은 우선순위의 규칙은 필요하지 않습니다.
승인이 루프를 차단하는 방법
자동 규칙으로 결정할 수 없을 때, 제어 흐름은 개념적으로 다음과 같습니다:
LLM이 tool_use를 방출함
↓
런타임이 도구를 준비하고 권한을 확인함
...
루프는 사용자가 클릭했는지 주기적으로 폴링(poll)하지 않습니다. 대신 Promise가 완료되기를 await합니다. 만약 사용자가 10초 동안 생각한다면, 루프는 10초 동안 아무것도 하지 않습니다.
이는 또한 승인 과정 중에 LLM API 호출이 진행되지 않음을 의미합니다. 사용자의 숙고 시간은 API 비용을 추가하지 않습니다.
세 가지 승인 출처가 동시에 경쟁함
상호작용적인 사용자 입력만이 승인을 해결하는 유일한 방법은 아닙니다. 결과는 다음 세 가지 출처에서 도착할 수 있습니다:
- **사용자 인터페이스(user interface)**를 통한 허용(Allow) 또는 거부(Deny).
settings.json에 사용자나 팀이 정의하고 명령이나 HTTP 엔드포인트를 통해 구현하는PermissionRequest훅.- 운영이 명백히 안전한지 판단하는 AI 분류기(classifier).
세 가지 모두 동시에 시작됩니다. 가장 먼저 도착한 결과가 승리하며, 나중에 도착하는 결과는 더 이상 중요하지 않습니다. 이것은 의도적인 경쟁입니다:
- 사용자 입력은 권위적이지만 보통 몇 초가 걸립니다;
- 훅은 프로그래밍 가능하며 밀리초 또는 몇 초가 걸릴 수 있습니다;
- 분류기는 빠르게 응답할 수 있지만 보수적일 수 있습니다.
이들을 순차적으로 실행하면 사용자는 지연 시간의 합계만큼 기다려야 합니다. 이들을 경쟁시키면 승인 흐름은 첫 번째 유효한 답변만 기다리면 됩니다.
동시성(Concurrency)은 동일한 Promise를 두 번 해결하는 것에 대한 방어책을 요구합니다. Claude Code는 ResolveOnce 메커니즘을 사용하는데, 첫 번째 소스에서 해결 권한을 주장하고 이후의 시도는 무해하게 실패합니다. 종종 버그인 레이스 컨디션(race condition)이 반응성을 위한 제품 기능이 됩니다.
서브에이전트는 세션 승인을 상속받지 않습니다
사용자가 메인 대화에서 “Bash 항상 허용”을 선택했다고 가정해 봅시다. 만약 메인 에이전트가 서브에이전트를 시작한다면, 서브에이전트도 이 세션 수준의 신뢰를 물려받을까요?
아닙니다. Claude Code는 서브에이전트가 시작될 때 부모 대화의 세션 승인을 지웁니다. 이는 런칭 수준의 CLI 설정은 유지하지만, 서브에이전트는 자체적인 allowedTools 정책을 가집니다.
이는 의도적으로 보수적입니다. alwaysAllow 결정은 메인 대화에 대한 신뢰를 표현하는 것이지만, 서브에이전트는 자동으로 동등하게 신뢰되는 연속이 아니라 또 다른 자율적인 모델 컨텍스트입니다. 따라서 다시 물어봐야 할 수도 있지만, 낮은 신뢰도로 기본 설정하는 것이 더 안전한 보안 태세(security posture)입니다.
요약
- API 요청의
tools섹션은 세션에서 호출할 수 있는 것을 선언합니다. 모델은 설명을 사용하여 도구가 적합한지 결정합니다. tool_use응답 역시 단지 요청일 뿐입니다. 실행 전에 권한 승인이 개입될 수 있습니다.- 승인은 그렇지 않으면 자체적으로 실행되는 루프에 대한 의도적인 예외입니다.
- 규칙은 중단(aborts) 및 명시적 거부부터 기본 모드까지 여섯 가지 소스에서 해결됩니다.
- 루프는 Promise를 통해 대기하므로, 사용자의 숙고가 API 호출을 소모하지 않습니다.
- 사용자 입력, 훅(hooks), 그리고 자동 분류기가 가장 빠른 해결책을 두고 경쟁합니다.
ResolveOnce는 이중 해결을 방지합니다. - 서브에이전트는 세션 수준의 승인을 상속받지 않아 보수적인 신뢰 경계(trust boundary)를 유지합니다.
다음 기사에서는 훅(Hooks): 루프 주변의 더 일반적인 프로그래밍 가능한 삽입 지점을 검토할 것입니다. 권한 승인은 특화된 개입이며, 훅은 “사용자가 자율 에이전트 루프에 사용자 지정 로직을 어떻게 삽입할 수 있을까요?”라는 질문에 대한 일반적인 답변입니다.
참고 자료
주요 구현 위치 (Claude Code v2.1.220):
src/utils/permissions/permissions.ts—hasPermissionsToUseTool흐름src/hooks/toolPermission/handlers/interactiveHandler.ts— 대화형 승인 Promisesrc/hooks/toolPermission/PermissionContext.ts—ResolveOnce클레임src/utils/permissions/PermissionUpdate.ts—alwaysAllow영속성(persistence)src/utils/settings/types.ts—settings.json의permissions스키마src/types/permissions.ts— 권한 규칙 소스src/tools/AgentTool/runAgent.ts— 서브 에이전트(subagent) 권한 초기화
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기