Claude Code는 어떻게 동작하는가 2


터미널에서 Claude Code에게 프롬프트 하나를 던지면, 그 뒤에서는 생각보다 많은 일이 일어난다.

이 글은 “프롬프트를 입력하는 순간부터 답이 돌아오기까지” 그 사이에 무슨 일이 벌어지는지를 다룬다. 바깥의 Agentic loop부터 그 안쪽에서 토큰을 하나씩 만들어내는 Autoregressive decoding loop까지 들여다보며, Claude Code의 동작 원리와 왜 우리가 이걸 **‘하네스’**라고 부르는지를 알아본다.

1. 프롬프트가 모델에 닿기까지: Prompt Construction

Claude와 같은 모델은 우리의 프롬프트만 입력으로 받지 않는다.

예를 들어 Claude Code 터미널에 “이 프로젝트를 분석해줘.”라고 입력했다고 하자. 만약 모델이 저 문장만 받았다면, 지금 있는 폴더가 어떤 폴더인지, 무슨 파일이 있는지 알 방법이 없다. 우리가 지정한 CLAUDE.md, Anthropic이 만든 system prompt, 사용할 수 있는 도구, 그리고 작업 디렉터리나 OS 같은 환경 정보까지 함께 들어가야 모델이 작업을 제대로 수행할 수 있다

이를 위해 입력 프롬프트를 가공하는 과정을 거치는데, 이 일련의 과정을 Prompt Construction이라 부른다.

{
  "model": "claude-opus-4-8",
  "max_tokens": 16000,
  "system": "당신은 Claude Code입니다. ...(Anthropic이 만든 system prompt)",
  "tools": [
    {
      "name": "Read",
      "description": "...",
      "input_schema": {}
    },
    {
      "name": "Bash",
      "description": "...",
      "input_schema": {}
    }
  ],
  "messages": [
    {
      "role": "user",
      "content": "<CLAUDE.md 내용>\n<작업 디렉터리/OS 등 환경 정보>\n\n이 프로젝트를 분석해줘."
    }
  ]
}

(Claude API 호출 예시)

이렇게 만들어진 프롬프트는 크게 3가지로 구성된다. 이는 Anthropic Messages API의 구조와도 대응된다.

  1. System block
  2. Tool block
  3. Messages block

System block에는 Anthropic이 만든 system prompt가 들어간다. Tool block에는 Claude가 사용가능한 Tool 정보와 skill 일부가 들어간다. Messages block에는 CLAUDE.md, 메모리, 일부 Tool과 skill, 그리고 지금까지의 대화 내용 등이 들어간다.

위 내용은 버전이 업데이트 되면서 바뀔 수 있고 정확하지 않을 수 있다. 확실한 것은, Prompt construction을 통해 Claude에게 system prompt, CLAUDE.md, Skill, Tool 등을 알려줄 수 있다는 것이다.

눈여겨볼 만한 건 CLAUDE.md가 System prompt가 아니라는 점, 그리고 Message의 최상단에 Skill과 함께 들어간다는 점이다.

Skill과 CLAUDE.md가 최상단에 들어감으로써 일반 메시지보다 모델이 더 잘 따르게 만들 수 있는데, 이는 2가지 이유 때문이다.

  1. Serial Position Effect(직렬 위치 효과) Transformer 모델은 컨텍스트의 앞부분과 뒷부분을 중간보다 상대적으로 더 잘 활용하는 경향이 있다.
  2. Prompt construction 단계에서 CLAUDE.md에 지시를 덧붙이거나 강조하고, 파일 경로를 함께 넣는 등의 보정이 들어간다.

정리해보면,

프롬프트 구성 다이어그램

이렇게 시스템 프롬프트와 메시지들이 합쳐져 하나의 완성된 입력이 만들어진다.

2. Claude 모델 내부: Transformer와 Autoregressive decoding(자기회귀 디코딩)

자기회귀 디코딩 루프

이렇게 완성된 프롬프트를 Claude 모델이 받으면, 내부에서는 Transformer가 토큰을 하나씩 생성하기 시작한다.

Transformer는 쉽게 말해 “입력이 들어오면 다음에 올 토큰의 확률 분포를 내놓는” 장치다. 가능한 모든 토큰에 대해 “이게 다음에 올 확률”을 계산하고, 그중 하나를 골라낸다. 이 고르는 단계를 decoding이라 부른다. 가장 확률이 높은 토큰만 고를 수도 있고, 확률에 따라 약간의 무작위성을 주며 고를 수도 있다.

이렇게 한 번에 토큰 1개만 정해지기 때문에 완성된 글을 만들려면 이 과정을 계속 반복해야 한다.

방금 생성한 토큰을 다시 입력 끝에 붙여서 또 다음 토큰을 만들고, 이 과정을 종료 토큰(EOS)이 나오거나 최대 길이(max_token)에 도달할 때까지 반복한다.

이것이 Claude 모델 내부에서 일어나는 일이다. 이렇게 자기 출력을 다시 입력으로 먹으며 한 토큰씩 이어 붙이는 방식을 **Autoregressive decoding(자기회귀 디코딩)**이라고 부른다. 앞서 말한 바깥쪽 Agentic loop 안에서 쉴 새 없이 도는 안쪽 루프가 바로 이것이다.

3. Thinking, 그리고 Effort / Adaptive Thinking

Thinking block

Claude에는 EffortAdaptive Thinking 같은 파라미터가 있는데, 둘 다 모델이 “생각(thinking)을 얼마나 할지에 관여한다.”

Thinking이라는 것을 경험해봤을 것이다. “생각중…” 으로 나오거나, “이 프로젝트를 분석해 봐야 하겠습니다” 라던가… (사실 정확하게는 이것은 Thinking block이 아니라 요약본일 수 있다. 이건 뒤에서 다룬다.)

여기서 Thinking이란, 모델이 입력을 받은 뒤 최종 출력을 내기 전에 먼저 Thinking block을 생성하는 것을 말한다. 비유하자면 답을 쓰기 전에 연습장에 먼저 끄적여 보는 과정이다. 이 Thinking block 역시 앞서 본 자기회귀 디코딩으로 한 토큰씩 만들어지고, 다 쓰고 나면 그 내용을 바탕으로 최종 답을 이어쓴다.

Adaptive Thinking은 모델이 “이번 입력에는 생각이 얼마나 필요한지”를 스스로 판단하게 하는 방식이다. 간단한 질문이면 거의 또는 전혀 생각하지 않고, 복잡한 문제면 더 길게 생각한다.

정리하면 이 연습장을 얼마나 길게 쓸지를, Adaptive Thinking은 모델이 알아서, Effort는 우리가 정한 수준에 맞춰 조절하는 셈이다. 그래서 경우에 따라서는 thinking이 아예 일어나지 않을 수도 있다.

4. 모델의 출력: Thinking → Output(tool_use / text)

출력 콘텐츠 블록 순서

모델은 Thinking을 마친 뒤 출력을 내놓는다. 이 출력은 크게 두 가지다.

  • text: 사용자에게 보여주는 텍스트.
  • tool_use: sub-agent, grep, glob, read 같은 도구의 사용 요청. 파일을 읽고 쓰거나 검색하는 등 Claude가 쓸 수 있는 도구들이다.

생성 순서는 대략 Thinking이 먼저, 그다음에 texttool_use가 나온다. 이때 texttool_use는 순서가 고정돼 있지 않고 섞여 나올 수 있다. 예를 들어 “분석을 시작하겠습니다”라는 text를 먼저 낸 뒤 이어서 tool_use를 내기도 한다.

한편 Claude code 화면에서 Thinking block은 전체 내용이 그대로 표시되지 않고, 요약되거나 접힌 형태로만 보인다. 그래서 texttool_use와는 구분된다.

이 점이 흔한 오해를 만든다. 예를 들어 “프로젝트를 분석해줘”라고 하면 Claude가 “프로젝트를 분석하겠습니다”라고 답할 때가 있다. 이건 thinking이 아니라, thinking이 끝난 뒤에 생성된 평범한 text일 뿐이다.

중요한 사실 하나. 모델의 입력부터 출력까지, 도구가 실제로 실행되지는 않는다. 모델은 그저 “이 도구를 써 달라”고 요청만 한다. 도구 요청이 있었다면 모델의 응답에는 stop_reason: tool_use가 담겨, 이제 도구를 실행해야 한다는 신호를 보낸다. 반대로 도구 요청 없이 답을 마치면 stop_reason: end_turn이 담긴다.

5. Claude Code가 루프를 돌린다 : Agentic loop

에이전틱 루프

모델이 stop_reason: tool_use Output을 내면, 이제 Claude Code가 나설 차례다.

  1. 모델이 요청한 도구들을 실제로 실행한다.
  2. 요청된 모든 도구가 끝날 때까지 기다린다.
  3. tool_use와 그 결과(result)를 알맞게 매핑해 Context에 추가한다.
  4. 그 Context로 모델을 다시 호출한다.

아까 예시로 돌아가서, “프로젝트를 분석해줘” 라고 하면, Claude는 생각을 해서 Thinking block을 만들고, “프로젝트를 분석하겠습니다” 라는 text를 만들고, 분석에 필요한 도구를 tool_use로 요청한다. 그리고 마지막 응답에 stop_reason: tool_use를 담는다.

Claude code는 stop_reason을 보고 요청된 도구들을 실행한 뒤, 각 tool_use와 그 결과를 매핑해 Context에 덧붙인다. 작업이 아직 끝나지 않았으므로, 늘어난 Context를 다시 Input으로 넣어 Claude 모델을 한 번 더 호출한다.

이 과정을 반복하다가 모델이 더 이상 도구가 필요 없다고 판단하면, 출력의 stop_reasonend_turn을 담아 “이번 턴을 끝내겠다”고 알린다. Claude code는 이 신호를 보고 턴을 종료한다.

결국 Claude code의 동작 방식은 **“프롬프트가 들어오면 모델을 반복 호출하는 것”**으로 요약된다. 이 반복 구조를 추상화한 것이 바로 **Agentic loop(에이전틱 루프)**다.

6. 루프 속의 루프

중첩 루프

흥미로운 점은, 모델 안에서도 루프가 돈다는 것이다.

  • 바깥 루프(Agentic loop): Claude Code가 모델을 호출하고, 도구를 실행하고, 결과를 붙여 다시 호출한다.

  • 안쪽 루프(Autoregressive decoding loop): 모델 한 번의 호출 안에서, Transformer가 토큰을 하나씩 계속 뽑아낸다.

즉 프롬프트 입력부터 응답까지의 과정은 루프가 중첩된 구조다. 바깥은 Claude code가 돌리는 “모델 호출 ↔ 도구 실행”의 Agentic loop, 안쪽은 모델 내부에서 도는 “한 토큰씩”의 Autoregressive decoding loop다. 우리가 터미널에서 보는 매끈한 한 번의 응답은, 사실 이 두 겹의 루프가 함께 돌아간 결과다.

7. 그래서, Claude code는 하네스다

지금까지의 과정을 되짚어 보자.

  • 우리가 입력한 프롬프트는 그대로 모델에 가지 않는다. system prompt, 도구 정의, CLAUDE.md, 대화 내역이 합쳐지는 Prompt Construction을 거친다.
  • 모델은 이 입력을 받아 토큰을 하나씩 생성할 뿐이다. 도구를 직접 실행하지도, 다음에 무엇을 할지 스스로 행동하지도 않는다. 그저 “이 도구를 써 달라”고 요청(tool_use)하고 멈춘다.
  • 그 요청을 실제 실행으로 옮기고, 결과를 다시 Context에 붙여 모델을 또 호출하는 것, 즉 Agentic loop를 돌리는 것은 전부 Claude Code의 몫이다.

정리하면, 모델 자체는 “입력을 받아 다음 토큰을 내놓는” 장치에 가깝다. 이 장치를 감싸서 프롬프트를 조립하고, 루프를 돌리고, 도구를 실행하고, 컨텍스트를 관리해 실제로 일을 해내는 에이전트로 만드는 소프트웨어 계층, 이것을 하네스(harness)라고 부른다.

말이 힘을 내지만 마구(하네스)가 그 힘을 수레로 연결해 주듯, 토큰을 생성하는 힘은 모델에서 나오지만 그 힘을 실제 작업으로 연결해 주는 것은 Claude Code다. 그래서 우리는 Claude Code를 하네스라고 부른다.

reference: https://code.claude.com/docs/en/how-claude-code-works