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)**다.
여기에는 두 개의 부품만 있다.
- 모델(추론) — “다음에 뭘 할까?”를 생각하는 머리.
- 도구(행동) — 파일 읽기, 코드 고치기, 명령 실행 같은 손발.
그리고 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 X | API 비용 상한 도달 시 정지 (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이면 강제 종료된다.
스스로 답해보기:
- 모델이 도구를 부른 뒤 그 결과를 다시 모델 앞에 넣지 않으면, 루프는 어떻게 망가지나?
- 자동 반복 작업을 만들 때
stop_hook_active를 검사해야 하는 이유는? --max-turns와Esc인터럽트는 둘 다 루프를 멈추는데,Stop훅이 도는지 여부에서 무엇이 다른가?
연결
[!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를 SDKquery()로 노출)/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 종료조건)