지식위키

Claude Code · 진입점과 에이전트 실행 루프

Claude Code · 진입점과 에이전트 실행 루프

한 줄 요약

Claude Code는 “터미널 안에서 도는 에이전트”로, 한 번 시키면 읽고 → 고치고 → 검증하고 → 다시 결정하기를 스스로 반복한다. → 왜 배우나: 이 반복(에이전트 루프)이 모든 코딩 에이전트의 심장이라, 직접 하네스를 만들 때 가장 먼저 베껴야 할 뼈대이기 때문이다.

그림

한 턴(turn) 안에서 모델과 도구가 어떻게 맞물려 돌고, 언제 멈추는지를 그린 그림이다.

flowchart TD
  E["진입점: claude 또는 claude -p"] --> SS["세션 시작<br/>(컨텍스트 조립: CLAUDE.md·메모리·도구정의)"]
  SS --> UP["사용자 프롬프트 1개 받음"]
  UP --> M["모델 추론<br/>(다음 행동 결정)"]
  M -->|"도구를 부름"| PRE["도구 실행 직전 점검"]
  PRE --> RUN["도구 실행<br/>(읽기/편집/명령/검색/웹...)"]
  RUN --> POST["결과를 컨텍스트에 다시 넣음"]
  POST --> M
  M -->|"텍스트로 마무리"| STOP{"멈춤(Stop) 신호"}
  STOP -->|"훅이 계속하라고 막음(block)"| UP
  STOP -->|"종료조건 충족<br/>(자연종료/턴상한/예산초과/Esc)"| END["턴 종료, -p면 프로세스 종료"]

쉽게 풀기

비유: 신입 직원에게 일을 시키는 과정. “로그인 버그 고쳐”라고 한마디 던지면, 똑똑한 신입은 곧바로 입으로만 답하지 않는다. 관련 서류를 꺼내 읽고, 코드를 손보고, 테스트를 돌려보고, 결과를 보고 다음에 뭘 할지 다시 정한다. 이 “한 사이클”을 끝낼 때까지 알아서 도는 것 — 이게 **에이전트 루프(agentic loop)**다.

여기에는 두 개의 부품만 있다.

  1. 모델(추론) — “다음에 뭘 할까?”를 생각하는 머리.
  2. 도구(행동) — 파일 읽기, 코드 고치기, 명령 실행 같은 손발.

그리고 Claude Code 자체는 이 머리와 손발 사이를 잇는 하네스(harness), 즉 “도구를 쥐어주고, 컨텍스트를 챙겨주고, 언제 멈출지 관리하는 껍데기”다. 모델은 혼자서는 텍스트밖에 못 만든다. 하네스가 도구 실행 결과를 다시 모델 앞에 갖다놓아 주기 때문에, 모델은 그 결과를 보고 방향을 수정하며 수십 개 작업을 연결할 수 있다.

들어가는 문(진입점)은 두 가지다.

  • 대화형 (claude): 사람이 옆에서 계속 말을 거는 모드. 끝낼 때까지 계속 산다.
  • 헤드리스 (claude -p): 스크립트가 한 번 던지고 결과만 받아가는 모드. 한 턴 돌고 바로 꺼진다.

한 턴 안에는 더 작은 루프가 또 있다. “프롬프트 1개 → 응답 1번 완료”가 한 턴인데, 그 안에서 도구는 여러 번 돌 수 있다. 이 작은 순환의 매 단계마다 신호(이벤트)가 울려서, 우리가 끼어들어 행동을 바꿀 수 있다.

울리는 주기신호 이름무슨 순간인가
세션당 1번SessionStart / SessionEnd프로그램 켜질 때 / 꺼질 때
턴당 1번UserPromptSubmit → … → Stop프롬프트 하나에 대한 한 번의 응답 사이클
도구 호출마다PreToolUse → 실행 → PostToolUse한 턴 안에서 도구가 N번 도는 지점

[!note] “Stop”은 끝이 아니라 분기점 Stop은 “모델이 응답을 마쳤다”는 신호일 뿐, 강제 종료가 아니다. 여기서 훅이 “아직 안 끝났어, 계속해”라고 막으면(decision: "block") 루프가 한 바퀴 더 돈다. 이게 자동 반복 작업(예: Ralph 루프)의 핵심 트릭이다.

핵심 정리

진입점 모드 (어느 문으로 들어오나)

명령모드종료 시점
claude대화형사용자가 끝낼 때까지
claude -p "query"헤드리스모델이 턴 끝내면 즉시 종료
claude -c / -r "<id>"양쪽이어받은 모드를 따라감

루프를 멈추거나 늘리는 핵심 플래그

플래그역할
--max-turns N에이전트 턴 수 상한 (print 모드, 기본 무제한)
--max-budget-usd XAPI 비용 상한 도달 시 정지 (print 모드)
--permission-mode <m>도구 호출을 멈출지/물어볼지 결정
--allowedTools / --tools실행 허용 도구 / 쓸 수 있는 도구 자체를 제한

[!note] 종료조건 = “Stop 신호의 원천” 4가지

  • 모델이 도구를 더 안 부르고 응답을 마침 → 자연 종료 (Stop 발생)
  • --max-turns 또는 --max-budget-usd 초과 → 강제 종료 (print 모드)
  • 사람이 Esc를 누름 → 도구 취소 + 즉시 정지 (이때는 Stop 훅이 돈다. API 오류면 StopFailure)
  • Stop 훅이 decision:"block" 반환 → 종료를 거부하고 한 바퀴 더 (단 8연속 block이면 강제 종료)

Stop 훅이 받는 입력 / 돌려주는 출력

  • 입력 stop_hook_active (bool): 이미 block으로 도는 중이면 true무한루프 방지 가드용
  • 입력 last_assistant_message: 모델 최종 응답 텍스트 (완료 여부 판단에 사용)
  • 출력 decision: "block": 멈춤을 차단 → 루프 계속 (생략하면 정상 종료)
  • 출력 reason: block일 때 필수. 모델에게 “왜/무엇을 계속하라”고 다음 입력으로 주입됨

[!warning] 안전장치(원문) stop_hook_active를 검사해 무한실행을 막아야 하고, Claude Code는 8번 연속 block되면 hook을 무시하고 턴을 종료한다.

실제 예시

자동 반복 플러그인(Ralph Loop)의 실제 Stop 훅이다. 모델이 턴을 끝내면(Stop), 완료조건 미달일 때 같은 프롬프트를 다시 주입해 루프를 재개한다.

# /home/seunghyeong/.claude/plugins/marketplaces/claude-plugins-official/plugins/ralph-loop/hooks/stop-hook.sh
# (발췌) 모델이 turn을 끝내(Stop) → 완료조건 미달이면 같은 프롬프트를 다시 주입해 루프 재개

# Check if max iterations reached  ← 종료조건 1: 최대 반복
if [[ $MAX_ITERATIONS -gt 0 ]] && [[ $ITERATION -ge $MAX_ITERATIONS ]]; then
  echo "Ralph loop: Max iterations ($MAX_ITERATIONS) reached."
  rm "$RALPH_STATE_FILE"
  exit 0                       # decision 없이 exit 0 → Claude 정상 종료(루프 끝)
fi

# Not complete - continue loop with SAME PROMPT
NEXT_ITERATION=$((ITERATION + 1))
...
# Output JSON to block the stop and feed prompt back
# The "reason" field contains the prompt that will be sent back to Claude
jq -n \
  --arg prompt "$PROMPT_TEXT" \
  --arg msg "$SYSTEM_MSG" \
  '{
    "decision": "block",       # ← Stop을 차단 = 루프 계속
    "reason": $prompt,         # ← 다음 턴 입력으로 재주입되는 프롬프트
    "systemMessage": $msg
  }'

이 훅을 등록하는 설정(실제):

// /home/seunghyeong/.claude/plugins/marketplaces/claude-plugins-official/plugins/ralph-loop/hooks/hooks.json
{
  "description": "Ralph Loop plugin stop hook for self-referential loops",
  "hooks": {
    "Stop": [
      { "hooks": [ { "type": "command", "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/stop-hook.sh\"" } ] }
    ]
  }
}

직접 하네스를 만든다면, 이 루프의 뼈대는 다음 의사코드로 압축된다. Claude Code의 turn/tool cadence를 그대로 옮긴 것이다.

# minimal_agent_loop.py  — Claude Code 에이전트 루프의 뼈대 재현
def run_turn(messages, tools, *, max_turns=None, max_budget=None):
    spent, steps = 0.0, 0
    while True:                                   # ── 한 턴 = 모델↔도구 내부 루프 ──
        resp = model.infer(messages, tools)       # 1) 모델 추론
        spent += resp.cost
        # ── 종료조건(Stop 시그널의 원천) ──
        if resp.stop_reason == "end_turn":        #   모델이 도구 없이 마무리
            return on_stop(resp)                  #   ← Stop hook 위치: block이면 계속
        if max_turns and steps >= max_turns:      #   --max-turns
            raise StopError("max turns")
        if max_budget and spent >= max_budget:    #   --max-budget-usd
            raise StopError("budget")
        # ── 도구 순환 ──
        for call in resp.tool_calls:              # 2) tool_use 마다
            if not permitted(call):               #   권한모드/allowedTools 체크
                result = abort_or_ask(call)
            else:
                result = run_tool(call)           # PreToolUse → 실행 → PostToolUse
            messages.append(tool_result(call, result))  # 3) 결과를 컨텍스트에 주입
        steps += 1                                # 4) 반복

def on_stop(resp):
    decision = stop_hook(resp)                    # 외부 훅 호출
    if decision and decision.get("decision") == "block":
        messages.append(user_msg(decision["reason"]))  # 같은 루프에 재주입
        return CONTINUE                           # ※ 8연속 block이면 강제 종료 가드 필요
    return resp.text                              # 정상 종료

재구현 체크리스트:

  • 진입점 2종 분기: 대화형(stdin 반복) vs 헤드리스(-p, 1회 후 exit)
  • messages에 시스템 프롬프트 + 컨텍스트(메모리/규칙/도구정의)를 먼저 조립
  • 모델 응답의 stop_reason으로 “도구 더 부름 vs 마무리” 분기
  • 도구 결과를 반드시 messages에 다시 넣고 모델을 재호출 (이게 루프의 본질)
  • 종료조건 4종 구현: end_turn / max-turns / budget / 사용자 인터럽트
  • Stop 확장점: 종료 직전 훅이 decision:"block"+reason으로 루프 연장 + 무한루프 가드(연속 block 카운트 상한)
  • 권한 게이트: 도구 실행 전 --permission-mode/--allowedTools 평가, 미허용 시 abort 또는 질의

요약 & 셀프체크

3줄 요약:

  • 에이전트 루프 = **모델(추론) ↔ 도구(행동)**가 한 턴 안에서 반복하며, 도구 결과를 컨텍스트에 다시 넣어 다음 결정을 만드는 구조.
  • 진입점은 **대화형(claude)**과 헤드리스(claude -p) 두 가지이고, 종료는 자연종료·턴상한·예산초과·Esc 네 갈래로 일어난다.
  • Stop 훅이 decision:"block"을 내면 루프가 연장되며, 8연속 block이면 강제 종료된다.

스스로 답해보기:

  1. 모델이 도구를 부른 뒤 그 결과를 다시 모델 앞에 넣지 않으면, 루프는 어떻게 망가지나?
  2. 자동 반복 작업을 만들 때 stop_hook_active를 검사해야 하는 이유는?
  3. --max-turnsEsc 인터럽트는 둘 다 루프를 멈추는데, Stop 훅이 도는지 여부에서 무엇이 다른가?

연결

CC_개요 · _분석축_루브릭

[!tip] Codex 교차검증 (보존) 원문 근거 파일:

  • /mnt/d/6study/10_프레임워크분석/_원문아카이브/claude-code/how-claude-code-works.md (에이전트 루프 3단계, 모델+도구, 도구 5범주, Esc 인터럽트, 권한모드)
  • /mnt/d/6study/10_프레임워크분석/_원문아카이브/claude-code/headless.md (-p 진입점, --bare, stream-json, system/init·api_retry 이벤트, 백그라운드 작업 종료)
  • /mnt/d/6study/10_프레임워크분석/_원문아카이브/claude-code/cli-reference.md (명령/플래그 표: -p,--max-turns,--max-budget-usd,--permission-mode,--tools,--output-format, 시스템 프롬프트 플래그)
  • /mnt/d/6study/10_프레임워크분석/_원문아카이브/claude-code/overview.md (진입점 cd project && claude, 설치)
  • /mnt/d/6study/10_프레임워크분석/_원문아카이브/claude-code/hooks.md (L22 세 cadence, L2219 Stop, L2293 Stop 결정 제어, stop_hook_active·8연속 block, StopFailure)
  • /mnt/d/6study/10_프레임워크분석/_원문아카이브/claude-code/agent-sdk__overview.md (동일 agent loop를 SDK query()로 노출)
  • /home/seunghyeong/.claude/plugins/marketplaces/claude-plugins-official/plugins/ralph-loop/hooks/hooks.json (실제 Stop 훅 등록)
  • /home/seunghyeong/.claude/plugins/marketplaces/claude-plugins-official/plugins/ralph-loop/hooks/stop-hook.sh (실제 decision:block+reason 재주입 루프, max-iterations 종료조건)