Lesson 01 · 기초

루프 없이는 agent도 없다

Claude Code의 모든 비밀은 한 줄로 요약됩니다: while stop_reason == "tool_use"

⏱ 약 10 분 · 📝 3 개 인터랙티브 컴포넌트 · 🧑‍💻 기반 shareAI-lab · s01_agent_loop.py

agent는 실제로 무슨 일을 하는가?

터미널에서 Claude Code를 실행해 "모든 TODO 주석을 목록으로 정리해줘"라고 하면, 스스로 grep을 실행하고, 몇 개 파일을 cat으로 읽고, 마지막에 Markdown을 출력하는 과정을 볼 수 있습니다. 모델 자체는 코드를 실행하지 않습니다—실행을 요청할 뿐입니다. 이 모델이 "손발이 있는 것처럼" 보이게 만드는 건 바깥을 감싸는 30줄 남짓의 접착 코드입니다.

이번 강의는 그 접착 코드를 뜯어봅니다. 이것을 agent loop(에이전트 루프)라고 하며, 구조는 다음과 같습니다:

while response.stop_reason == "tool_use":
    response = LLM(messages, tools)      # 1. ask the model
    execute_tools(response.tool_calls)   # 2. run what it asked for
    messages.append(tool_results)        # 3. feed results back

이게 전부입니다. production Claude Code는 이 위에 권한, hook, 서브 agent, worktree 격리, 메모리 압축을 쌓아올렸지만—핵심은 이 네 줄입니다.

핵심 직관: 모델은 매번 응답할 때 "도구를 호출하고 싶다"거나 "할 말을 다 했다" 중 하나입니다. 전자 상태에 있는 한 루프는 계속되고, 후자가 되면 루프에서 빠져나옵니다. 판단 기준은 응답의 stop_reason 필드입니다.

messages[]가 한 단계씩 쌓이는 것 보기

아래 루프가 시뮬레이션하는 작업은 "현재 디렉토리에 어떤 파일이 있나요? 그리고 package.json을 읽어주세요"입니다. Step을 클릭하면 루프 내 동작이 하나씩 진행됩니다. 왼쪽은 사람이 보는 대화 말풍선, 오른쪽은 실제로 모델에게 전달되는 messages[] 배열—어떻게 늘어나는지 주목하세요.

tool 결과는 반드시 메시지 히스토리에 다시 넣어야 한다

초보자가 가장 자주 실수하는 부분은 "도구 실행"을 부수 효과로 취급하는 것입니다—실행하면 끝이라고 생각하는 것이죠. 하지만 모델은 다음 추론에서 messages[]만 볼 수 있고, 거기 없는 건 일어난 적 없는 것과 같습니다. append를 한 번 빠뜨리면 루프 전체가 망가집니다.

실수의 형태는 두 가지입니다:

  • append를 잊는 경우: 모델은 다음 차례에 결과를 볼 수 없어, 같은 도구를 다시 요청합니다—그래서 무한 루프가 됩니다.
  • append는 했지만 tool_use_id를 누락한 경우: Anthropic API가 바로 tool_result must have tool_use_id 에러를 반환하고, API 호출 단계에서 루프가 중단됩니다.

같은 작업: "프로젝트에 Python 파일이 몇 개 있나요". 두 버전을 나란히 실행해 결과의 차이를 확인하세요.

stop_reason 읽기

모델은 매번 응답할 때 stop_reason을 함께 반환합니다. 루프를 계속할지 말지는 이 필드를 보면 됩니다:

  • tool_use — 모델이 도구를 호출하려 합니다, 루프 계속.
  • end_turn — 모델이 할 말을 다 했습니다, 루프 종료.
  • max_tokens — 토큰 한도에 달해 잘렸습니다, 루프 종료 (일반적으로 예외로 처리하고 사용자에게 출력이 불완전함을 알립니다).
  • stop_sequence — 커스텀 정지 문자열을 만났습니다, 루프 종료.

"tool_use가 아니면 계속"으로 잘못 작성하거나, max_tokens를 정상적인 end로 무시하는 것은 흔한 버그입니다.

직접 실행해보기

로컬에 shareAI-lab/learn-claude-code를 클론하세요:

git clone https://github.com/shareAI-lab/learn-claude-code
cd learn-claude-code
pip install -r requirements.txt
cp .env.example .env  # fill in ANTHROPIC_API_KEY
python agents/s01_agent_loop.py

그런 다음 실제 작업을 시켜보세요: 이 저장소에 .py 파일이 몇 개 있나요. ls -R을 한 번 보내고, 출력을 보고, find . -name "*.py" | wc -l을 보내고, 답을 알려주는 과정을 볼 수 있습니다. 전체 흐름이 바로 이 루프가 돌아가는 것입니다.

Interactive

Widget 1 · Loop Stepper · messages[] 성장 과정

핵심 포인트: tool 결과는 특별한 형식(tool_result 블록, tool_use_id 포함)으로 messages에 다시 삽입됩니다. 이것이 모델이 다음 추론에서 "도구 실행이 완료됐고 결과는 X"라는 것을 아는 유일한 경로입니다.

대화 뷰시작 전
Step을 클릭해 시작 →
messages[] JSONlength: 0
[]
stop_reason:
Interactive

Widget 2 · Break the Loop · 올바른 구현 vs append 누락

핵심 포인트: messages.append(tool_results) 한 줄만 빠져도 agent 전체가 망가집니다—매 라운드마다 이전 것을 잊는 앵무새가 됩니다.

while True:
    resp = LLM(msgs, tools)
    msgs.append({"role":"assistant",
                 "content": resp.content})
    if resp.stop_reason != "tool_use":
        return
    results = execute(resp.tool_uses)
    msgs.append({"role":"user",
                 "content": results})
시뮬레이션 실행
while True:
    resp = LLM(msgs, tools)
    msgs.append({"role":"assistant",
                 "content": resp.content})
    if resp.stop_reason != "tool_use":
        return
    results = execute(resp.tool_uses)
    # BUG: forgot to append results
    # msgs.append(...)
시뮬레이션 실행
Interactive

Widget 3 · stop_reason 식별 · 실제 응답 4개 판별

각각은 실제로 발생할 수 있는 모델 응답 조각입니다. "계속"할지 "종료"할지 선택하고 즉시 정오를 확인하세요.

0 / 4 정답