---
title: 백그라운드 압축
url: https://platform.claude.com/docs/ko/build-with-claude/compaction-background
description: 대화가 전체 기록으로 계속 진행되는 동안 온디맨드 압축 요약을 요청하고, 요약이 도착하면 블록을 교체하세요.
featureMetadata:
  status: beta
  betaHeader: compact-2026-09-04
  supportedModels:
    - claude-fable-5-1
    - claude-mythos-5-1
    - claude-fable-5
    - claude-mythos-5
    - claude-mythos-preview
    - claude-opus-5-5
    - claude-opus-5
    - claude-opus-4-8
    - claude-opus-4-7
    - claude-opus-4-6
    - claude-sonnet-5-5
    - claude-sonnet-5
    - claude-sonnet-4-6
    - claude-haiku-5-5
  supportedPlatforms:
    Claude API: beta
    Claude Platform on AWS: beta
    Google Cloud: beta
    Microsoft Foundry: beta
---

"Background compaction"(백그라운드 압축)은 흔히 "async compaction"(비동기 압축)이라고도 불리며, [압축 루프](https://platform.claude.com/docs/ko/build-with-claude/compaction-on-demand#compact-in-a-loop)에서 두 가지를 변경합니다: 압축 요청은 대화가 전체 기록을 유지한 채로 계속되는 동안 실행되며, 교체는 블록이 도착할 때까지 대기합니다. [요약에서 이어서 진행하기](https://platform.claude.com/docs/ko/build-with-claude/compaction-on-demand#continue-from-the-summary)와 [요약 누락 또는 오류 처리하기](https://platform.claude.com/docs/ko/build-with-claude/compaction-on-demand#when-no-summary-comes-back)는 변경 없이 그대로 적용됩니다.

## 작업이 계속되는 동안 교체가 작동하는 방식

압축 요청과 그것이 반환하는 블록은 루프에서와 동일합니다. 요청을 보내는 시점과 그 결과를 사용하는 시점 사이에 기록이 늘어나며, 교체는 이렇게 늘어난 부분을 그대로 유지해야 합니다.

1. 현재 상태의 기록으로 [압축 요청](https://platform.claude.com/docs/ko/build-with-claude/compaction-on-demand#request-a-summary)을 보내고, 그 기록에 포함된 메시지 수를 기록해 두세요.
2. 해당 요청이 실행되는 동안 전체 기록으로 대화를 계속 진행하세요. 새로운 턴마다 추가하고, 이미 기록에 있는 내용은 수정하지 말며, 이번 요청이 교체되거나 실패하기 전까지는 다른 압축 요청을 시작하지 마세요.
3. `stop_reason`이 `"compaction"`인 응답이 도착하면, 보냈던 메시지를 기록의 앞부분에서 정확히 제거하고 그 자리에 반환된 메시지를 넣으세요. 1단계 이후 추가된 모든 턴은 그 뒤에 그대로 유지됩니다.
4. 블록이 도착한 후 첫 번째 요청에서는 교체된 기록을 보내세요. 그러면 요약이 작성되는 동안 생성된 사고(thinking)가 유효하게 유지됩니다.

예를 들어, 압축 요청이 메시지 1부터 5까지를 포함했고 그 요청이 실행되는 동안 대화에 메시지 6부터 8까지가 추가되었다면, 교체 후 기록은 블록 다음에 메시지 6부터 8까지가 이어지는 형태가 됩니다.

<Frame>
  ![Background compaction(백그라운드 압축) 타임라인: 대화가 전체 기록으로 계속 진행되어 메시지 6\~8이 추가되는 동안 메시지 1\~5로 압축 요청이 전송됩니다. 블록이 도착하면 기록 앞부분의 메시지 1\~5를 대체하며, 기록은 블록 다음에 메시지 6\~8이 이어지는 형태가 됩니다](https://platform.claude.com/docs/ko/images/compaction-background-timeline.svg)
</Frame>

응답에 다른 `stop_reason`이 있다면 요약이 생성되지 않은 것이며, 이는 2단계에서의 실패로 간주됩니다. 전체 기록을 그대로 유지하세요. [요약 누락 또는 오류 처리하기](https://platform.claude.com/docs/ko/build-with-claude/compaction-on-demand#when-no-summary-comes-back)에서 원인과 각각에 대한 대처 방법을 확인할 수 있습니다.

## 백그라운드에서 요약 요청하기

압축 요청은 다른 요청과 마찬가지로 속도 제한에 포함되며, 요청이 실행되는 동안 애플리케이션은 동시에 두 개의 요청을 열어 두게 됩니다. 대화는 교체가 이루어지기 전까지 전체 기록을 기준으로 계속 늘어나므로, 그 사이에 도착하는 턴을 위한 여유가 컨텍스트 윈도우에 남아 있을 때 압축 요청을 시작하세요.

다음 프로그램은 [루프에서 압축하기](https://platform.claude.com/docs/ko/build-with-claude/compaction-on-demand#compact-in-a-loop)의 루프에서 압축 요청을 대화의 경로 밖으로 옮긴 것입니다. 강조 표시된 줄은 루프와 다른 부분을 보여 주며, 아래 목록은 프로그램이 실행하는 순서대로 이를 설명합니다.

<CodeGroup exclude="shell, php">
  ```python Python
  from concurrent.futures import Future, ThreadPoolExecutor

  import anthropic
  from anthropic.types.beta import BetaMessage, BetaMessageParam

  client = anthropic.Anthropic()
  executor = ThreadPoolExecutor(max_workers=1)

  # 실제 입력 예산에 가깝게 설정하세요. 여기서는 짧은 대화도 압축되도록 낮게 설정했습니다.
  COMPACT_AT_TOKENS = 2500
  SYSTEM = "You help design a recipe app's data model. Keep answers short."

  QUESTIONS = [
      "What are the main entities in the data model?",
      "Which fields should Recipe have?",
      "Which fields should Ingredient have?",
      "Which fields should RecipeIngredient have?",
      "Which fields should Step have?",
      "Which indexes should these tables have?",
      "Which fields should be required?",
      "Which fields should have default values?",
  ]


  def swap_in(history: list[BetaMessageParam], summary: BetaMessage, sent: int) -> None:
      if summary.stop_reason == "compaction":
          # 압축 요청이 보유했던 메시지만 정확히 교체합니다.
          # 이후 턴은 블록 뒤에 그대로 유지됩니다.
          history[:sent] = [{"role": "assistant", "content": summary.content}]
          print(f"Swapped {sent} messages")


  history: list[BetaMessageParam] = []
  pending: Future[BetaMessage] | None = None
  sent = 0
  for turn, question in enumerate(QUESTIONS, start=1):
      if pending is not None and pending.done():
          swap_in(history, pending.result(), sent)
          pending = None

      history.append({"role": "user", "content": question})
      response = client.beta.messages.create(
          model="claude-opus-5-5",
          max_tokens=8192,
          system=SYSTEM,
          betas=["compact-2026-09-04"],
          messages=history,
      )
      history.append({"role": "assistant", "content": response.content})

      # 다음 요청에서는 이 응답도 함께 전송되므로 포함해서 계산하세요.
      conversation_tokens = response.usage.input_tokens + response.usage.output_tokens
      if (
          conversation_tokens > COMPACT_AT_TOKENS
          and turn < len(QUESTIONS)
          and pending is None
      ):
          sent = len(history)
          pending = executor.submit(
              client.beta.messages.create,
              model="claude-opus-5-5",
              max_tokens=4096,
              system=SYSTEM,
              betas=["compact-2026-09-04"],
              messages=history.copy(),
              compaction={"type": "summarize"},
          )

  # 저장하기 전에 아직 진행 중인 요약으로 교체합니다
  # 또는 대화를 계속합니다.
  if pending is not None:
      swap_in(history, pending.result(), sent)
  executor.shutdown()
  ```

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

  // 실제 입력 예산에 가깝게 설정하세요. 여기서는 짧은 대화도 압축되도록 낮게 설정했습니다.
  const compactAtTokens = 2500;
  const systemPrompt = "You help design a recipe app's data model. Keep answers short.";

  const questions = [
    "What are the main entities in the data model?",
    "Which fields should Recipe have?",
    "Which fields should Ingredient have?",
    "Which fields should RecipeIngredient have?",
    "Which fields should Step have?",
    "Which indexes should these tables have?",
    "Which fields should be required?",
    "Which fields should have default values?"
  ];

  function swapIn(
    history: Anthropic.Beta.Messages.BetaMessageParam[],
    summary: Anthropic.Beta.Messages.BetaMessage,
    sent: number
  ): Anthropic.Beta.Messages.BetaMessageParam[] {
    if (summary.stop_reason !== "compaction") {
      return history;
    }
    console.log(`Swapped ${sent} messages`);
    // 압축 요청이 보유한 메시지만 정확히 교체합니다. 이후 턴은 블록 뒤에 그대로 유지됩니다.
    return [{ role: "assistant", content: summary.content }, ...history.slice(sent)];
  }

  let history: Anthropic.Beta.Messages.BetaMessageParam[] = [];
  let pending: Promise<Anthropic.Beta.Messages.BetaMessage> | undefined;
  let settled = false;
  let sent = 0;
  for (const [index, question] of questions.entries()) {
    const turn = index + 1;
    if (pending && settled) {
      history = swapIn(history, await pending, sent);
      pending = undefined;
      settled = false;
    }

    history.push({ role: "user", content: question });
    const response = await client.beta.messages.create({
      model: "claude-opus-5-5",
      max_tokens: 8192,
      system: systemPrompt,
      betas: ["compact-2026-09-04"],
      messages: history
    });
    history.push({ role: "assistant", content: response.content });

    // 다음 요청에서 이 응답도 함께 전송되므로 포함해서 계산하세요.
    const conversationTokens = response.usage.input_tokens + response.usage.output_tokens;
    if (conversationTokens > compactAtTokens && turn < questions.length && !pending) {
      sent = history.length;
      pending = client.beta.messages.create({
        model: "claude-opus-5-5",
        max_tokens: 4096,
        system: systemPrompt,
        betas: ["compact-2026-09-04"],
        messages: [...history],
        compaction: { type: "summarize" }
      });
      // 어느 경우든 요청을 완료된 것으로 표시합니다. 이를 await하면 요약을 반환하거나 예외를 던집니다.
      const markSettled = () => {
        settled = true;
      };
      pending.then(markSettled, markSettled);
    }
  }

  // 대화를 저장하거나 계속하기 전에 아직 진행 중인 요약으로 교체합니다.
  if (pending) {
    history = swapIn(history, await pending, sent);
  }
  ```

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

  AnthropicClient client = new();

  // 실제 입력 예산(input budget)에 가깝게 설정하세요. 여기서는 낮게 설정되어 있어 짧은 대화도 압축됩니다.
  const int CompactAtTokens = 2500;
  const string SystemPrompt = "You help design a recipe app's data model. Keep answers short.";

  string[] questions =
  [
      "What are the main entities in the data model?",
      "Which fields should Recipe have?",
      "Which fields should Ingredient have?",
      "Which fields should RecipeIngredient have?",
      "Which fields should Step have?",
      "Which indexes should these tables have?",
      "Which fields should be required?",
      "Which fields should have default values?",
  ];

  static List<BetaMessageParam> SwapIn(List<BetaMessageParam> history, BetaMessage summary, int sent)
  {
      if (summary.StopReason != BetaStopReason.Compaction)
      {
          return history;
      }
      Console.WriteLine($"Swapped {sent} messages");
      // 압축 요청이 보유했던 메시지만 정확히 교체합니다. 이후 턴은 블록 뒤에 그대로 유지됩니다.
      return
      [
          new()
          {
              Role = Role.Assistant,
              Content = summary.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
          },
          .. history[sent..],
      ];
  }

  List<BetaMessageParam> history = [];
  Task<BetaMessage>? pending = null;
  var sent = 0;
  foreach (var (index, question) in questions.Index())
  {
      var turn = index + 1;
      if (pending is { IsCompleted: true })
      {
          history = SwapIn(history, await pending, sent);
          pending = null;
      }

      history.Add(new() { Role = Role.User, Content = question });
      var response = await client.Beta.Messages.Create(new MessageCreateParams
      {
          Model = Model.ClaudeOpus5_5,
          MaxTokens = 8192,
          System = SystemPrompt,
          Betas = [AnthropicBeta.Compact2026_09_04],
          Messages = history,
      });
      history.Add(new()
      {
          Role = Role.Assistant,
          Content = response.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
      });

      // 다음 요청에서 이 응답도 함께 전송되므로 포함해서 계산하세요.
      var conversationTokens = response.Usage.InputTokens + response.Usage.OutputTokens;
      if (conversationTokens > CompactAtTokens && turn < questions.Length && pending is null)
      {
          sent = history.Count;
          pending = client.Beta.Messages.Create(new MessageCreateParams
          {
              Model = Model.ClaudeOpus5_5,
              MaxTokens = 4096,
              System = SystemPrompt,
              Betas = [AnthropicBeta.Compact2026_09_04],
              Messages = [.. history],
              Compaction = new BetaCompactionConfig(), // type defaults to "summarize"
          });
      }
  }

  // 대화를 저장하거나 계속하기 전에, 아직 진행 중인 요약으로 교체하세요.
  if (pending is not null)
  {
      history = SwapIn(history, await pending, sent);
  }
  ```

  ```go Go
  ctx := context.Background()
  client := anthropic.NewClient()

  // 실제 입력 예산(input budget)에 가깝게 설정하세요. 여기서는 값이 낮아 짧은 대화도 압축됩니다.
  const compactAtTokens = 2500
  system := []anthropic.BetaTextBlockParam{{Text: "You help design a recipe app's data model. Keep answers short."}}

  questions := []string{
  	"What are the main entities in the data model?",
  	"Which fields should Recipe have?",
  	"Which fields should Ingredient have?",
  	"Which fields should RecipeIngredient have?",
  	"Which fields should Step have?",
  	"Which indexes should these tables have?",
  	"Which fields should be required?",
  	"Which fields should have default values?",
  }

  var history []anthropic.BetaMessageParam
  var pending chan *anthropic.BetaMessage
  var sent int
  swapIn := func(summary *anthropic.BetaMessage) {
  	if summary.StopReason != anthropic.BetaStopReasonCompaction {
  		return
  	}
  	fmt.Printf("Swapped %d messages\n", sent)
  	// 압축(compaction) 요청이 보유했던 메시지만 정확히 교체합니다. 이후 턴은 블록 뒤에 그대로 유지됩니다.
  	history = slices.Replace(history, 0, sent, summary.ToParam())
  }

  for i, question := range questions {
  	turn := i + 1
  	// nil 채널에서의 수신은 절대 성공하지 않으므로, 대기 중인 것이 없으면 건너뜁니다.
  	select {
  	case summary := <-pending:
  		swapIn(summary)
  		pending = nil
  	default:
  	}

  	history = append(history, anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock(question)))
  	response, err := client.Beta.Messages.New(ctx, anthropic.BetaMessageNewParams{
  		Model:     anthropic.ModelClaudeOpus5_5,
  		MaxTokens: 8192,
  		System:    system,
  		Betas:     []anthropic.AnthropicBeta{anthropic.AnthropicBetaCompact2026_09_04},
  		Messages:  history,
  	})
  	if err != nil {
  		log.Fatal(err)
  	}
  	history = append(history, response.ToParam())

  	// 다음 요청에서 이 응답도 함께 전송되므로 개수에 포함시킵니다.
  	conversationTokens := response.Usage.InputTokens + response.Usage.OutputTokens
  	if conversationTokens > compactAtTokens && turn < len(questions) && pending == nil {
  		sent = len(history)
  		pending = make(chan *anthropic.BetaMessage, 1)
  		go func(messages []anthropic.BetaMessageParam, result chan<- *anthropic.BetaMessage) {
  			summary, err := client.Beta.Messages.New(ctx, anthropic.BetaMessageNewParams{
  				Model:     anthropic.ModelClaudeOpus5_5,
  				MaxTokens: 4096,
  				System:    system,
  				Betas:     []anthropic.AnthropicBeta{anthropic.AnthropicBetaCompact2026_09_04},
  				Messages:  messages,
  				Compaction: anthropic.BetaCompactionConfigUnionParam{
  					OfSummarize: &anthropic.BetaSummarizeCompactionParam{},
  				},
  			})
  			if err != nil {
  				log.Fatal(err)
  			}
  			result <- summary
  		}(slices.Clone(history), pending)
  	}
  }

  // 대화를 저장하거나 계속하기 전에, 아직 진행 중인 요약으로 교체하세요.
  if pending != nil {
  	swapIn(<-pending)
  }
  ```

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

  // 이 값을 실제 입력 예산에 가깝게 설정하세요. 여기서는 값이 낮아서 짧은 대화도 압축됩니다.
  static final long COMPACT_AT_TOKENS = 2500;
  static final String SYSTEM = "You help design a recipe app's data model. Keep answers short.";

  void swapIn(List<BetaMessageParam> history, BetaMessage summary, int sent) {
      if (!summary.stopReason().map(BetaStopReason.COMPACTION::equals).orElse(false)) {
          return;
      }
      IO.println("Swapped " + sent + " messages");
      // 압축 요청이 보유했던 메시지만 정확히 교체합니다. 이후 턴은 블록 뒤에 그대로 유지됩니다.
      history.subList(0, sent).clear();
      history.addFirst(summary.toParam());
  }

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

      var questions = List.of(
          "What are the main entities in the data model?",
          "Which fields should Recipe have?",
          "Which fields should Ingredient have?",
          "Which fields should RecipeIngredient have?",
          "Which fields should Step have?",
          "Which indexes should these tables have?",
          "Which fields should be required?",
          "Which fields should have default values?"
      );

      var history = new ArrayList<BetaMessageParam>();
      CompletableFuture<BetaMessage> pending = null;
      int sent = 0;
      for (int turn = 1; turn <= questions.size(); turn++) {
          if (pending != null && pending.isDone()) {
              swapIn(history, pending.join(), sent);
              pending = null;
          }

          history.add(BetaMessageParam.builder()
              .role(BetaMessageParam.Role.USER)
              .content(questions.get(turn - 1))
              .build());
          var params = MessageCreateParams.builder()
              .model(Model.CLAUDE_OPUS_5_5)
              .maxTokens(8192)
              .system(SYSTEM)
              .addBeta(AnthropicBeta.COMPACT_2026_09_04)
              .messages(history)
              .build();
          var response = client.beta().messages().create(params);
          history.add(response.toParam());

          // 다음 요청에서 이 응답도 함께 전송되므로 포함해서 계산하세요.
          long conversationTokens = response.usage().inputTokens() + response.usage().outputTokens();
          if (conversationTokens > COMPACT_AT_TOKENS && turn < questions.size() && pending == null) {
              sent = history.size();
              var summaryParams = MessageCreateParams.builder()
                  .model(Model.CLAUDE_OPUS_5_5)
                  .maxTokens(4096)
                  .system(SYSTEM)
                  .addBeta(AnthropicBeta.COMPACT_2026_09_04)
                  .messages(List.copyOf(history))
                  .compaction(BetaCompactionConfig.builder().build()) // type defaults to "summarize"
                  .build();
              pending = client.async().beta().messages().create(summaryParams);
          }
      }

      // 저장하거나 대화를 계속하기 전에 아직 진행 중인 요약으로 교체하세요.
      if (pending != null) {
          swapIn(history, pending.join(), sent);
      }
      client.close();
  }
  ```

  <CodeGroupItem language="PHP">
    PHP SDK는 요청을 동기적으로 보내므로, 대화가 진행되는 동안 백그라운드에서 압축 요청을 실행할 수 없습니다.
  </CodeGroupItem>

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

  # 실제 입력 예산에 가깝게 설정하세요. 여기서는 짧은 대화도 압축되도록 낮게 설정했습니다.
  COMPACT_AT_TOKENS = 2500
  SYSTEM = "You help design a recipe app's data model. Keep answers short."

  questions = [
    "What are the main entities in the data model?",
    "Which fields should Recipe have?",
    "Which fields should Ingredient have?",
    "Which fields should RecipeIngredient have?",
    "Which fields should Step have?",
    "Which indexes should these tables have?",
    "Which fields should be required?",
    "Which fields should have default values?"
  ]

  def swap_in(history, summary, sent)
    return history unless summary.stop_reason == :compaction

    puts "Swapped #{sent} messages"
    # 압축 요청이 보유했던 메시지만 정확히 교체합니다. 이후 턴은 블록 뒤에 그대로 유지됩니다.
    [{ role: "assistant", content: summary.content }, *history[sent..]]
  end

  history = []
  pending = nil
  sent = 0
  questions.each.with_index(1) do |question, turn|
    if pending && !pending.alive?
      history = swap_in(history, pending.value, sent)
      pending = nil
    end

    history << { role: "user", content: question }
    response = client.beta.messages.create(
      model: Anthropic::Model::CLAUDE_OPUS_5_5,
      max_tokens: 8192,
      system_: SYSTEM,
      betas: [Anthropic::AnthropicBeta::COMPACT_2026_09_04],
      messages: history
    )
    history << { role: "assistant", content: response.content }

    # 다음 요청에서 이 응답도 함께 전송되므로 포함해서 계산하세요.
    conversation_tokens = response.usage.input_tokens + response.usage.output_tokens
    if conversation_tokens > COMPACT_AT_TOKENS && turn < questions.length && pending.nil?
      sent = history.length
      pending = Thread.new(history.dup) do |snapshot|
        client.beta.messages.create(
          model: Anthropic::Model::CLAUDE_OPUS_5_5,
          max_tokens: 4096,
          system_: SYSTEM,
          betas: [Anthropic::AnthropicBeta::COMPACT_2026_09_04],
          messages: snapshot,
          compaction: { type: "summarize" }
        )
      end
    end
  end

  # 대화를 저장하거나 계속하기 전에 아직 진행 중인 요약으로 교체하세요.
  history = swap_in(history, pending.value, sent) if pending
  ```
</CodeGroup>

* **압축 시점 결정:** 크기 확인 시 대기 중인 압축 요청이 없어야 한다는 조건도 함께 확인합니다.
* **요청 시작:** 루프가 압축 응답을 기다리는 지점에서, 이 버전은 기록에 포함된 메시지 수를 기록하고, 각 언어 고유의 동시성 도구를 사용해 기록의 복사본으로 요청을 시작한 다음, 기다리지 않고 다음 턴으로 넘어갑니다.
* **결과 확인:** 각 턴이 시작될 때마다 프로그램은 대기 중인 요청이 완료되었는지 확인합니다. 완료되었다면, 프로그램은 해당 턴의 요청을 보내기 전에 교체를 수행합니다.
* **교체 수행:** 루프가 전체 기록을 반환된 메시지로 교체하는 지점에서, 이 버전의 교체 함수는 요청이 포함했던 메시지만 앞부분부터 세어 교체하고, 그 이후에 추가된 모든 내용은 그대로 유지합니다.
* **루프 종료:** 루프가 종료될 때 압축 요청이 아직 대기 중이라면, 프로그램은 그 요청을 기다린 후 교체를 수행하여, 아직 도착 중인 요약이 대화를 저장하거나 계속하기 전에 사라지지 않도록 합니다.

`stop_reason` 확인은 루프와 동일하게 유지됩니다. 블록이 없는 응답은 기록을 그대로 둡니다. 더 이상 대기 중인 요청이 없으므로, 프로그램은 이제 새로운 압축 요청을 시작할 수 있습니다.

## 요약이 작성되는 동안 사고를 유효하게 유지하기

요약이 작성되는 동안 도착하는 턴은 유지되는 턴입니다. [보존된 사고](https://platform.claude.com/docs/ko/build-with-claude/preserved-thinking)를 지원하는 모델에서 사고 블록을 다시 보내는 경우, 해당 턴의 사고는 [유지된 사고가 유효하기 위한 조건](https://platform.claude.com/docs/ko/build-with-claude/compaction-thinking-blocks#conditions-for-kept-thinking-to-stay-valid)이 충족되는 동안에만 유효하게 유지됩니다.
