---
title: 대화 중간 시스템 메시지 및 도구 변경
url: https://platform.claude.com/docs/ko/build-with-claude/mid-conversation-system-messages
description: 이전에 캐시된 프리픽스를 무효화하지 않고 대화 도중에 시스템 지침이나 도구 가용성을 변경합니다.
---

<Note>
  "zero data retention"(제로 데이터 보존), 즉 ZDR이 이 기능에 어떻게 적용되는지 알아보려면 [API 및 데이터 보존](https://platform.claude.com/docs/ko/manage-claude/api-and-data-retention)을 참조하세요.
</Note>

시스템 지침은 일반적으로 대화의 모든 메시지보다 앞에 있는 최상위 `system` 필드에 위치합니다. 이 위치는 ["prompt caching"(프롬프트 캐싱)](https://platform.claude.com/docs/ko/build-with-claude/prompt-caching)에 매우 적합합니다. 시스템 프롬프트가 안정적인 프리픽스의 일부이므로 이후 턴에서 캐시 적중이 발생하기 때문입니다. 하지만 세션 도중에야 필요하다는 것을 알게 되는 지침에는 좋지 않은 위치입니다. 최상위 `system` 필드를 편집하면 프롬프트의 맨 앞부분이 변경되어 그 뒤에 오는 모든 내용에 대한 캐시가 무효화되기 때문입니다.

"Mid-conversation system message"(대화 중간 시스템 메시지)는 이러한 격차를 해소합니다. 최상위 `system` 필드를 편집하는 대신, 새 지침이 관련성을 갖게 되는 대화 지점에 `{"role": "system"}` 메시지를 추가합니다. 캐시된 프리픽스는 그대로 유지되므로 다음 요청도 여전히 캐시에서 이를 읽어오며, 새 지침은 일반 사용자 텍스트가 아닌 시스템 지침으로 적용됩니다.

<Note>
  대화 중간 시스템 메시지는 Claude API, [Claude in Amazon Bedrock](https://platform.claude.com/docs/ko/build-with-claude/claude-in-amazon-bedrock), [Google Cloud](https://platform.claude.com/docs/ko/build-with-claude/claude-on-vertex-ai)에서 사용할 수 있습니다.

  이 기능은 Claude Fable 5.1, [Claude Mythos 5.1](https://platform.claude.com/docs/ko/models/mythos-5-1/overview), Claude Fable 5, [Claude Mythos 5](https://platform.claude.com/docs/ko/models/mythos-5/overview), Claude Opus 5.5, Claude Opus 4.8, Claude Opus 5, Claude Sonnet 5.5, Claude Haiku 5.5에서 사용할 수 있습니다. 대화 중간 시스템 메시지에는 베타 헤더가 필요하지 않습니다. 이 기능은 Claude Sonnet 5에서는 사용할 수 없습니다. 해당 모델에서는 대신 최상위 `system` 필드를 사용하세요.

  [대화 중간 도구 변경](https://platform.claude.com/docs/ko/build-with-claude/mid-conversation-system-messages#mid-conversation-tool-changes)은 동일한 모델에서 베타로 제공됩니다. Claude API에서는 `inline-tools-2026-09-15` 베타 헤더를 보내세요. 이 헤더는 [`tool_addition` 블록 안에서 도구를 정의하는 기능](https://platform.claude.com/docs/ko/build-with-claude/mid-conversation-system-messages#define-tools-in-a-message-beta)도 지원합니다. [그 방식으로 MCP 서버를 추가하려면](https://platform.claude.com/docs/ko/build-with-claude/mid-conversation-system-messages#add-an-mcp-server-mid-conversation-beta) 두 번째 베타 헤더인 `mcp-client-2026-09-15`가 필요하며, 이 헤더는 Claude API에서 사용할 수 있습니다. `mid-conversation-tool-changes-2026-07-01` 헤더는 Claude API, Amazon Bedrock, Google Cloud에서 참조로 도구 이름을 지정하는 변경에 사용할 수 있습니다.

  [턴 범위 시스템 메시지](https://platform.claude.com/docs/ko/build-with-claude/mid-conversation-system-messages#turn-scoped-system-messages)(`clear_at`)는 베타 기능이며 `mid-conversation-system-clear-at-2026-08-21` 베타 헤더가 필요하고, 대화 중간 시스템 메시지와 동일한 모델 및 플랫폼에서 사용할 수 있습니다.
</Note>

## 대화 중간 도구 변경

`tools` 배열은 해시되는 요청 접두사에서 최상위 `system` 필드보다도 앞에 위치하므로, 이를 편집하면 대화 전체의 [프롬프트 캐시](https://platform.claude.com/docs/ko/build-with-claude/prompt-caching)가 무효화됩니다. "Mid-conversation tool changes"(대화 중간 도구 변경)는 대화 중간 시스템 메시지에 대응하는 도구 기능입니다. 대화 전체 기간 동안 도구 목록을 고정하는 대신, 턴 사이에 모델에 제공되는 도구를 변경합니다. 전체 도구 세트를 처음부터 `tools`에 선언한 다음, `tool_addition` 및 `tool_removal` 블록을 사용하여 대화의 특정 지점부터 모델에 도구를 제공하거나 철회합니다. `tools` 배열 자체는 절대 변경되지 않으므로 캐시된 접두사가 그대로 유지됩니다. 대화 중간 도구 변경은 베타로 제공되며 Claude API에서 `inline-tools-2026-09-15` 베타 헤더를 사용합니다.

`tool_addition`과 `tool_removal`은 `role: "system"` 메시지의 `content` 배열에 들어가는 콘텐츠 블록이며, 같은 메시지 안에서 `text` 블록과 함께 사용할 수 있습니다. 이 메시지는 모든 대화 중간 시스템 메시지의 배치 규칙을 따르며, 일시 중지된 턴 이후에는 추가 제한이 하나 있습니다([제한 사항](https://platform.claude.com/docs/ko/build-with-claude/mid-conversation-system-messages#limitations) 참조). 변경 사항은 대화의 해당 지점부터 적용됩니다. 각 블록의 `tool` 필드는 도구를 정의하는 것이 아니라 참조합니다. `{"type": "tool_reference", "name": "..."}`는 요청의 `tools` 배열에 선언된 도구의 이름을 지정하며, [MCP 커넥터](https://platform.claude.com/docs/ko/agents-and-tools/mcp-connector) 도구는 `mcp_tool_reference`(`server_name` 및 `name`)로 개별적으로 참조하거나 `mcp_toolset_reference`(`server_name`)로 도구 세트 전체를 참조할 수 있습니다. `tools`에 선언되지 않은 이름을 참조하면 400 오류가 반환됩니다(Claude API에서는 `error.details.error_code`가 `tool_reference_unresolved`로 설정됨). 대신 `tool_addition` 블록에 [도구의 전체 정의를 담을 수도 있지만](https://platform.claude.com/docs/ko/build-with-claude/mid-conversation-system-messages#define-tools-in-a-message-beta), `mid-conversation-tool-changes-2026-07-01` 헤더는 이를 지원하지 않습니다.

`tools`에 선언된 모든 도구는 `defer_loading: true`로 선언되지 않는 한 대화 시작부터 모델에 제공됩니다. `defer_loading: true`로 선언된 도구는 `tool_addition` 블록이 이를 노출할 때까지 보류됩니다. `tool_addition`은 이전 `tool_removal`이 철회한 도구를 다시 제공하기도 합니다.

다음 요청은 `tools`에 `get_weather`를 선언한 다음, 첫 번째 사용자 턴 이후 `tool_removal` 블록으로 이를 철회합니다. 이 요청은 `inline-tools-2026-09-15` 베타 헤더를 전송합니다.

<CodeGroup>
  ```bash cURL
  curl https://api.anthropic.com/v1/messages \
    -H "content-type: application/json" \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -H "anthropic-beta: inline-tools-2026-09-15" \
    -d '{
      "model": "claude-opus-5-5",
      "max_tokens": 1024,
      "tools": [
        {
          "name": "get_weather",
          "description": "Get the current weather for a location.",
          "input_schema": {
            "type": "object",
            "properties": {
              "location": {"type": "string", "description": "City name"}
            },
            "required": ["location"]
          }
        }
      ],
      "messages": [
        {
          "role": "user",
          "content": "Say OK."
        },
        {
          "role": "system",
          "content": [
            {
              "type": "tool_removal",
              "tool": {"type": "tool_reference", "name": "get_weather"}
            }
          ]
        }
      ]
    }'
  ```

  ```bash CLI
  ant beta:messages create --beta inline-tools-2026-09-15 \
    --transform 'content.#(type=="text").text' --raw-output <<'YAML'
  model: claude-opus-5-5
  max_tokens: 1024
  tools:
    - name: get_weather
      description: Get the current weather for a location.
      input_schema:
        type: object
        properties:
          location:
            type: string
            description: City name
        required:
          - location
  messages:
    - role: user
      content: Say OK.
    - role: system
      content:
        - type: tool_removal
          tool:
            type: tool_reference
            name: get_weather
  YAML
  ```

  ```python Python
  client = anthropic.Anthropic()

  response = client.beta.messages.create(
      model="claude-opus-5-5",
      max_tokens=1024,
      betas=["inline-tools-2026-09-15"],
      # 전체 도구 세트를 처음에 선언하고 이후 변경하지 않으므로
      # 캐시된 접두사가 그대로 유지됩니다.
      tools=[
          {
              "name": "get_weather",
              "description": "Get the current weather for a location.",
              "input_schema": {
                  "type": "object",
                  "properties": {
                      "location": {"type": "string", "description": "City name"},
                  },
                  "required": ["location"],
              },
          },
      ],
      messages=[
          {
              "role": "user",
              "content": "Say OK.",
          },
          # 이 시점부터 get_weather 도구를 철회합니다. 이 블록은 `tools`를 수정하는 대신
          # 이름으로 도구를 참조하므로 이전 턴이
          # 바이트 단위로 동일하게 유지되어 캐시가 계속 적중합니다.
          {
              "role": "system",
              "content": [
                  {
                      "type": "tool_removal",
                      "tool": {"type": "tool_reference", "name": "get_weather"},
                  },
              ],
          },
      ],
  )

  for block in response.content:
      if block.type == "text":
          print(block.text)
  ```

  ```typescript TypeScript
  const client = new Anthropic();

  const response = await client.beta.messages.create({
    model: "claude-opus-5-5",
    max_tokens: 1024,
    betas: ["inline-tools-2026-09-15"],
    // 전체 도구 세트를 처음에 선언하고 이후 변경하지 않으므로
    // 캐시된 접두사가 그대로 유지됩니다.
    tools: [
      {
        name: "get_weather",
        description: "Get the current weather for a location.",
        input_schema: {
          type: "object",
          properties: {
            location: {
              type: "string",
              description: "City name"
            }
          },
          required: ["location"]
        }
      }
    ],
    messages: [
      { role: "user", content: "Say OK." },
      // 이 시점부터 get_weather를 철회합니다. 이 블록은
      // `tools`를 편집하는 대신 도구를 이름으로 참조하므로 이전 턴이
      // 바이트 단위로 동일하게 유지되어 캐시가 계속 적중합니다.
      {
        role: "system",
        content: [
          {
            type: "tool_removal",
            tool: { type: "tool_reference", name: "get_weather" }
          }
        ]
      }
    ]
  });

  for (const block of response.content) {
    if (block.type === "text") {
      console.log(block.text);
    }
  }
  ```

  ```csharp C#
  using Anthropic.Models.Beta;
  using Anthropic.Models.Beta.Messages;
  using Messages = Anthropic.Models.Messages;

  AnthropicClient client = new();

  var response = await client.Beta.Messages.Create(new MessageCreateParams
  {
      Model = Messages::Model.ClaudeOpus5_5,
      MaxTokens = 1024,
      Betas = [AnthropicBeta.InlineTools2026_09_15],
      // 전체 도구 세트는 처음에 선언되며 변경되지 않으므로
      // 캐시된 접두사가 그대로 유지됩니다.
      Tools =
      [
          new BetaTool
          {
              Name = "get_weather",
              Description = "Get the current weather for a location.",
              InputSchema = new InputSchema
              {
                  Properties = new Dictionary<string, JsonElement>
                  {
                      ["location"] = JsonSerializer.SerializeToElement(new { type = "string", description = "City name" }),
                  },
                  Required = ["location"],
              },
          },
      ],
      Messages =
      [
          new() { Role = Role.User, Content = "Say OK." },
          // 이 시점부터 get_weather를 철회합니다. 이 블록은
          // `Tools`를 수정하는 대신 이름으로 도구를 참조하므로, 이전 턴은
          // 바이트 단위로 동일하게 유지되고 캐시도 계속 적중합니다.
          new()
          {
              Role = Role.System,
              Content = new(
              [
                  new BetaRequestToolRemovalBlock
                  {
                      Tool = new BetaToolChangeToolReference { Name = "get_weather" },
                  },
              ]),
          },
      ],
  });

  foreach (var block in response.Content)
  {
      if (block.TryPickText(out var text))
      {
          Console.WriteLine(text.Text);
      }
  }
  ```

  ```go Go
  client := anthropic.NewClient()

  response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
  	Model:     anthropic.ModelClaudeOpus5_5,
  	MaxTokens: 1024,
  	Betas:     []anthropic.AnthropicBeta{anthropic.AnthropicBetaInlineTools2026_09_15},
  	// 전체 도구 세트를 처음에 선언하고 이후 변경하지 않으므로
  	// 캐시된 접두사가 그대로 유지됩니다.
  	Tools: []anthropic.BetaToolUnionParam{
  		{OfTool: &anthropic.BetaToolParam{
  			Name:        "get_weather",
  			Description: anthropic.String("Get the current weather for a location."),
  			InputSchema: anthropic.BetaToolInputSchemaParam{
  				Properties: map[string]any{
  					"location": map[string]any{
  						"type":        "string",
  						"description": "City name",
  					},
  				},
  				Required: []string{"location"},
  			},
  		}},
  	},
  	Messages: []anthropic.BetaMessageParam{
  		anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Say OK.")),
  		// 이 시점부터 get_weather를 제외합니다. 이 블록은 Tools를 편집하는 대신
  		// 이름으로 도구를 참조하므로, 이전 턴이
  		// 바이트 단위로 동일하게 유지되어 캐시가 계속 적중합니다.
  		{
  			Role: anthropic.BetaMessageParamRoleSystem,
  			Content: []anthropic.BetaContentBlockParamUnion{
  				anthropic.NewBetaToolRemovalBlock(anthropic.BetaToolChangeToolReferenceParam{
  					Name: "get_weather",
  				}),
  			},
  		},
  	},
  })
  if err != nil {
  	log.Fatal(err)
  }

  for _, block := range response.Content {
  	if textBlock, ok := block.AsAny().(anthropic.BetaTextBlock); ok {
  		fmt.Println(textBlock.Text)
  	}
  }
  ```

  ```java Java
  import com.anthropic.models.beta.AnthropicBeta;
  import com.anthropic.models.beta.messages.BetaContentBlockParam;
  import com.anthropic.models.beta.messages.BetaMessage;
  import com.anthropic.models.beta.messages.BetaMessageParam;
  import com.anthropic.models.beta.messages.BetaRequestToolRemovalBlock;
  import com.anthropic.models.beta.messages.BetaTool;
  import com.anthropic.models.beta.messages.MessageCreateParams;
  // ...
      AnthropicClient client = AnthropicOkHttpClient.fromEnv();

      // 전체 도구 세트를 처음에 선언하고 이후 변경하지 않으므로
      // 캐시된 접두사가 그대로 유지됩니다.
      BetaTool weatherTool = BetaTool.builder()
          .name("get_weather")
          .description("Get the current weather for a location.")
          .inputSchema(BetaTool.InputSchema.builder()
              .properties(BetaTool.InputSchema.Properties.builder()
                  .putAdditionalProperty("location", JsonValue.from(Map.of(
                      "type", "string",
                      "description", "City name")))
                  .build())
              .addRequired("location")
              .build())
          .build();

      MessageCreateParams params = MessageCreateParams.builder()
          .model(Model.CLAUDE_OPUS_5_5)
          .maxTokens(1024)
          .addBeta(AnthropicBeta.INLINE_TOOLS_2026_09_15)
          .addTool(weatherTool)
          .addUserMessage("Say OK.")
          // 이 시점부터 get_weather 도구를 철회합니다. 이 블록은 `tools` 목록을 수정하는 대신
          // 도구를 이름으로 참조하므로 이전 턴이
          // 바이트 단위로 동일하게 유지되어 캐시가 계속 적중합니다.
          .addMessage(BetaMessageParam.builder()
              .role(BetaMessageParam.Role.SYSTEM)
              .contentOfBetaContentBlockParams(List.of(
                  BetaContentBlockParam.ofToolRemoval(BetaRequestToolRemovalBlock.builder()
                      .referenceTool("get_weather")
                      .build())))
              .build())
          .build();

      BetaMessage response = client.beta().messages().create(params);
      response.content().stream()
          .flatMap(block -> block.text().stream())
          .forEach(textBlock -> IO.println(textBlock.text()));
  ```

  ```php PHP
  use Anthropic\Beta\AnthropicBeta;
  // ...

  $client = new Client();

  $response = $client->beta->messages->create(
      model: Model::CLAUDE_OPUS_5_5,
      maxTokens: 1024,
      betas: [AnthropicBeta::INLINE_TOOLS_2026_09_15],
      // 전체 도구 세트를 처음에 선언하고 이후 변경하지 않으므로
      // 캐시된 접두사가 그대로 유지됩니다.
      tools: [
          [
              'name' => 'get_weather',
              'description' => 'Get the current weather for a location.',
              'input_schema' => [
                  'type' => 'object',
                  'properties' => [
                      'location' => [
                          'type' => 'string',
                          'description' => 'City name',
                      ],
                  ],
                  'required' => ['location'],
              ],
          ],
      ],
      messages: [
          ['role' => 'user', 'content' => 'Say OK.'],
          // 이 시점부터 get_weather를 철회합니다. 이 블록은 `tools`를 수정하는 대신
          // 이름으로 도구를 참조하므로 이전 턴이
          // 바이트 단위로 동일하게 유지되어 캐시가 계속 적중합니다.
          [
              'role' => 'system',
              'content' => [
                  [
                      'type' => 'tool_removal',
                      'tool' => ['type' => 'tool_reference', 'name' => 'get_weather'],
                  ],
              ],
          ],
      ],
  );

  foreach ($response->content as $block) {
      if ($block->type === 'text') {
          echo $block->text, PHP_EOL;
      }
  }
  ```

  ```ruby Ruby
  client = Anthropic::Client.new

  response = client.beta.messages.create(
    model: Anthropic::Model::CLAUDE_OPUS_5_5,
    max_tokens: 1024,
    betas: [Anthropic::AnthropicBeta::INLINE_TOOLS_2026_09_15],
    # 전체 도구 세트는 처음에 선언되며 변경되지 않으므로
    # 캐시된 접두사가 그대로 유지됩니다.
    tools: [
      {
        name: "get_weather",
        description: "Get the current weather for a location.",
        input_schema: {
          type: "object",
          properties: {
            location: { type: "string", description: "City name" }
          },
          required: ["location"]
        }
      }
    ],
    messages: [
      { role: "user", content: "Say OK." },
      # 이 시점부터 get_weather를 제외합니다. 이 블록은 도구를 이름으로
      # 참조할 뿐 `tools`를 편집하지 않으므로, 이전 턴이
      # 바이트 단위로 동일하게 유지되어 캐시가 계속 적중합니다.
      {
        role: "system",
        content: [
          {
            type: "tool_removal",
            tool: { type: "tool_reference", name: "get_weather" }
          }
        ]
      }
    ]
  )

  response.content.each do |block|
    puts block.text if block.type == :text
  end
  ```
</CodeGroup>

### 메시지에서 도구 정의하기 (베타)

`inline-tools-2026-09-15` 베타 헤더를 사용하면 `tool_addition` 블록이 도구를 참조로 지정하는 대신 전체 정의를 담아 값으로 도구를 정의할 수 있습니다. 이를 통해 대화 시작 시점에는 알 수 없었던 도구나 나중에 스키마가 변경되는 도구를 `role: "system"` 메시지를 추가하여 도입할 수 있습니다. `tools` 배열과 이전의 모든 메시지는 전송된 그대로 유지되므로 캐시가 여전히 적중하며, 추가된 메시지만 새 입력으로 처리됩니다. 유일한 예외인, 지연되지 않은 도구가 하나도 없는 `tools` 배열의 경우는 아래 규칙에서 다룹니다. 이 헤더는 참조를 통한 도구 추가 및 제거도 포함하므로 `mid-conversation-tool-changes-2026-07-01`을 함께 보낼 필요가 없습니다.

정의를 `tool_definition` 유형의 `tool` 객체로 감싸세요. `definition`은 사용자 정의 도구나 Anthropic 정의 클라이언트 또는 서버 도구와 같은 `tools` 항목이며, `cache_control` 및 `defer_loading`을 포함한 일반적인 구성을 갖습니다. 베타 기간 동안 일부 도구 유형(컴퓨터 사용 도구 포함)은 아직 메시지에서 정의할 수 없으며, 그렇다는 내용의 400 오류를 반환합니다. 이러한 도구는 `tools`에 선언하고 참조로 추가하세요. 예를 들어, 대화 중간에 사용자 정의 도구를 정의하려면 다음과 같이 합니다:

```json
{
  "role": "system",
  "content": [
    {
      "type": "tool_addition",
      "tool": {
        "type": "tool_definition",
        "definition": {
          "name": "db_query",
          "description": "Run a read-only SQL query against the analytics database.",
          "input_schema": {
            "type": "object",
            "properties": { "sql": { "type": "string" } },
            "required": ["sql"]
          }
        }
      }
    }
  ]
}
```

해당 위치부터 모델은 `tools`에 선언된 도구를 호출하는 것과 같은 방식으로 이 도구를 호출할 수 있습니다. 동일한 정의를 다시 보내도 아무것도 변경되지 않으므로, 클라이언트는 재시도 시 등에 안전하게 다시 보낼 수 있습니다.

다음 요청은 `get_weather`를 `tools`에 유지하고 첫 번째 사용자 턴 이후에 `db_query`를 정의합니다:

<CodeGroup>
  ```bash cURL
  curl https://api.anthropic.com/v1/messages \
    -H "content-type: application/json" \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -H "anthropic-beta: inline-tools-2026-09-15" \
    -d '{
      "model": "claude-opus-5-5",
      "max_tokens": 1024,
      "tools": [
        {
          "name": "get_weather",
          "description": "Get the current weather for a location.",
          "input_schema": {
            "type": "object",
            "properties": {
              "location": {"type": "string", "description": "City name"}
            },
            "required": ["location"]
          }
        }
      ],
      "messages": [
        {
          "role": "user",
          "content": "How many orders shipped yesterday?"
        },
        {
          "role": "system",
          "content": [
            {
              "type": "tool_addition",
              "tool": {
                "type": "tool_definition",
                "definition": {
                  "name": "db_query",
                  "description": "Run a read-only SQL query against the analytics database.",
                  "input_schema": {
                    "type": "object",
                    "properties": {"sql": {"type": "string"}},
                    "required": ["sql"]
                  }
                }
              }
            }
          ]
        }
      ]
    }'
  ```

  ```bash CLI
  ant beta:messages create --beta inline-tools-2026-09-15 <<'YAML'
  model: claude-opus-5-5
  max_tokens: 1024
  # `tools`에 지연되지 않은(non-deferred) 도구를 하나 이상 유지하세요. 그래야 나중에
  # 정의되는 도구가 렌더링된 프롬프트의 시작 부분을 바꾸지 않습니다.
  tools:
    - name: get_weather
      description: Get the current weather for a location.
      input_schema:
        type: object
        properties:
          location:
            type: string
            description: City name
        required:
          - location
  messages:
    - role: user
      content: How many orders shipped yesterday?
    # 이 시점부터는 db_query를 값으로 정의합니다. `tools`와
    # 이전 메시지는 전송된 그대로 유지되므로 캐시가 계속 적중합니다.
    - role: system
      content:
        - type: tool_addition
          tool:
            type: tool_definition
            definition:
              name: db_query
              description: Run a read-only SQL query against the analytics database.
              input_schema:
                type: object
                properties:
                  sql:
                    type: string
                required:
                  - sql
  YAML
  ```

  ```python Python
  client = anthropic.Anthropic()

  response = client.beta.messages.create(
      model="claude-opus-5-5",
      max_tokens=1024,
      betas=["inline-tools-2026-09-15"],
      # 나중에 정의되는 도구가 렌더링된 프롬프트의 시작 부분을 바꾸지 않도록,
      # `tools`에 지연되지 않은 도구를 하나 이상 유지하세요.
      tools=[
          {
              "name": "get_weather",
              "description": "Get the current weather for a location.",
              "input_schema": {
                  "type": "object",
                  "properties": {
                      "location": {"type": "string", "description": "City name"},
                  },
                  "required": ["location"],
              },
          },
      ],
      messages=[
          {"role": "user", "content": "How many orders shipped yesterday?"},
          # 이 시점부터 db_query를 값으로 정의합니다. `tools`와
          # 이전 메시지는 보낸 그대로 유지되므로 캐시가 계속 적중합니다.
          {
              "role": "system",
              "content": [
                  {
                      "type": "tool_addition",
                      "tool": {
                          "type": "tool_definition",
                          "definition": {
                              "name": "db_query",
                              "description": "Run a read-only SQL query against the analytics database.",
                              "input_schema": {
                                  "type": "object",
                                  "properties": {"sql": {"type": "string"}},
                                  "required": ["sql"],
                              },
                          },
                      },
                  },
              ],
          },
      ],
  )

  for block in response.content:
      if block.type == "tool_use":
          print(block.name, block.input)
  ```

  ```typescript TypeScript
  const client = new Anthropic();

  const response = await client.beta.messages.create({
    model: "claude-opus-5-5",
    max_tokens: 1024,
    betas: ["inline-tools-2026-09-15"],
    // `tools`에 지연 로드되지 않는 도구를 하나 이상 유지하세요. 그래야 나중에 정의된
    // 도구가 렌더링된 프롬프트의 시작 부분을 바꾸지 않습니다.
    tools: [
      {
        name: "get_weather",
        description: "Get the current weather for a location.",
        input_schema: {
          type: "object",
          properties: {
            location: { type: "string", description: "City name" }
          },
          required: ["location"]
        }
      }
    ],
    messages: [
      { role: "user", content: "How many orders shipped yesterday?" },
      // 이 시점부터는 db_query를 값으로 정의합니다. `tools`와
      // 이전 메시지는 전송된 그대로 유지되므로 캐시가 계속 적중합니다.
      {
        role: "system",
        content: [
          {
            type: "tool_addition",
            tool: {
              type: "tool_definition",
              definition: {
                name: "db_query",
                description: "Run a read-only SQL query against the analytics database.",
                input_schema: {
                  type: "object",
                  properties: { sql: { type: "string" } },
                  required: ["sql"]
                }
              }
            }
          }
        ]
      }
    ]
  });

  for (const block of response.content) {
    if (block.type === "tool_use") {
      console.log(block.name, JSON.stringify(block.input));
    }
  }
  ```

  ```csharp C#
  using Anthropic.Models.Beta;
  using Anthropic.Models.Beta.Messages;
  using Messages = Anthropic.Models.Messages;

  AnthropicClient client = new();

  var response = await client.Beta.Messages.Create(new MessageCreateParams
  {
      Model = Messages::Model.ClaudeOpus5_5,
      MaxTokens = 1024,
      Betas = [AnthropicBeta.InlineTools2026_09_15],
      // `Tools`에 지연되지 않은(non-deferred) 도구를 하나 이상 유지하세요. 그래야 나중에
      // 정의되는 도구가 렌더링된 프롬프트의 시작 부분을 바꾸지 않습니다.
      Tools =
      [
          new BetaTool
          {
              Name = "get_weather",
              Description = "Get the current weather for a location.",
              InputSchema = new InputSchema
              {
                  Properties = new Dictionary<string, JsonElement>
                  {
                      ["location"] = JsonSerializer.SerializeToElement(new { type = "string", description = "City name" }),
                  },
                  Required = ["location"],
              },
          },
      ],
      Messages =
      [
          new() { Role = Role.User, Content = "How many orders shipped yesterday?" },
          // 이 시점부터는 db_query를 값으로 정의합니다. `Tools`와
          // 이전 메시지는 전송된 그대로 유지되므로 캐시가 계속 적중합니다.
          new()
          {
              Role = Role.System,
              Content = new(
              [
                  new BetaRequestToolAdditionBlock
                  {
                      Tool = new BetaToolChangeToolDefinitionParam
                      {
                          Definition = new BetaTool
                          {
                              Name = "db_query",
                              Description = "Run a read-only SQL query against the analytics database.",
                              InputSchema = new InputSchema
                              {
                                  Properties = new Dictionary<string, JsonElement>
                                  {
                                      ["sql"] = JsonSerializer.SerializeToElement(new { type = "string" }),
                                  },
                                  Required = ["sql"],
                              },
                          },
                      },
                  },
              ]),
          },
      ],
  });

  foreach (var block in response.Content)
  {
      if (block.TryPickToolUse(out var toolUse))
      {
          Console.WriteLine($"{toolUse.Name} {JsonSerializer.Serialize(toolUse.Input)}");
      }
  }
  ```

  ```go Go
  client := anthropic.NewClient()

  response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
  	Model:     anthropic.ModelClaudeOpus5_5,
  	MaxTokens: 1024,
  	Betas:     []anthropic.AnthropicBeta{anthropic.AnthropicBetaInlineTools2026_09_15},
  	// Tools에 지연되지 않은(non-deferred) 도구를 하나 이상 유지하세요. 그래야 나중에
  	// 정의되는 도구가 렌더링된 프롬프트의 시작 부분을 바꾸지 않습니다.
  	Tools: []anthropic.BetaToolUnionParam{
  		{OfTool: &anthropic.BetaToolParam{
  			Name:        "get_weather",
  			Description: anthropic.String("Get the current weather for a location."),
  			InputSchema: anthropic.BetaToolInputSchemaParam{
  				Properties: map[string]any{
  					"location": map[string]any{
  						"type":        "string",
  						"description": "City name",
  					},
  				},
  				Required: []string{"location"},
  			},
  		}},
  	},
  	Messages: []anthropic.BetaMessageParam{
  		anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("How many orders shipped yesterday?")),
  		// 이 시점부터는 db_query를 값으로 직접 정의합니다. Tools와
  		// 이전 메시지는 전송된 그대로 유지되므로 캐시가 계속 적중합니다.
  		{
  			Role: anthropic.BetaMessageParamRoleSystem,
  			Content: []anthropic.BetaContentBlockParamUnion{
  				anthropic.NewBetaToolAdditionBlock(anthropic.BetaToolChangeToolDefinitionParam{
  					Definition: anthropic.BetaToolUnionParam{OfTool: &anthropic.BetaToolParam{
  						Name:        "db_query",
  						Description: anthropic.String("Run a read-only SQL query against the analytics database."),
  						InputSchema: anthropic.BetaToolInputSchemaParam{
  							Properties: map[string]any{
  								"sql": map[string]any{"type": "string"},
  							},
  							Required: []string{"sql"},
  						},
  					}},
  				}),
  			},
  		},
  	},
  })
  if err != nil {
  	log.Fatal(err)
  }

  for _, block := range response.Content {
  	if toolUse, ok := block.AsAny().(anthropic.BetaToolUseBlock); ok {
  		fmt.Println(toolUse.Name, toolUse.Input)
  	}
  }
  ```

  ```java Java
  import com.anthropic.models.beta.AnthropicBeta;
  import com.anthropic.models.beta.messages.BetaContentBlockParam;
  import com.anthropic.models.beta.messages.BetaMessage;
  import com.anthropic.models.beta.messages.BetaRequestToolAdditionBlock;
  import com.anthropic.models.beta.messages.BetaTool;
  import com.anthropic.models.beta.messages.MessageCreateParams;
  // ...

  void main() {
      AnthropicClient client = AnthropicOkHttpClient.fromEnv();

      BetaTool weatherTool = BetaTool.builder()
          .name("get_weather")
          .description("Get the current weather for a location.")
          .inputSchema(BetaTool.InputSchema.builder()
              .properties(BetaTool.InputSchema.Properties.builder()
                  .putAdditionalProperty("location", JsonValue.from(Map.of(
                      "type", "string",
                      "description", "City name")))
                  .build())
              .addRequired("location")
              .build())
          .build();

      BetaTool dbQueryTool = BetaTool.builder()
          .name("db_query")
          .description("Run a read-only SQL query against the analytics database.")
          .inputSchema(BetaTool.InputSchema.builder()
              .properties(BetaTool.InputSchema.Properties.builder()
                  .putAdditionalProperty("sql", JsonValue.from(Map.of("type", "string")))
                  .build())
              .addRequired("sql")
              .build())
          .build();

      MessageCreateParams params = MessageCreateParams.builder()
          .model(Model.CLAUDE_OPUS_5_5)
          .maxTokens(1024)
          .addBeta(AnthropicBeta.INLINE_TOOLS_2026_09_15)
          // `tools`에 지연 로드되지 않는 도구를 최소 하나 유지하세요. 그래야 나중에 정의된
          // 도구가 렌더링된 프롬프트의 시작 부분을 바꾸지 않습니다.
          .addTool(weatherTool)
          .addUserMessage("How many orders shipped yesterday?")
          // 이 시점부터는 db_query를 값으로 정의합니다. `tools`와
          // 이전 메시지는 전송된 그대로 유지되므로 캐시가 계속 적중합니다.
          .addSystemMessageOfBetaContentBlockParams(List.of(
              BetaContentBlockParam.ofToolAddition(BetaRequestToolAdditionBlock.builder()
                  .definitionTool(dbQueryTool)
                  .build())))
          .build();

      BetaMessage response = client.beta().messages().create(params);
      response.content().stream()
          .flatMap(block -> block.toolUse().stream())
          .forEach(toolUse -> IO.println(toolUse.name() + " " + toolUse._input()));
  }
  ```

  ```php PHP
  use Anthropic\Beta\AnthropicBeta;
  use Anthropic\Beta\Messages\BetaToolUseBlock;
  // ...

  $client = new Client();

  $response = $client->beta->messages->create(
      model: Model::CLAUDE_OPUS_5_5,
      maxTokens: 1024,
      betas: [AnthropicBeta::INLINE_TOOLS_2026_09_15],
      // `tools`에 지연 로딩되지 않는 도구를 하나 이상 유지하세요. 그래야 나중에 정의된
      // 도구가 렌더링된 프롬프트의 시작 부분을 바꾸지 않습니다.
      tools: [
          [
              'name' => 'get_weather',
              'description' => 'Get the current weather for a location.',
              'input_schema' => [
                  'type' => 'object',
                  'properties' => [
                      'location' => [
                          'type' => 'string',
                          'description' => 'City name',
                      ],
                  ],
                  'required' => ['location'],
              ],
          ],
      ],
      messages: [
          ['role' => 'user', 'content' => 'How many orders shipped yesterday?'],
          // 이 시점부터는 db_query를 값으로 직접 정의합니다. `tools`와
          // 이전 메시지는 전송된 그대로 유지되므로 캐시가 계속 적중합니다.
          [
              'role' => 'system',
              'content' => [
                  [
                      'type' => 'tool_addition',
                      'tool' => [
                          'type' => 'tool_definition',
                          'definition' => [
                              'name' => 'db_query',
                              'description' => 'Run a read-only SQL query against the analytics database.',
                              'input_schema' => [
                                  'type' => 'object',
                                  'properties' => ['sql' => ['type' => 'string']],
                                  'required' => ['sql'],
                              ],
                          ],
                      ],
                  ],
              ],
          ],
      ],
  );

  foreach ($response->content as $block) {
      if ($block instanceof BetaToolUseBlock) {
          echo $block->name, ' ', json_encode($block->input), PHP_EOL;
      }
  }
  ```

  ```ruby Ruby
  client = Anthropic::Client.new

  response = client.beta.messages.create(
    model: Anthropic::Model::CLAUDE_OPUS_5_5,
    max_tokens: 1024,
    betas: [Anthropic::AnthropicBeta::INLINE_TOOLS_2026_09_15],
    # `tools`에 지연되지 않은(non-deferred) 도구를 하나 이상 유지하세요. 그래야 나중에 정의된
    # 도구가 렌더링된 프롬프트의 시작 부분을 바꾸지 않습니다.
    tools: [
      {
        name: "get_weather",
        description: "Get the current weather for a location.",
        input_schema: {
          type: "object",
          properties: {
            location: { type: "string", description: "City name" }
          },
          required: ["location"]
        }
      }
    ],
    messages: [
      { role: "user", content: "How many orders shipped yesterday?" },
      # 이 시점부터는 db_query를 값으로 정의합니다. `tools`와
      # 이전 메시지는 전송된 그대로 유지되므로 캐시가 계속 적중합니다.
      {
        role: "system",
        content: [
          {
            type: "tool_addition",
            tool: {
              type: "tool_definition",
              definition: {
                name: "db_query",
                description: "Run a read-only SQL query against the analytics database.",
                input_schema: {
                  type: "object",
                  properties: { sql: { type: "string" } },
                  required: ["sql"]
                }
              }
            }
          }
        ]
      }
    ]
  )

  response.content.each do |block|
    puts "#{block.name} #{block.input}" if block.is_a?(Anthropic::Beta::BetaToolUseBlock)
  end
  ```
</CodeGroup>

응답의 `content`에는 새 도구에 대한 `tool_use` 블록이 포함됩니다. 예를 들면 다음과 같습니다:

```json
{
  "type": "tool_use",
  "id": "toolu_01A09q90qw90lq917835lq9",
  "name": "db_query",
  "input": {
    "sql": "SELECT COUNT(*) FROM orders WHERE shipped_at::date = CURRENT_DATE - 1"
  }
}
```

도구의 스키마를 변경하거나 서버 도구를 최신 버전으로 옮기려면 같은 이름으로 다른 정의를 보내세요. 새 정의는 해당 위치부터 이전 정의를 대체합니다. 다른 유형의 도구 이름을 재사용하는 정의는 `error.details.error_code`가 `tool_name_conflict`로 설정된 400 오류를 반환합니다. 같은 도구의 최신 버전은 다른 유형으로 간주되지 않습니다. `tool_removal`은 여전히 참조를 사용하며, 제거된 도구는 나중에 다시 정의하거나 다시 제공할 수 있습니다.

정의가 렌더링되는 위치에 따라 몇 가지 규칙이 적용됩니다:

* **알고 있는 것은 미리 선언하세요.** 첫 번째 요청 시점에 알고 있는 도구는 `tools`에 넣어야 하며, 모델이 아직 보지 않아야 한다면 `defer_loading: true`와 이후의 `tool_addition` 참조를 사용하세요. 첫 번째 요청 시점에 알 수 없거나 나중에 변경되는 것만 값으로 정의하세요.
* **`tools`에 지연되지 않은 도구를 최소 하나 유지하세요.** `tools` 배열에 지연되지 않은 도구가 없는 대화도 허용되지만, 값으로 정의하는 첫 번째 도구가 렌더링된 프롬프트의 시작 부분을 변경하므로 해당 요청에서 전체 캐시 미스가 한 번 발생합니다. [도구 검색 도구](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/tool-search-tool)는 지연되지 않은 도구로 간주됩니다.
* **날짜가 지정된 도구 유형은 자체 베타 헤더를 유지합니다.** 값으로 정의하는 서버 도구에 자체 베타 헤더가 필요한 경우, 대화의 이후 모든 요청에 해당 헤더를 보내세요.
* **`cache_control`은 블록 또는 정의 중 한 곳에만 두고 둘 다에 두지 않으며,** 요청의 중단점 제한에 포함됩니다. 지연된 정의에는 `cache_control`을 지정할 수 없습니다.

다음 제한 중 하나라도 초과하면 요청은 `error.details.error_code`가 `available_tools_limit_exceeded`로 설정된 400 오류를 반환합니다:

* 어떤 메시지 이후에든 10,000개를 초과하는 지연된 도구가 사용 가능한 경우.
* 첫 번째 사용자 메시지 이후에 정의된 도구 중 10,000개를 초과하는 도구가 어떤 메시지 이후에든 사용 가능한 경우.
* 첫 번째 사용자 메시지 이후에 전송되어 어떤 메시지 이후에든 여전히 사용 가능한 도구 정의의 합계가 4 MB(4,194,304바이트)를 초과하는 경우.
* 렌더링된 도구 텍스트가 4 MB(4,194,304바이트)보다 큰 경우.

### 대화 중간에 MCP 서버 추가하기 (베타)

대화 도중에 [MCP 커넥터](https://platform.claude.com/docs/ko/agents-and-tools/mcp-connector) 서버를 추가하려면 `inline-tools-2026-09-15`와 함께 `mcp-client-2026-09-15` 베타 헤더를 보내세요. 그러면 `tool_addition` 블록의 `definition`이 `mcp_toolset`이 될 수 있으므로, `tools`를 편집하지 않고도 서버의 도구를 사용할 수 있게 됩니다. 평소처럼 `mcp_servers`에 서버의 연결 세부 정보를 나열한 다음, 서버가 사용 가능해진 위치에 도구 세트를 추가하세요:

```json
{
  "role": "system",
  "content": [
    {
      "type": "tool_addition",
      "tool": {
        "type": "tool_definition",
        "definition": { "type": "mcp_toolset", "mcp_server_name": "calendar" }
      }
    }
  ]
}
```

`mcp_toolset` 객체는 `default_config` 및 `configs`를 포함하여 `tools`에 넣는 것과 동일한 객체입니다. `tool_addition` 블록에는 서버 URL이나 토큰이 절대 포함되지 않습니다. 이러한 정보는 `mcp_servers`에 유지됩니다.

다음 요청은 `get_weather`를 `tools`에 유지하고, `mcp_servers`에 캘린더 서버를 나열하며, 첫 번째 사용자 턴 이후에 서버의 도구 세트를 추가합니다:

<CodeGroup>
  ```bash cURL
  curl https://api.anthropic.com/v1/messages \
    -H "content-type: application/json" \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -H "anthropic-beta: inline-tools-2026-09-15,mcp-client-2026-09-15" \
    -d '{
      "model": "claude-opus-5-5",
      "max_tokens": 1024,
      "mcp_servers": [
        {
          "type": "url",
          "url": "https://mcp.example.com/calendar",
          "name": "calendar",
          "authorization_token": "YOUR_TOKEN"
        }
      ],
      "tools": [
        {
          "name": "get_weather",
          "description": "Get the current weather for a location.",
          "input_schema": {
            "type": "object",
            "properties": {
              "location": {"type": "string", "description": "City name"}
            },
            "required": ["location"]
          }
        }
      ],
      "messages": [
        {
          "role": "user",
          "content": "What'\''s on my calendar tomorrow?"
        },
        {
          "role": "system",
          "content": [
            {
              "type": "tool_addition",
              "tool": {
                "type": "tool_definition",
                "definition": {
                  "type": "mcp_toolset",
                  "mcp_server_name": "calendar"
                }
              }
            }
          ]
        }
      ]
    }'
  ```

  ```bash CLI
  ant beta:messages create \
    --beta inline-tools-2026-09-15,mcp-client-2026-09-15 <<'YAML'
  model: claude-opus-5-5
  max_tokens: 1024
  mcp_servers:
    - type: url
      url: https://mcp.example.com/calendar
      name: calendar
      authorization_token: YOUR_TOKEN
  tools:
    - name: get_weather
      description: Get the current weather for a location.
      input_schema:
        type: object
        properties:
          location:
            type: string
            description: City name
        required:
          - location
  messages:
    - role: user
      content: What's on my calendar tomorrow?
    # 이 시점부터 캘린더 서버의 도구를 사용할 수 있도록 합니다.
    # 이 블록은 서버 이름만 지정하며, URL이나 토큰은 절대 포함하지 않습니다.
    - role: system
      content:
        - type: tool_addition
          tool:
            type: tool_definition
            definition:
              type: mcp_toolset
              mcp_server_name: calendar
  YAML
  ```

  ```python Python
  client = anthropic.Anthropic()

  response = client.beta.messages.create(
      model="claude-opus-5-5",
      max_tokens=1024,
      betas=["inline-tools-2026-09-15", "mcp-client-2026-09-15"],
      mcp_servers=[
          {
              "type": "url",
              "url": "https://mcp.example.com/calendar",
              "name": "calendar",
              "authorization_token": "YOUR_TOKEN",
          },
      ],
      tools=[
          {
              "name": "get_weather",
              "description": "Get the current weather for a location.",
              "input_schema": {
                  "type": "object",
                  "properties": {
                      "location": {"type": "string", "description": "City name"},
                  },
                  "required": ["location"],
              },
          },
      ],
      messages=[
          {"role": "user", "content": "What's on my calendar tomorrow?"},
          # 이 시점부터 캘린더 서버의 도구를 사용할 수 있도록 합니다.
          # 이 블록은 서버 이름만 지정하며, URL이나 토큰은 절대 포함하지 않습니다.
          {
              "role": "system",
              "content": [
                  {
                      "type": "tool_addition",
                      "tool": {
                          "type": "tool_definition",
                          "definition": {
                              "type": "mcp_toolset",
                              "mcp_server_name": "calendar",
                          },
                      },
                  },
              ],
          },
      ],
  )

  # 응답은 캘린더 서버에 대한 mcp_tool_listing 블록으로 시작하므로,
  # content[0]을 읽는 대신 각 블록의 type을 확인하세요.
  for block in response.content:
      match block.type:
          case "mcp_tool_listing":
              print(block.mcp_server_name, [tool.name for tool in block.tools])
          case "text":
              print(block.text)
  ```

  ```typescript TypeScript
  const client = new Anthropic();

  const response = await client.beta.messages.create({
    model: "claude-opus-5-5",
    max_tokens: 1024,
    betas: ["inline-tools-2026-09-15", "mcp-client-2026-09-15"],
    mcp_servers: [
      {
        type: "url",
        url: "https://mcp.example.com/calendar",
        name: "calendar",
        authorization_token: "YOUR_TOKEN"
      }
    ],
    tools: [
      {
        name: "get_weather",
        description: "Get the current weather for a location.",
        input_schema: {
          type: "object",
          properties: {
            location: { type: "string", description: "City name" }
          },
          required: ["location"]
        }
      }
    ],
    messages: [
      { role: "user", content: "What's on my calendar tomorrow?" },
      // 이 시점부터 캘린더 서버의 도구를 사용할 수 있도록 합니다.
      // 이 블록은 서버 이름만 지정하며, URL이나 토큰은 절대 포함하지 않습니다.
      {
        role: "system",
        content: [
          {
            type: "tool_addition",
            tool: {
              type: "tool_definition",
              definition: { type: "mcp_toolset", mcp_server_name: "calendar" }
            }
          }
        ]
      }
    ]
  });

  // 응답은 캘린더 서버에 대한 mcp_tool_listing 블록으로 시작하므로,
  // content[0]을 읽는 대신 각 블록의 type을 확인하세요.
  for (const block of response.content) {
    switch (block.type) {
      case "mcp_tool_listing":
        console.log(
          block.mcp_server_name,
          block.tools.map((tool) => tool.name)
        );
        break;
      case "text":
        console.log(block.text);
        break;
    }
  }
  ```

  ```csharp C#
  using Anthropic.Models.Beta;
  using Anthropic.Models.Beta.Messages;
  using Messages = Anthropic.Models.Messages;

  AnthropicClient client = new();

  var response = await client.Beta.Messages.Create(new MessageCreateParams
  {
      Model = Messages::Model.ClaudeOpus5_5,
      MaxTokens = 1024,
      Betas = [AnthropicBeta.InlineTools2026_09_15, AnthropicBeta.McpClient2026_09_15],
      McpServers =
      [
          new BetaRequestMcpServerUrlDefinition
          {
              Url = "https://mcp.example.com/calendar",
              Name = "calendar",
              AuthorizationToken = "YOUR_TOKEN",
          },
      ],
      Tools =
      [
          new BetaTool
          {
              Name = "get_weather",
              Description = "Get the current weather for a location.",
              InputSchema = new InputSchema
              {
                  Properties = new Dictionary<string, JsonElement>
                  {
                      ["location"] = JsonSerializer.SerializeToElement(new { type = "string", description = "City name" }),
                  },
                  Required = ["location"],
              },
          },
      ],
      Messages =
      [
          new() { Role = Role.User, Content = "What's on my calendar tomorrow?" },
          // 이 시점부터 캘린더 서버의 도구를 사용할 수 있도록 합니다.
          // 이 블록은 서버 이름만 지정하며, URL이나 토큰은 절대 포함하지 않습니다.
          new()
          {
              Role = Role.System,
              Content = new(
              [
                  new BetaRequestToolAdditionBlock
                  {
                      Tool = new BetaToolChangeToolDefinitionParam
                      {
                          Definition = new BetaMcpToolset("calendar"),
                      },
                  },
              ]),
          },
      ],
  });

  // 응답은 캘린더 서버에 대한 mcp_tool_listing 블록으로 시작하므로,
  // Content[0]을 읽는 대신 각 블록의 type을 확인하세요.
  foreach (var block in response.Content)
  {
      if (block.TryPickMcpToolListing(out var listing))
      {
          Console.WriteLine($"{listing.McpServerName} {JsonSerializer.Serialize(listing.Tools.Select(tool => tool.Name))}");
      }
      else if (block.TryPickText(out var text))
      {
          Console.WriteLine(text.Text);
      }
  }
  ```

  ```go Go
  client := anthropic.NewClient()

  response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
  	Model:     anthropic.ModelClaudeOpus5_5,
  	MaxTokens: 1024,
  	Betas: []anthropic.AnthropicBeta{
  		anthropic.AnthropicBetaInlineTools2026_09_15,
  		anthropic.AnthropicBetaMCPClient2026_09_15,
  	},
  	MCPServers: []anthropic.BetaRequestMCPServerURLDefinitionParam{
  		{
  			URL:                "https://mcp.example.com/calendar",
  			Name:               "calendar",
  			AuthorizationToken: anthropic.String("YOUR_TOKEN"),
  		},
  	},
  	Tools: []anthropic.BetaToolUnionParam{
  		{OfTool: &anthropic.BetaToolParam{
  			Name:        "get_weather",
  			Description: anthropic.String("Get the current weather for a location."),
  			InputSchema: anthropic.BetaToolInputSchemaParam{
  				Properties: map[string]any{
  					"location": map[string]any{
  						"type":        "string",
  						"description": "City name",
  					},
  				},
  				Required: []string{"location"},
  			},
  		}},
  	},
  	Messages: []anthropic.BetaMessageParam{
  		anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("What's on my calendar tomorrow?")),
  		// 이 시점부터 캘린더 서버의 도구를 사용할 수 있도록 합니다.
  		// 이 블록은 서버 이름만 지정하며, URL이나 토큰은 절대 포함하지 않습니다.
  		{
  			Role: anthropic.BetaMessageParamRoleSystem,
  			Content: []anthropic.BetaContentBlockParamUnion{
  				anthropic.NewBetaToolAdditionBlock(anthropic.BetaToolChangeToolDefinitionParam{
  					Definition: anthropic.BetaToolUnionParam{OfMCPToolset: &anthropic.BetaMCPToolsetParam{
  						MCPServerName: "calendar",
  					}},
  				}),
  			},
  		},
  	},
  })
  if err != nil {
  	log.Fatal(err)
  }

  // 응답은 캘린더 서버에 대한 mcp_tool_listing 블록으로 시작하므로,
  // Content[0]을 읽는 대신 각 블록의 type을 확인하세요.
  for _, block := range response.Content {
  	switch variant := block.AsAny().(type) {
  	case anthropic.BetaMCPToolListingBlock:
  		var toolNames []string
  		for _, tool := range variant.Tools {
  			toolNames = append(toolNames, tool.Name)
  		}
  		fmt.Println(variant.MCPServerName, toolNames)
  	case anthropic.BetaTextBlock:
  		fmt.Println(variant.Text)
  	}
  }
  ```

  ```java Java
  import com.anthropic.models.beta.AnthropicBeta;
  import com.anthropic.models.beta.messages.BetaContentBlockParam;
  import com.anthropic.models.beta.messages.BetaMcpTool;
  import com.anthropic.models.beta.messages.BetaMcpToolset;
  import com.anthropic.models.beta.messages.BetaMessage;
  import com.anthropic.models.beta.messages.BetaRequestMcpServerUrlDefinition;
  import com.anthropic.models.beta.messages.BetaRequestToolAdditionBlock;
  import com.anthropic.models.beta.messages.BetaTool;
  import com.anthropic.models.beta.messages.MessageCreateParams;
  // ...

  void main() {
      AnthropicClient client = AnthropicOkHttpClient.fromEnv();

      BetaTool weatherTool = BetaTool.builder()
          .name("get_weather")
          .description("Get the current weather for a location.")
          .inputSchema(BetaTool.InputSchema.builder()
              .properties(BetaTool.InputSchema.Properties.builder()
                  .putAdditionalProperty("location", JsonValue.from(Map.of(
                      "type", "string",
                      "description", "City name")))
                  .build())
              .addRequired("location")
              .build())
          .build();

      MessageCreateParams params = MessageCreateParams.builder()
          .model(Model.CLAUDE_OPUS_5_5)
          .maxTokens(1024)
          .addBeta(AnthropicBeta.INLINE_TOOLS_2026_09_15)
          .addBeta(AnthropicBeta.MCP_CLIENT_2026_09_15)
          .addMcpServer(BetaRequestMcpServerUrlDefinition.builder()
              .url("https://mcp.example.com/calendar")
              .name("calendar")
              .authorizationToken("YOUR_TOKEN")
              .build())
          .addTool(weatherTool)
          .addUserMessage("What's on my calendar tomorrow?")
          // 이 시점부터 캘린더 서버의 도구를 사용할 수 있도록 합니다.
          // 이 블록은 서버 이름만 지정하며, URL이나 토큰은 절대 포함하지 않습니다.
          .addSystemMessageOfBetaContentBlockParams(List.of(
              BetaContentBlockParam.ofToolAddition(BetaRequestToolAdditionBlock.builder()
                  .definitionTool(BetaMcpToolset.builder()
                      .mcpServerName("calendar")
                      .build())
                  .build())))
          .build();

      BetaMessage response = client.beta().messages().create(params);

      // 응답은 캘린더 서버에 대한 mcp_tool_listing 블록으로 시작하므로,
      // 첫 번째 블록을 읽는 대신 각 블록의 type을 확인하세요.
      for (var block : response.content()) {
          switch (block.type().value()) {
              case MCP_TOOL_LISTING -> {
                  var listing = block.asMcpToolListing();
                  var toolNames = listing.tools().stream().map(BetaMcpTool::name).toList();
                  IO.println(listing.mcpServerName() + " " + toolNames);
              }
              case TEXT -> IO.println(block.asText().text());
          }
      }
  }
  ```

  ```php PHP
  use Anthropic\Beta\AnthropicBeta;
  use Anthropic\Beta\Messages\BetaMCPTool;
  use Anthropic\Beta\Messages\BetaMCPToolListingBlock;
  use Anthropic\Beta\Messages\BetaTextBlock;
  // ...

  $client = new Client();

  $response = $client->beta->messages->create(
      model: Model::CLAUDE_OPUS_5_5,
      maxTokens: 1024,
      betas: [
          AnthropicBeta::INLINE_TOOLS_2026_09_15,
          AnthropicBeta::MCP_CLIENT_2026_09_15,
      ],
      mcpServers: [
          [
              'type' => 'url',
              'url' => 'https://mcp.example.com/calendar',
              'name' => 'calendar',
              'authorization_token' => 'YOUR_TOKEN',
          ],
      ],
      tools: [
          [
              'name' => 'get_weather',
              'description' => 'Get the current weather for a location.',
              'input_schema' => [
                  'type' => 'object',
                  'properties' => [
                      'location' => [
                          'type' => 'string',
                          'description' => 'City name',
                      ],
                  ],
                  'required' => ['location'],
              ],
          ],
      ],
      messages: [
          ['role' => 'user', 'content' => "What's on my calendar tomorrow?"],
          // 이 시점부터 캘린더 서버의 도구를 사용할 수 있도록 합니다.
          // 이 블록은 서버 이름만 지정하며, URL이나 토큰은 절대 포함하지 않습니다.
          [
              'role' => 'system',
              'content' => [
                  [
                      'type' => 'tool_addition',
                      'tool' => [
                          'type' => 'tool_definition',
                          'definition' => [
                              'type' => 'mcp_toolset',
                              'mcp_server_name' => 'calendar',
                          ],
                      ],
                  ],
              ],
          ],
      ],
  );

  // 응답은 캘린더 서버에 대한 mcp_tool_listing 블록으로 시작하므로,
  // content[0]을 읽는 대신 각 블록의 type을 확인하세요.
  foreach ($response->content as $block) {
      switch (true) {
          case $block instanceof BetaMCPToolListingBlock:
              $toolNames = array_map(fn (BetaMCPTool $tool) => $tool->name, $block->tools);
              echo $block->mcpServerName, ' ', json_encode($toolNames), PHP_EOL;
              break;
          case $block instanceof BetaTextBlock:
              echo $block->text, PHP_EOL;
              break;
      }
  }
  ```

  ```ruby Ruby
  client = Anthropic::Client.new

  response = client.beta.messages.create(
    model: Anthropic::Model::CLAUDE_OPUS_5_5,
    max_tokens: 1024,
    betas: [
      Anthropic::AnthropicBeta::INLINE_TOOLS_2026_09_15,
      Anthropic::AnthropicBeta::MCP_CLIENT_2026_09_15
    ],
    mcp_servers: [
      {
        type: "url",
        url: "https://mcp.example.com/calendar",
        name: "calendar",
        authorization_token: "YOUR_TOKEN"
      }
    ],
    tools: [
      {
        name: "get_weather",
        description: "Get the current weather for a location.",
        input_schema: {
          type: "object",
          properties: {
            location: { type: "string", description: "City name" }
          },
          required: ["location"]
        }
      }
    ],
    messages: [
      { role: "user", content: "What's on my calendar tomorrow?" },
      # 이 시점부터 캘린더 서버의 도구를 사용할 수 있도록 합니다.
      # 이 블록은 서버 이름만 지정하며, URL이나 토큰은 절대 포함하지 않습니다.
      {
        role: "system",
        content: [
          {
            type: "tool_addition",
            tool: {
              type: "tool_definition",
              definition: { type: "mcp_toolset", mcp_server_name: "calendar" }
            }
          }
        ]
      }
    ]
  )

  # 응답은 캘린더 서버에 대한 mcp_tool_listing 블록으로 시작하므로,
  # content[0]을 읽는 대신 각 블록의 type을 확인하세요.
  response.content.each do |block|
    case block
    when Anthropic::Beta::BetaMCPToolListingBlock
      puts "#{block.mcp_server_name} #{block.tools.map(&:name)}"
    when Anthropic::Beta::BetaTextBlock
      puts block.text
    end
  end
  ```
</CodeGroup>

`mcp-client-2026-09-15`를 사용하면, API가 서버의 도구 목록을 가져온 응답은 가져온 서버마다 하나씩 `mcp_tool_listing` 블록으로 시작합니다. 코드에서 `content[0]`을 읽는 경우 이러한 블록을 건너뛰세요. 이 블록을 포함하여 어시스턴트 메시지를 변경하지 않고 그대로 다시 보내고, 이 블록을 포함하는 모든 요청에 `mcp-client-2026-09-15`를 계속 보내세요. 그러면 이후 요청은 서버에 다시 요청하는 대신 기록된 목록을 사용합니다. 도구 세트를 직접 고정하려면 [MCP 서버의 도구 목록 고정하기](https://platform.claude.com/docs/ko/agents-and-tools/mcp-connector#pin-mcp-tool-list)에 설명된 대로 해당 목록을 `mcp_toolset`의 `tools` 필드에 복사하세요.

`mcp-client-2026-09-15`는 `mcp-client-2025-11-20`의 모든 기능을 포함하므로 둘 다 보낼 필요가 없습니다. 이러한 기능은 Claude API에서 사용할 수 있습니다. MCP 커넥터를 사용하는 요청에는 해당 [데이터 보존](https://platform.claude.com/docs/ko/agents-and-tools/mcp-connector#data-retention) 조건이 그대로 적용됩니다.

## 대화 중간 시스템 메시지를 사용해야 하는 경우

[프롬프트 캐싱](https://platform.claude.com/docs/ko/build-with-claude/prompt-caching)은 요청 프리픽스를 `tools`, `system`, `messages` 순서로 해시합니다. 캐시 적중이 발생하려면 프리픽스가 캐시 중단점까지 최근 요청과 바이트 단위로 정확히 일치해야 합니다.

이 순서는 최상위 `system` 필드가 해시되는 프리픽스의 거의 맨 앞에 위치한다는 것을 의미합니다. 문장 하나를 추가하는 것조차 포함하여 어떤 변경이든 다른 해시를 생성하며, 요청은 시스템 프롬프트와 그 뒤의 모든 캐시된 메시지에 대해 캐시를 놓치게 됩니다.

대화 중간 시스템 메시지를 사용하면 대신 메시지 기록의 **끝**에 지침을 추가할 수 있습니다. 새 지침 이전의 모든 것은 변경되지 않으므로 기존 캐시 항목이 여전히 일치하며, 새 메시지만 새로운 입력으로 처리됩니다.

이것이 중요한 몇 가지 상황은 다음과 같습니다.

* **세션 중간의 정책 또는 페르소나 변경.** 긴 에이전트 세션에서 수십 개의 캐시된 턴 이후에 새로운 제약("지금부터 모든 SQL을 매개변수화된 쿼리로 작성하세요")이 필요합니다. 이를 최상위 `system` 필드에 추가하면 전체 기록이 다시 처리됩니다.
* **권위를 가져야 하는 턴별 컨텍스트.** 최신성 메모, 세션 마감 시한 또는 도구 가용성 변경을 시스템 수준의 가중치로 주입하고 싶지만, 캐시된 프리픽스에 두기에는 너무 자주 변경됩니다.
* **쌓이지 않아야 하는 턴별 리마인더.** 하네스가 각 도구 결과 배치 후에 모델에 넛지를 주고("독립적인 읽기는 함께 요청하세요", "사용자가 한동안 응답을 받지 못했습니다") 모델이 최신 사본만 보기를 원합니다. [턴 범위 시스템 메시지](https://platform.claude.com/docs/ko/build-with-claude/mid-conversation-system-messages#turn-scoped-system-messages)는 한 턴 동안만 렌더링되고 그 이후에는 비용이 들지 않으며, 기록에서 아무것도 삭제하지 않습니다.
* **애플리케이션이 관찰하는 상태 변경.** 애플리케이션이 Claude가 운영자 수준의 사실로 취급해야 하는 무언가를 감지합니다. 디스크의 파일이 변경되었거나, 사용자가 자동 승인 설정을 전환했거나, 사용 가능한 도구가 변경되었거나, 남은 토큰 예산이 임계값 아래로 떨어진 경우 등입니다.
* **에이전트 루프를 중단시키지 않아야 하는 사용자 입력.** Claude가 이전 요청에 대한 도구를 아직 실행하는 동안 사용자가 후속 메시지를 입력한 경우입니다. 다음 도구 결과 이후에 이를 시스템 메시지로 전달하면, Claude는 새 입력을 전환해야 할 새로운 요청으로 취급하는 대신 이미 진행 중인 작업에 통합할 수 있습니다. Claude Sonnet 5.5 및 Claude Haiku 5.5에서는 대신 같은 `user` 메시지의 마지막 `tool_result` 뒤에 사용자의 말을 `text` 블록으로 추가하세요. [도구 결과 이후 배치](https://platform.claude.com/docs/ko/build-with-claude/mid-conversation-system-messages#placement-after-tool-results)를 참조하세요.
* **상시 권한을 부여하는 모드 전환.** 세션 수준 모드는 대화 중간 시스템 메시지를 사용하여 멀티에이전트 워크플로 자동 실행과 같은 비용이 큰 기능에 대한 상시 동의를 부여할 수 있으며, 몇 턴마다 짧은 리프레셔를 보내고 모드가 꺼질 때 종료 알림을 보낼 수 있습니다. 실제 예시는 [오케스트레이션 모드 구축](https://platform.claude.com/docs/ko/build-with-claude/mid-conversation-effort-example)을 참조하세요.

이 모든 경우에 지침을 일반 `user` 메시지에 넣을 수도 있으며, Claude는 사용자 턴에 도착하는 지침도 따릅니다. 차이점은 우선순위입니다. `user` 메시지는 최종 사용자로부터 온 것으로 취급되는 반면, `system` 메시지는 애플리케이션 운영자인 여러분으로부터 온 것으로 취급됩니다. 둘이 충돌할 때 시스템 지침이 우선하므로, 최종 사용자가 다른 것을 요청하더라도 유지되어야 하는 운영자 수준의 사실과 제약에는 `system` 역할을 사용하세요. 대화 중간 시스템 메시지는 최상위 `system` 필드를 편집하는 캐시 미스 비용을 지불하지 않으면서 운영자 수준의 우선순위를 유지합니다.

## 작동 방식

`messages` 배열에 `"role": "system"`이 있는 메시지를 추가하세요. `content`에는 `user` 또는 `assistant` 턴과 마찬가지로 일반 문자열이나 콘텐츠 블록을 사용합니다. 지침은 대화의 해당 지점부터 적용됩니다. 지침이 충돌하는 경우 나중의 시스템 메시지가 이전 시스템 메시지보다 우선하며, 대화 중간 시스템 메시지는 그 뒤에 오는 턴에 대해 최상위 `system` 필드보다 우선합니다.

전체 대화에 적용되어야 하는 지침에는 여전히 최상위 `system` 필드를 설정할 수 있습니다. 대화 중간 시스템 메시지는 나중에야 관련성을 갖게 되는 지침이나 캐시된 프리픽스를 무효화하지 않고 추가하려는 지침에 사용하세요.

`role: "system"` 메시지는 대화 도중에 [effort](https://platform.claude.com/docs/ko/build-with-claude/effort) 수준을 변경하기 위해 `output_config.effort`를 담을 수도 있습니다. 이는 Claude API 및 Google Cloud의 Claude Fable 5.1, Claude Mythos 5.1, Claude Opus 5.5, Claude Opus 5, Claude Sonnet 5.5, Claude Haiku 5.5에서 베타로 제공되며, `mid-conversation-output-config-2026-07-01` 베타 헤더가 필요합니다. Amazon Bedrock에서는 Claude Fable 5.1, Claude Mythos 5.1, Claude Opus 5.5에서 동일한 베타 값으로 베타 제공됩니다. Claude Opus 5는 Amazon Bedrock에서 이를 지원하지 않습니다. [메시지별 effort](https://platform.claude.com/docs/ko/build-with-claude/effort#change-effort-mid-conversation-beta)를 참조하세요.

<CodeGroup>
  ```bash cURL
  curl https://api.anthropic.com/v1/messages \
    -H "content-type: application/json" \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -d '{
      "model": "claude-opus-5-5",
      "max_tokens": 1024,
      "cache_control": {"type": "ephemeral"},
      "system": "You are a code review assistant. Be concise.",
      "messages": [
        {
          "role": "user",
          "content": "Review process() in utils.py for performance issues."
        },
        {
          "role": "assistant",
          "content": "The list comprehension is fine for small inputs. For large inputs, consider a generator to avoid materializing the full list."
        },
        {
          "role": "user",
          "content": "Now review the calling code that invokes process()."
        },
        {
          "role": "system",
          "content": "From now on, every suggestion must include explicit type annotations."
        }
      ]
    }'
  ```

  ```bash CLI
  ant messages create --transform 'content.#(type=="text").text' --raw-output <<'YAML'
  model: claude-opus-5-5
  max_tokens: 1024
  cache_control:
    type: ephemeral
  system: You are a code review assistant. Be concise.
  messages:
    - role: user
      content: Review process() in utils.py for performance issues.
    - role: assistant
      content: >-
        The list comprehension is fine for small inputs. For large inputs,
        consider a generator to avoid materializing the full list.
    - role: user
      content: Now review the calling code that invokes process().
    - role: system
      content: From now on, every suggestion must include explicit type annotations.
  YAML
  ```

  ```python Python
  client = anthropic.Anthropic()

  response = client.messages.create(
      model="claude-opus-5-5",
      max_tokens=1024,
      # 자동 prompt caching(프롬프트 캐싱): 각 요청은 지금까지의 대화를 캐시하고,
      # 다음 요청은 변경되지 않은 접두사를 캐시에서 읽습니다.
      cache_control={"type": "ephemeral"},
      system="You are a code review assistant. Be concise.",
      messages=[
          {
              "role": "user",
              "content": "Review process() in utils.py for performance issues.",
          },
          {
              "role": "assistant",
              "content": "The list comprehension is fine for small inputs. For large inputs, consider a generator to avoid materializing the full list.",
          },
          {
              "role": "user",
              "content": "Now review the calling code that invokes process().",
          },
          # 리뷰어는 세션 도중에 모든 제안이 팀의 엄격한 타입 정책도
          # 통과해야 한다는 것을 깨닫습니다. 여기에 지시를 추가하면
          # 이전 턴이 바이트 단위로 동일하게 유지되므로,
          # 이전 요청이 캐시한 접두사를 여전히 캐시에서 읽습니다.
          {
              "role": "system",
              "content": "From now on, every suggestion must include explicit type annotations.",
          },
      ],
  )

  for block in response.content:
      if block.type == "text":
          print(block.text)
  ```

  ```typescript TypeScript
  const client = new Anthropic();

  const response = await client.messages.create({
    model: "claude-opus-5-5",
    max_tokens: 1024,
    // 자동 prompt caching(프롬프트 캐싱): 각 요청은 지금까지의 대화를 캐시하고,
    // 다음 요청은 변경되지 않은 접두사(prefix)를 캐시에서 읽습니다.
    cache_control: { type: "ephemeral" },
    system: "You are a code review assistant. Be concise.",
    messages: [
      {
        role: "user",
        content: "Review process() in utils.py for performance issues."
      },
      {
        role: "assistant",
        content:
          "The list comprehension is fine for small inputs. For large inputs, consider a generator to avoid materializing the full list."
      },
      {
        role: "user",
        content: "Now review the calling code that invokes process()."
      },
      // 리뷰어는 세션 도중에 모든 제안이 팀의 엄격한 타입 지정 정책도
      // 통과해야 한다는 것을 깨닫습니다. 이 지시를 여기에 덧붙이면
      // 이전 턴이 바이트 단위로 동일하게 유지되므로, 이전 요청이 캐시한 접두사를
      // 여전히 캐시에서 읽을 수 있습니다.
      {
        role: "system",
        content: "From now on, every suggestion must include explicit type annotations."
      }
    ]
  });

  const textBlock = response.content.find(
    (block): block is Anthropic.TextBlock => block.type === "text"
  );
  console.log(textBlock?.text);
  ```

  ```csharp C#
  AnthropicClient client = new();

  var parameters = new MessageCreateParams
  {
      Model = Model.ClaudeOpus5_5,
      MaxTokens = 1024,
      // 자동 "prompt caching"(프롬프트 캐싱): 각 요청은 지금까지의 대화를 캐시하며,
      // 다음 요청은 변경되지 않은 접두사를 캐시에서 읽습니다.
      CacheControl = new CacheControlEphemeral(),
      System = "You are a code review assistant. Be concise.",
      Messages =
      [
          new()
          {
              Role = Role.User,
              Content = "Review process() in utils.py for performance issues."
          },
          new()
          {
              Role = Role.Assistant,
              Content = "The list comprehension is fine for small inputs. For large inputs, consider a generator to avoid materializing the full list."
          },
          new()
          {
              Role = Role.User,
              Content = "Now review the calling code that invokes process()."
          },
          // 리뷰어는 세션 도중에 모든 제안이 팀의 엄격한 타입 정책도
          // 통과해야 한다는 것을 깨닫습니다. 여기에 지시를 추가하면
          // 이전 턴이 바이트 단위로 동일하게 유지되므로, 이전 요청에서 캐시된 접두사를
          // 여전히 캐시에서 읽을 수 있습니다.
          new()
          {
              Role = Role.System,
              Content = "From now on, every suggestion must include explicit type annotations."
          }
      ]
  };

  var response = await client.Messages.Create(parameters);
  Console.WriteLine(response);
  ```

  ```go Go
  client := anthropic.NewClient()

  response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
  	Model:     anthropic.ModelClaudeOpus5_5,
  	MaxTokens: 1024,
  	// 자동 prompt caching(프롬프트 캐싱): 각 요청은 지금까지의 대화를 캐시하고,
  	// 다음 요청은 변경되지 않은 접두사(prefix)를 캐시에서 읽습니다.
  	CacheControl: anthropic.NewCacheControlEphemeralParam(),
  	System: []anthropic.TextBlockParam{
  		{Text: "You are a code review assistant. Be concise."},
  	},
  	Messages: []anthropic.MessageParam{
  		anthropic.NewUserMessage(anthropic.NewTextBlock("Review process() in utils.py for performance issues.")),
  		anthropic.NewAssistantMessage(anthropic.NewTextBlock("The list comprehension is fine for small inputs. For large inputs, consider a generator to avoid materializing the full list.")),
  		anthropic.NewUserMessage(anthropic.NewTextBlock("Now review the calling code that invokes process().")),
  		// 리뷰어는 세션 도중에 모든 제안이 팀의 엄격한 타입 정책도
  		// 통과해야 한다는 것을 깨닫습니다. 지시를 여기에 추가하면
  		// 이전 턴이 바이트 단위로 동일하게 유지되므로, 이전 요청에서 캐시된
  		// 접두사를 여전히 캐시에서 읽습니다.
  		{
  			Role: anthropic.MessageParamRoleSystem,
  			Content: []anthropic.ContentBlockParamUnion{
  				anthropic.NewTextBlock("From now on, every suggestion must include explicit type annotations."),
  			},
  		},
  	},
  })
  if err != nil {
  	log.Fatal(err)
  }
  for _, block := range response.Content {
  	if textBlock, ok := block.AsAny().(anthropic.TextBlock); ok {
  		fmt.Println(textBlock.Text)
  	}
  }
  ```

  ```java Java
  import com.anthropic.models.messages.CacheControlEphemeral;
  // ...
  import com.anthropic.models.messages.MessageParam;
  // ...
      AnthropicClient client = AnthropicOkHttpClient.fromEnv();

      MessageCreateParams params = MessageCreateParams.builder()
          .model(Model.CLAUDE_OPUS_5_5)
          .maxTokens(1024)
          // 자동 prompt caching(프롬프트 캐싱): 각 요청은 지금까지의 대화를 캐시하고,
          // 다음 요청은 변경되지 않은 접두사를 캐시에서 읽습니다.
          .cacheControl(CacheControlEphemeral.builder().build())
          .system("You are a code review assistant. Be concise.")
          .addUserMessage("Review process() in utils.py for performance issues.")
          .addAssistantMessage("The list comprehension is fine for small inputs. For large inputs, consider a generator to avoid materializing the full list.")
          .addUserMessage("Now review the calling code that invokes process().")
          // 리뷰어는 세션 도중에 모든 제안이 팀의 엄격한 타입 정책도
          // 통과해야 한다는 것을 깨닫습니다. 이 지시를 여기에 추가하면
          // 이전 턴이 바이트 단위로 동일하게 유지되므로, 이전 요청이 캐시한 접두사를
          // 여전히 캐시에서 읽을 수 있습니다.
          .addMessage(MessageParam.builder()
              .role(MessageParam.Role.SYSTEM)
              .content("From now on, every suggestion must include explicit type annotations.")
              .build())
          .build();

      Message response = client.messages().create(params);
      response.content().stream()
          .flatMap(block -> block.text().stream())
          .forEach(textBlock -> IO.println(textBlock.text()));
  ```

  ```php PHP
  use Anthropic\Messages\CacheControlEphemeral;
  // ...
  $client = new Client();

  $response = $client->messages->create(
      maxTokens: 1024,
      messages: [
          ['role' => 'user', 'content' => 'Review process() in utils.py for performance issues.'],
          ['role' => 'assistant', 'content' => 'The list comprehension is fine for small inputs. For large inputs, consider a generator to avoid materializing the full list.'],
          ['role' => 'user', 'content' => 'Now review the calling code that invokes process().'],
          // 리뷰어는 세션 도중에 모든 제안이 팀의 엄격한 타입 정책도
          // 통과해야 한다는 것을 깨닫습니다. 지시를 여기에 추가하면 이전 턴이
          // 바이트 단위로 동일하게 유지되므로, 이전 요청에서 캐시된 prefix를
          // 여전히 캐시에서 읽을 수 있습니다.
          ['role' => 'system', 'content' => 'From now on, every suggestion must include explicit type annotations.']
      ],
      model: 'claude-opus-5-5',
      // 자동 prompt caching(프롬프트 캐싱): 각 요청은 지금까지의 대화를 캐시하고,
      // 다음 요청은 변경되지 않은 prefix를 캐시에서 읽습니다.
      cacheControl: CacheControlEphemeral::with(),
      system: 'You are a code review assistant. Be concise.',
  );

  foreach ($response->content as $block) {
      if ($block->type === 'text') {
          echo $block->text, PHP_EOL;
      }
  }
  ```

  ```ruby Ruby
  client = Anthropic::Client.new

  response = client.messages.create(
    model: "claude-opus-5-5",
    max_tokens: 1024,
    # 자동 prompt caching(프롬프트 캐싱): 각 요청은 지금까지의 대화를 캐시하고,
    # 다음 요청은 변경되지 않은 접두사를 캐시에서 읽습니다.
    cache_control: { type: "ephemeral" },
    system: "You are a code review assistant. Be concise.",
    messages: [
      { role: "user", content: "Review process() in utils.py for performance issues." },
      { role: "assistant", content: "The list comprehension is fine for small inputs. For large inputs, consider a generator to avoid materializing the full list." },
      { role: "user", content: "Now review the calling code that invokes process()." },
      # 리뷰어는 세션 도중에 모든 제안이 팀의 엄격한 타입 정책도
      # 통과해야 한다는 것을 깨닫습니다. 여기에 지시 사항을 덧붙이면
      # 이전 턴이 바이트 단위로 동일하게 유지되므로, 이전 요청이 캐시한 접두사를
      # 여전히 캐시에서 읽을 수 있습니다.
      { role: "system", content: "From now on, every suggestion must include explicit type annotations." }
    ]
  )

  response.content.each do |block|
    puts block.text if block.type == :text
  end
  ```
</CodeGroup>

이 예시는 최상위 `cache_control` 필드로 [자동 캐싱](https://platform.claude.com/docs/ko/build-with-claude/prompt-caching#automatic-caching)을 활성화합니다. 프롬프트 캐싱은 옵트인 방식입니다. 요청에 `cache_control` 필드(자동 또는 [명시적 중단점](https://platform.claude.com/docs/ko/build-with-claude/prompt-caching#explicit-cache-breakpoints))가 없으면 아무것도 캐시되지 않으며 모든 요청이 전체 대화에 대해 일반 입력 토큰 가격을 지불합니다. 캐싱이 활성화된 경우 시스템 메시지를 추가해도 이미 캐시된 턴은 변경되지 않으므로, 새 지침을 포함하는 요청도 해당 턴을 다시 처리하는 대신 캐시에서 읽어옵니다. 캐싱을 위해서는 대화가 [최소 캐시 가능 프롬프트 길이](https://platform.claude.com/docs/ko/build-with-claude/prompt-caching#cache-limitations)를 충족해야 합니다. 이 예시처럼 짧은 대화는 그 기준에 미치지 못하므로, 대화가 길어질 때까지 `cache_creation_input_tokens`와 `cache_read_input_tokens`는 0으로 유지됩니다.

콘텐츠(`text`, `tool_addition` 또는 `tool_removal` 블록)를 포함하는 대화 중간 시스템 메시지는 `user` 턴(또는 서버 도구 결과로 끝나는 `assistant` 턴) 바로 뒤에 와야 하며, `messages`의 마지막 항목이거나 바로 뒤에 `assistant` 턴이 와야 합니다. `tool_result` 블록을 포함하는 `user` 메시지도 해당됩니다. 에이전트 루프에서는 Claude의 다음 턴 이전, 도구 결과 바로 뒤에 시스템 메시지를 배치할 수 있습니다. `assistant`의 `tool_use` 블록과 이에 응답하는 `tool_result` 사이를 포함하여, 콘텐츠를 포함하는 메시지를 그 외의 위치에 두면 400 오류가 반환됩니다. [`output_config.effort`](https://platform.claude.com/docs/ko/build-with-claude/effort#change-effort-mid-conversation-beta)만 설정하고 `content`가 비어 있는 메시지는 `messages`의 어디에든 허용됩니다. 연속된 `system` 메시지는 함께 판단되므로, 콘텐츠를 포함하는 메시지 옆에 있는 effort 전용 메시지는 콘텐츠에 대한 규칙을 따릅니다.

### 도구 결과 이후 배치

[에이전트 루프](https://platform.claude.com/docs/ko/agents-and-tools/tool-use/overview)에서 시스템 메시지는 도구 결과를 전달하는 `user` 메시지 뒤에 옵니다. 대부분의 모델에서 이 위치는 Claude가 작업하는 동안 사용자가 입력한 내용을 애플리케이션이 전달할 수 있는 위치이기도 하므로, 턴을 다시 시작하지 않고도 새 컨텍스트가 반영됩니다(Claude Sonnet 5.5 및 Claude Haiku 5.5의 경우 이 섹션의 끝부분을 참조하세요):

```json
[
  { "role": "user", "content": "Run the test suite and fix any failures." },
  {
    "role": "assistant",
    "content": [{ "type": "tool_use", "id": "toolu_01", "name": "run_tests", "input": {} }]
  },
  {
    "role": "user",
    "content": [
      { "type": "tool_result", "tool_use_id": "toolu_01", "content": "12 passed, 0 failed" }
    ]
  },
  {
    "role": "system",
    "content": "The user sent the following message while you were working: also update the changelog before you finish."
  }
]
```

시스템 콘텐츠는 사용자를 무시하는 명령이 아니라 컨텍스트로 표현하세요. 사실을 진술하고("사용자로부터 새 입력이 도착했습니다: X", "남은 토큰 예산은 이제 Y입니다") Claude가 이에 따라 행동하도록 하세요. Claude는 사용자에게 불리하게 작용하는 것처럼 보이는 지침에 저항하도록 훈련되어 있으며, 이 보호는 시스템 역할에도 여전히 적용됩니다. 따라서 "사용자가 말한 것을 무시하세요"와 같은 표현은 무엇이 변경되었는지 진술하는 것보다 효과가 떨어집니다.

이 패턴은 대화 자체의 최종 사용자로부터 온 입력을 전달하기 위한 것입니다. 도구 출력, 검색된 문서 또는 기타 제3자 콘텐츠를 전달하는 데 사용하지 마세요. 그러한 콘텐츠는 `tool_result` 블록에 유지하세요([제한 사항](https://platform.claude.com/docs/ko/build-with-claude/mid-conversation-system-messages#limitations) 참조).

Claude Sonnet 5.5 및 Claude Haiku 5.5에서는 대신 사용자 턴에서 사용자의 말을 전달하세요. 이 모델들은 도구 결과를 통한 "prompt injection"(프롬프트 인젝션)에 저항하도록 학습되었으므로, 도구 결과 바로 뒤에 배치된 시스템 메시지를 신뢰할 수 없는 텍스트로 취급하여 무시할 수 있습니다. 같은 `user` 메시지의 마지막 `tool_result` 뒤에 사용자의 말을 `text` 블록으로 추가하세요. 리마인더와 같은 하네스 알림은 해당 `user` 메시지 뒤의 별도 대화 중간 시스템 메시지에 두고, 알림과 사용자의 말을 절대 같은 블록에 넣지 마세요:

```json
[
  { "role": "user", "content": "Run the test suite and fix any failures." },
  {
    "role": "assistant",
    "content": [{ "type": "tool_use", "id": "toolu_01", "name": "run_tests", "input": {} }]
  },
  {
    "role": "user",
    "content": [
      { "type": "tool_result", "tool_use_id": "toolu_01", "content": "12 passed, 0 failed" },
      { "type": "text", "text": "Also update the changelog before you finish." }
    ]
  }
]
```

이 동작에 대한 자세한 내용은 [Claude Sonnet 5.5 프롬프팅](https://platform.claude.com/docs/ko/build-with-claude/prompt-engineering/prompting-claude-sonnet-5-5#mid-turn-user-messages-and-task-budgets) 및 [Claude Haiku 5.5 프롬프팅](https://platform.claude.com/docs/ko/build-with-claude/prompt-engineering/prompting-claude-haiku-5-5#mid-turn-user-messages)을 참조하세요.

### 턴 범위 시스템 메시지

`role: "system"` 메시지의 범위를 현재 턴으로 한정하려면 `clear_at` 필드를 설정하세요. 이 필드는 두 가지 값 중 하나를 받습니다:

* `"never"`(기본값): 메시지를 포함하는 모든 요청에서 해당 위치에 메시지가 렌더링됩니다. 필드를 생략해도 동일합니다.
* `"next_user_message"`: 메시지가 **턴 범위**가 됩니다. 해당 텍스트는 `messages`에서 그 뒤에 `role: "user"` 메시지가 오지 않는 동안에만 렌더링됩니다. 여기서는 `tool_result` 블록만 포함하는 사용자 메시지도 사용자 메시지로 간주됩니다. 이후 사용자 메시지가 존재하게 되면 메시지는 **지워집니다**. 배열에는 남아 있지만 해당 요청과 이후의 모든 요청에서 아무것도 렌더링하지 않으며 입력 토큰 비용도 발생하지 않습니다.

턴 범위 시스템 메시지는 베타 기능입니다. [베타 헤더](https://platform.claude.com/docs/ko/api/beta-headers) `mid-conversation-system-clear-at-2026-08-21`을 포함하세요. 이 헤더가 없으면 `clear_at`은 알 수 없는 필드로 거부됩니다.

```json
{
  "role": "system",
  "clear_at": "next_user_message",
  "content": "First privately list what you need next; then request every item that doesn't depend on another's result in this one response."
}
```

주요 용도는 도구 루프에서의 턴별 리마인더입니다. 모델이 리마인더를 보기를 원할 때마다 `tool_result` 메시지 뒤에 리마인더를 추가하고, 이전의 모든 사본은 그대로 두세요. 모델은 마지막 사용자 메시지 이후에 오는 사본만 보므로 리마인더가 누적되지 않습니다. `messages`의 이전 내용은 아무것도 변경되지 않으므로 [프롬프트 캐시](https://platform.claude.com/docs/ko/build-with-claude/prompt-caching)가 계속 일치합니다. Claude Fable 5.1, Claude Opus 5.5, Claude Sonnet 5.5, Claude Haiku 5.5에서는 이를 통해 이후의 [thinking 블록도 유효하게 유지됩니다](https://platform.claude.com/docs/ko/build-with-claude/thinking#preserved-in-conversation). 이전 리마인더를 삭제하면 해당 블록 이전의 대화가 변경되어 대화 검사에 실패하지만, 지워진 메시지는 배열에 남아 있어 해당 대화를 변경하지 않습니다.

다음 요청은 에이전트 루프의 이후 단계입니다. `messages[3]`은 배열의 마지막 메시지였던 이전 요청에서 렌더링되었습니다. `messages[5]`(이후 사용자 메시지)가 존재하게 되면 `messages[3]`은 지워집니다. 지워진 메시지는 배열에 남아 있으므로 `messages[4]`의 사고 블록 이전의 대화는 변경되지 않지만, 모델은 더 이상 해당 텍스트를 보지 않습니다. `messages[6]`과 `messages[7]`은 모두 순서대로 렌더링됩니다.

```json
{
  "model": "claude-fable-5-1",
  "max_tokens": 16000,
  "messages": [
    { "role": "user", "content": "Fix the failing test." },
    {
      "role": "assistant",
      "content": [
        { "type": "thinking", "thinking": "", "signature": "..." },
        {
          "type": "tool_use",
          "id": "toolu_01",
          "name": "read_file",
          "input": { "path": "test_auth.py" }
        }
      ]
    },
    {
      "role": "user",
      "content": [{ "type": "tool_result", "tool_use_id": "toolu_01", "content": "..." }]
    },
    {
      "role": "system",
      "clear_at": "next_user_message",
      "content": "Request independent reads in one turn."
    },
    {
      "role": "assistant",
      "content": [
        { "type": "thinking", "thinking": "", "signature": "..." },
        {
          "type": "tool_use",
          "id": "toolu_02",
          "name": "read_file",
          "input": { "path": "auth.py" }
        },
        {
          "type": "tool_use",
          "id": "toolu_03",
          "name": "read_file",
          "input": { "path": "tokens.py" }
        }
      ]
    },
    {
      "role": "user",
      "content": [
        { "type": "tool_result", "tool_use_id": "toolu_02", "content": "..." },
        {
          "type": "tool_result",
          "tool_use_id": "toolu_03",
          "content": "...",
          "cache_control": { "type": "ephemeral" }
        }
      ]
    },
    {
      "role": "system",
      "clear_at": "next_user_message",
      "content": "Request independent reads in one turn."
    },
    {
      "role": "system",
      "clear_at": "next_user_message",
      "content": "The shell exited with status 137."
    }
  ]
}
```

턴 범위 메시지에 대한 규칙:

* **지워진 메시지는 그대로 다시 보내세요.** 지워진 메시지도 여전히 대화 기록의 일부입니다. 현재 상태(새 토큰 수, 타임스탬프)로 다시 작성하거나, 불필요하다고 삭제하거나, `clear_at` 값을 변경하는 것은 이전 메시지를 편집하는 것입니다. 그 지점부터 프롬프트 캐시 미스가 발생하며, Claude Fable 5.1, Claude Opus 5.5, Claude Sonnet 5.5, Claude Haiku 5.5에서는 그 이후에 생성된 모든 thinking 블록이 [대화 검사](https://platform.claude.com/docs/ko/build-with-claude/thinking#preserved-in-conversation)에 실패합니다.
* **텍스트만 사용할 수 있습니다.** `content`는 하나 이상의 `text` 블록(또는 문자열)입니다. 턴 범위 메시지에서 `tool_addition` 및 `tool_removal` 블록은 400 오류를 반환하며, `output_config`도 마찬가지입니다. 이러한 항목에는 `clear_at`이 없는 별도의 `role: "system"` 메시지를 사용하세요.
* **블록에 `cache_control`을 넣을 수 없습니다.** 지워진 메시지는 캐시 키의 일부가 되지 않으므로, 그 위에 둔 중단점은 절대 일치할 수 없습니다. 예시처럼 대신 앞선 사용자 턴의 마지막 블록에 중단점을 두세요. 최상위 [자동 캐싱](https://platform.claude.com/docs/ko/build-with-claude/prompt-caching#automatic-caching) 필드는 중단점을 선택할 때 턴 범위 메시지를 건너뜁니다. 메시지를 지우는 요청에서 재사용 가능한 캐시된 접두사는 그 앞의 사용자 턴에서 끝나므로, 해당 메시지와 새 사용자 메시지 사이의 어시스턴트 턴 하나만 다시 처리됩니다.
* **지워졌는지 여부와 관계없이 배치 규칙은 여전히 적용됩니다.** 턴 범위 메시지는 다른 대화 중간 시스템 메시지와 마찬가지로 `user` 턴(또는 서버 도구 결과로 끝나는 `assistant` 턴) 뒤에 와야 하며, `assistant` 턴 앞에 오거나 배열의 끝이어야 합니다. 배열의 끝에 있는 메시지는 항상 렌더링됩니다. 바로 뒤에 다른 `user` 메시지가 오는 메시지는 지워진 메시지가 아니라 400 오류가 됩니다. 한 도구 라운드의 모든 결과를 하나의 사용자 메시지에 넣고 리마인더는 그 뒤에 두세요.
* **어시스턴트 턴은 메시지를 지우지 않습니다.** 메시지 뒤에 오는 미리 채워진(prefilled) 또는 [일시 중지된](https://platform.claude.com/docs/ko/build-with-claude/handling-stop-reasons#pause-turn) 어시스턴트 턴이나 서버 측 도구 루프는 사용자 메시지를 추가하지 않으므로, 해당 연속 요청에서도 메시지가 계속 렌더링됩니다. 클라이언트 측 도구 루프 전반에 걸쳐 리마인더를 계속 보이게 하려면 각 `tool_result` 메시지 뒤에 다시 추가하세요.
* **토큰 계산은 렌더링되는 내용을 따릅니다.** 지워진 메시지는 `usage.input_tokens`나 [토큰 수 계산](https://platform.claude.com/docs/ko/build-with-claude/token-counting)에 아무것도 추가하지 않습니다.
* **가져온 기록.** 한 번에 구성하는 대화 기록(few-shot 예시, 마이그레이션된 대화)에서, 이미 뒤에 어시스턴트 턴과 사용자 메시지가 있는 턴 범위 메시지는 첫 번째 요청부터 지워진 상태이며 절대 렌더링되지 않습니다. 이는 그대로 옮겨 오는 턴별 리마인더에 적합한 상태입니다. 모델이 모든 요청에서 봐야 하는 메시지에만 `clear_at`을 설정하지 마세요.

유효성 검사 오류는 다음과 같습니다:

```text wrap
messages.3.clear_at: Extra inputs are not permitted
messages.3.clear_at: clear_at is only permitted on role 'system' messages
messages.3.clear_at: Input should be 'next_user_message' or 'never'
messages.3: a turn-scoped system message supports text blocks only (clear_at: 'next_user_message')
messages.3: output_config is not permitted on a turn-scoped system message (clear_at: 'next_user_message')
messages.3.content.0: cache_control is not permitted on a turn-scoped system message (clear_at: 'next_user_message')
```

첫 번째는 베타 헤더 없이 반환되는 오류입니다. Google Cloud 및 [Claude in Amazon Bedrock](https://platform.claude.com/docs/ko/build-with-claude/claude-in-amazon-bedrock)에서는 Claude API와 마찬가지로 `anthropic-beta` 헤더에 베타 값을 보내세요([베타 헤더](https://platform.claude.com/docs/ko/api/beta-headers) 참조). Amazon Bedrock [InvokeModel API](https://platform.claude.com/docs/ko/build-with-claude/claude-on-amazon-bedrock-legacy)에서는 대신 요청 본문의 `anthropic_beta` 배열에 넣으세요.

SDK를 통해서는 `messages`의 `role: "system"` 항목에 `clear_at` (csharp, go: `ClearAt`; java: `.clearAt()`; php: `clearAt`)을 설정하고 베타 헤더를 보내세요. 다음 예시는 사용자 턴 뒤에 턴 범위 리마인더를 추가합니다. 다음 요청에서 이후 사용자 메시지가 존재하게 되면 리마인더는 배열에 남아 있지만 더 이상 렌더링되지 않습니다:

<CodeGroup>
  ```bash cURL
  curl https://api.anthropic.com/v1/messages \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -H "anthropic-beta: mid-conversation-system-clear-at-2026-08-21" \
    -H "content-type: application/json" \
    -d '{
      "model": "claude-fable-5-1",
      "max_tokens": 4096,
      "messages": [
        {"role": "user", "content": "Draft a short status update on the database migration for the team channel."},
        {"role": "system", "clear_at": "next_user_message", "content": "The reader is on call: keep this reply under 50 words."}
      ]
    }'
  ```

  ```bash CLI
  ant beta:messages create --beta mid-conversation-system-clear-at-2026-08-21 \
    --transform 'content.#(type=="text").text' --raw-output <<'YAML'
  model: claude-fable-5-1
  max_tokens: 4096
  messages:
    - role: user
      content: Draft a short status update on the database migration for the team channel.
    # 턴 범위 리마인더: 이번 턴에 렌더링되며, 이후 사용자 메시지가 생기면 지워집니다.
    - role: system
      clear_at: next_user_message
      content: "The reader is on call: keep this reply under 50 words."
  YAML
  ```

  ```python Python
  client = anthropic.Anthropic()

  response = client.beta.messages.create(
      model="claude-fable-5-1",
      max_tokens=4096,
      messages=[
          {
              "role": "user",
              "content": "Draft a short status update on the database migration for the team channel.",
          },
          # 턴 범위 리마인더: 이번 턴에 렌더링되며, 이후 사용자 메시지가 생기면 지워집니다.
          {
              "role": "system",
              "clear_at": "next_user_message",
              "content": "The reader is on call: keep this reply under 50 words.",
          },
      ],
      betas=["mid-conversation-system-clear-at-2026-08-21"],
  )

  for block in response.content:
      if block.type == "text":
          print(block.text)
  ```

  ```typescript TypeScript
  const client = new Anthropic();

  const response = await client.beta.messages.create({
    model: "claude-fable-5-1",
    max_tokens: 4096,
    messages: [
      {
        role: "user",
        content: "Draft a short status update on the database migration for the team channel."
      },
      // 턴 범위 리마인더: 이번 턴에 렌더링되며, 이후 사용자 메시지가 생기면 지워집니다.
      {
        role: "system",
        clear_at: "next_user_message",
        content: "The reader is on call: keep this reply under 50 words."
      }
    ],
    betas: ["mid-conversation-system-clear-at-2026-08-21"]
  });

  for (const block of response.content) {
    if (block.type === "text") {
      console.log(block.text);
    }
  }
  ```

  ```csharp C#
  using Anthropic.Models.Beta;
  using Anthropic.Models.Beta.Messages;
  using Messages = Anthropic.Models.Messages;

  AnthropicClient client = new();

  var response = await client.Beta.Messages.Create(new MessageCreateParams
  {
      Model = Messages::Model.ClaudeFable5_1,
      MaxTokens = 4096,
      Messages =
      [
          new() { Role = Role.User, Content = "Draft a short status update on the database migration for the team channel." },
          // 턴 범위 리마인더: 이번 턴에 렌더링되며, 이후 사용자 메시지가 생기면 지워집니다.
          new()
          {
              Role = Role.System,
              ClearAt = ClearAt.NextUserMessage,
              Content = "The reader is on call: keep this reply under 50 words.",
          },
      ],
      Betas = [AnthropicBeta.MidConversationSystemClearAt2026_08_21],
  });

  foreach (var block in response.Content)
  {
      if (block.TryPickText(out var textBlock))
      {
          Console.WriteLine(textBlock.Text);
      }
  }
  ```

  ```go Go
  client := anthropic.NewClient()

  response, err := client.Beta.Messages.New(context.Background(), anthropic.BetaMessageNewParams{
  	Model:     anthropic.ModelClaudeFable5_1,
  	MaxTokens: 4096,
  	Messages: []anthropic.BetaMessageParam{
  		anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Draft a short status update on the database migration for the team channel.")),
  		// 턴 범위 리마인더: 이번 턴에 렌더링되며, 이후 사용자 메시지가 생기면 지워집니다.
  		{
  			Role:    anthropic.BetaMessageParamRoleSystem,
  			ClearAt: anthropic.BetaMessageParamClearAtNextUserMessage,
  			Content: []anthropic.BetaContentBlockParamUnion{anthropic.NewBetaTextBlock("The reader is on call: keep this reply under 50 words.")},
  		},
  	},
  	Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaMidConversationSystemClearAt2026_08_21},
  })
  if err != nil {
  	log.Fatal(err)
  }

  for _, block := range response.Content {
  	if textBlock, ok := block.AsAny().(anthropic.BetaTextBlock); ok {
  		fmt.Println(textBlock.Text)
  	}
  }
  ```

  ```java Java
  import com.anthropic.models.beta.AnthropicBeta;
  import com.anthropic.models.beta.messages.BetaMessage;
  import com.anthropic.models.beta.messages.BetaMessageParam;
  import com.anthropic.models.beta.messages.MessageCreateParams;
  import com.anthropic.models.messages.Model;

  void main() {
      AnthropicClient client = AnthropicOkHttpClient.fromEnv();

      MessageCreateParams params = MessageCreateParams.builder()
          .model(Model.CLAUDE_FABLE_5_1)
          .maxTokens(4096L)
          .addBeta(AnthropicBeta.MID_CONVERSATION_SYSTEM_CLEAR_AT_2026_08_21)
          .addUserMessage("Draft a short status update on the database migration for the team channel.")
          // 턴 범위 리마인더: 이번 턴에 렌더링되며, 이후 사용자 메시지가 생기면 지워집니다.
          .addMessage(BetaMessageParam.builder()
              .role(BetaMessageParam.Role.SYSTEM)
              .clearAt(BetaMessageParam.ClearAt.NEXT_USER_MESSAGE)
              .content("The reader is on call: keep this reply under 50 words.")
              .build())
          .build();

      BetaMessage response = client.beta().messages().create(params);
      response.content().stream()
          .flatMap(block -> block.text().stream())
          .forEach(textBlock -> IO.println(textBlock.text()));
  }
  ```

  ```php PHP
  use Anthropic\Beta\AnthropicBeta;
  use Anthropic\Beta\Messages\BetaMessageParam;
  use Anthropic\Client;

  $client = new Client();

  $response = $client->beta->messages->create(
      model: 'claude-fable-5-1',
      maxTokens: 4096,
      messages: [
          BetaMessageParam::with(role: 'user', content: 'Draft a short status update on the database migration for the team channel.'),
          // 턴 범위 리마인더: 이번 턴에 렌더링되며, 이후 사용자 메시지가 생기면 지워집니다.
          BetaMessageParam::with(
              role: 'system',
              clearAt: 'next_user_message',
              content: 'The reader is on call: keep this reply under 50 words.',
          ),
      ],
      betas: [AnthropicBeta::MID_CONVERSATION_SYSTEM_CLEAR_AT_2026_08_21],
  );

  foreach ($response->content as $block) {
      if ($block->type === 'text') {
          echo $block->text, PHP_EOL;
      }
  }
  ```

  ```ruby Ruby
  client = Anthropic::Client.new

  response = client.beta.messages.create(
    model: "claude-fable-5-1",
    max_tokens: 4096,
    messages: [
      {role: "user", content: "Draft a short status update on the database migration for the team channel."},
      # 턴 범위 리마인더: 이번 턴에 렌더링되며, 이후 사용자 메시지가 생기면 지워집니다.
      {role: "system", clear_at: :next_user_message, content: "The reader is on call: keep this reply under 50 words."}
    ],
    betas: [Anthropic::AnthropicBeta::MID_CONVERSATION_SYSTEM_CLEAR_AT_2026_08_21]
  )

  response.content.each do |block|
    puts block.text if block.type == :text
  end
  ```
</CodeGroup>

## 프롬프트 캐싱과 함께 사용하기

대화 중간 시스템 메시지와 ["prompt caching"(프롬프트 캐싱)](https://platform.claude.com/docs/ko/build-with-claude/prompt-caching)은 함께 사용하도록 설계되었습니다:

* **캐싱을 명시적으로 활성화하세요.** 캐싱은 요청에 `cache_control`이 포함된 경우에만 발생합니다. 이는 최상위 [자동 캐싱](https://platform.claude.com/docs/ko/build-with-claude/prompt-caching#automatic-caching) 필드일 수도 있고, 콘텐츠 블록에 설정한 [명시적 중단점](https://platform.claude.com/docs/ko/build-with-claude/prompt-caching#explicit-cache-breakpoints)일 수도 있습니다. 대화 중간 시스템 메시지는 그 자체로 캐시 항목을 생성하지 않습니다. 또한 캐싱이 활성화되지 않은 경우에는 보존할 절감 효과도 없습니다.
* **평소처럼 안정적인 프리픽스를 캐시하세요.** 요청 간에 동일하게 유지되는 마지막 블록에 `cache_control`을 배치하세요. 이 위치는 최상위 `system` 필드의 끝, 도구 정의의 끝, 또는 메시지 기록의 안정적인 지점일 수 있습니다.
* **중단점 뒤에 시스템 메시지를 추가하세요.** 시스템 메시지는 캐시된 프리픽스 뒤에 오므로 프리픽스 해시를 변경하지 않으며, 캐시도 계속 적중합니다.
* **대화 중간 시스템 메시지 자체도 캐시할 수 있습니다.** 시스템 메시지가 대화에 포함되면 안정적인 기록의 일부가 됩니다. 다음 턴에서는 캐시 중단점을 그 뒤로 옮길 수 있습니다(또는 [자동 캐싱](https://platform.claude.com/docs/ko/build-with-claude/prompt-caching#automatic-caching)이 이를 처리하도록 맡길 수 있습니다). 그러면 시스템 메시지도 다른 턴과 마찬가지로 캐시에서 읽힙니다.

이미 보낸 대화 중간 시스템 메시지를 편집하거나 제거하지 마세요. 이전 메시지에 대한 다른 변경과 마찬가지로, 그 지점부터 캐시가 무효화됩니다. Claude Fable 5.1, Claude Opus 5.5, Claude Sonnet 5.5, Claude Haiku 5.5에서는 이후 모든 어시스턴트 턴의 [thinking 블록](https://platform.claude.com/docs/ko/build-with-claude/thinking#preserved-in-conversation)도 무효화됩니다. 한 턴에만 적용되어야 하는 안내에는 [턴 범위 시스템 메시지](https://platform.claude.com/docs/ko/build-with-claude/mid-conversation-system-messages#turn-scoped-system-messages)를 사용하고 그대로 두세요. 지시를 발전시켜야 하는 경우 이전 메시지를 다시 작성하는 대신 새 시스템 메시지를 추가하세요. 연속된 시스템 메시지는 허용되며 하나의 시스템 섹션으로 취급되고, 전체적으로 동일한 배치 규칙을 따릅니다.

## 제한 사항

* **첫 번째 메시지로 사용할 수 없습니다.** 콘텐츠가 있는 `system` 메시지는 `messages`의 첫 번째 항목이 될 수 없습니다. 처음부터 적용할 지침에는 최상위 `system` 필드를 사용하세요.
* **배치 위치가 제한됩니다.** 콘텐츠(`text`, `tool_addition` 또는 `tool_removal` 블록)가 있는 `system` 메시지는 `user` 턴(`tool_result` 블록을 담은 `user` 턴 포함) 또는 서버 도구 결과로 끝나는 `assistant` 턴 바로 뒤에 와야 합니다. 또한 `assistant` 턴 앞에 오거나 배열의 마지막 항목이어야 합니다. `tool_use` 블록과 그에 대한 `tool_result` 사이에는 둘 수 없습니다. 다른 위치에 두면 400 오류가 반환됩니다. 단, 한 가지 예외가 있습니다. [일시 중지된](https://platform.claude.com/docs/ko/build-with-claude/handling-stop-reasons#pause-turn) `assistant` 턴(서버 도구 결과로 끝나는 턴) 바로 뒤에는 `text` 블록은 둘 수 있지만 `tool_addition` 및 `tool_removal` 블록은 둘 수 없습니다. 이 경우 일시 중지된 턴을 먼저 재개한 다음, 다음 `system` 메시지에서 도구 변경을 보내세요. `content`가 비어 있고 [`output_config.effort`](https://platform.claude.com/docs/ko/build-with-claude/effort#change-effort-mid-conversation-beta)만 설정하는 메시지는 해당 위치에 아무것도 렌더링하지 않으므로, 첫 번째 위치나 `assistant` 턴과 `user` 턴 사이를 포함해 `messages`의 어디에나 둘 수 있습니다. 연속된 `system` 메시지는 하나의 그룹으로 판단됩니다. 따라서 effort만 설정하는 메시지 옆에 텍스트가 있는 메시지를 추가하면 그룹 전체에 콘텐츠 규칙이 적용됩니다.
* **턴 범위 메시지는 텍스트만 담을 수 있으며 그대로 다시 보내야 합니다.** `clear_at: "next_user_message"` 메시지에는 `tool_addition`, `tool_removal`, `output_config`, `cache_control`을 넣을 수 없습니다. 또한 지워진 후에도 이후 요청의 `messages`에 바이트 단위로 동일하게 남아 있어야 합니다. [턴 범위 시스템 메시지](https://platform.claude.com/docs/ko/build-with-claude/mid-conversation-system-messages#turn-scoped-system-messages)를 참조하세요.
* **신뢰할 수 없는 콘텐츠를 넣으면 안 됩니다.** Claude는 시스템 콘텐츠를 운영자 지침으로 취급하고 따릅니다. 원시 도구 출력, 검색된 문서, 웹 콘텐츠처럼 대화 외부에서 온 텍스트를 시스템 메시지에 직접 넣지 마세요. 그렇게 하면 해당 텍스트에 운영자 수준의 권한이 부여됩니다. 이러한 데이터는 `tool_result` 블록에 두고, [탈옥 및 프롬프트 인젝션 완화](https://platform.claude.com/docs/ko/test-and-evaluate/strengthen-guardrails/mitigate-jailbreaks) 지침을 계속 따르세요.

## 관련 항목

<CardGroup cols={2}>
  <Card title="프롬프트 캐싱" icon="bolt" href="https://platform.claude.com/docs/ko/build-with-claude/prompt-caching">
    캐싱 작동 방식, 중단점 배치 위치, 캐시 사용량 필드를 읽는 방법.
  </Card>

  <Card title="캐시 진단" icon="magnifying-glass" href="https://platform.claude.com/docs/ko/build-with-claude/cache-diagnostics">
    예상한 캐시 적중이 발생하지 않을 때 두 요청이 정확히 어디서 갈라졌는지 알아보세요.
  </Card>

  <Card title="Messages API 사용하기" icon="message" href="https://platform.claude.com/docs/ko/build-with-claude/working-with-messages">
    메시지 구조, 멀티턴 대화, `system` 필드.
  </Card>

  <Card title="프롬프팅 모범 사례" icon="text" href="https://platform.claude.com/docs/ko/build-with-claude/prompt-engineering/claude-prompting-best-practices">
    효과적인 프롬프트와 시스템 지침 작성하기.
  </Card>

  <Card title="Claude와 함께하는 도구 사용" icon="wrench" href="https://platform.claude.com/docs/ko/agents-and-tools/tool-use/overview">
    `messages` 배열에서 `tool_use` 및 `tool_result` 블록이 구조화되는 방식.
  </Card>
</CardGroup>
