What this read moved
126–150 of 239This capture is too large to show at once. Changes 126-150 of 239 are below, significant first; the rest are on the following screens.
api/files Changed · +20 / -0 lines
api/messages Changed · +32 / -0 lines
This page is larger than the 256 KiB this site keeps, so one side of the diff below stops where the stored text does.
api/messages/batches Changed · +24 / -0 lines
api/models Changed · +8 / -0 lines
api/skills Changed · +32 / -0 lines
api/skills/versions Changed · +16 / -0 lines
build-with-claude/compaction-background New page · 529 lines, new page
## Compatibility ## How the swap works while work continues ## Request the summary in the background ## Keep thinking valid while the summary is built
A whole new page. There's nothing to diff it against, so here is what it says.
---
title: Compaction in the background
url: https://platform.claude.com/docs/en/build-with-claude/compaction-background
description: Request an on-demand compaction summary while the conversation continues on its full history, then swap the block in when it arrives.
---
## Compatibility
- Status: Beta
- [Beta header](https://platform.claude.com/docs/en/api/beta-headers): `compact-2026-09-04`
- Supported models: `claude-fable-5-1`, `claude-mythos-5-1`, `claude-fable-5`, `claude-mythos-5`, `claude-mythos-preview`, `claude-opus-5`, `claude-opus-4-8`, `claude-opus-4-7`, `claude-opus-4-6`, `claude-sonnet-5`, `claude-sonnet-4-6`
- Platforms: Claude API (beta), Claude Platform on AWS (beta), Microsoft Foundry (beta); not available on Amazon Bedrock, Google Cloud
Background compaction, often called async compaction, changes two things in the [compaction loop](https://platform.claude.com/docs/en/build-with-claude/compaction-on-demand#compact-in-a-loop): the compaction request runs while the conversation continues on its full history, and the swap waits until the block arrives. [Continue from the summary](https://platform.claude.com/docs/en/build-with-claude/compaction-on-demand#continue-from-the-summary) and [Handle a missing summary or an error](https://platform.claude.com/docs/en/build-with-claude/compaction-on-demand#when-no-summary-comes-back) apply unchanged.
## How the swap works while work continues
The compaction request and the block it returns are the same as in the loop. Your history grows between sending the request and using its result, and the swap must leave that growth in place.
1. Send the [compaction request](https://platform.claude.com/docs/en/build-with-claude/compaction-on-demand#request-a-summary) with your history as it stands, and record how many messages it held.
2. While that request runs, keep the conversation going on the full history. Append each new turn, don't edit anything already in the history, and don't start another compaction request until this one is swapped in or has failed.
3. When the response arrives with `stop_reason` `"compaction"`, drop exactly the messages you sent from the front of your history and put the returned message in their place. Every turn appended since step 1 stays after it.
4. Send the swapped history on the first request after the block arrives, so that thinking produced while the summary was being written stays valid.
For example, if the compaction request held messages 1 to 5 and the conversation gained messages 6 to 8 while it ran, after the swap your history is the block followed by messages 6 to 8.

If the response has any other `stop_reason`, no summary was produced, which counts as a failure in step 2. Keep the full history; [Handle a missing summary or an error](https://platform.claude.com/docs/en/build-with-claude/compaction-on-demand#when-no-summary-comes-back) lists the causes and what to do for each.
## Request the summary in the background
The compaction request counts against your rate limits like any other request, and while it runs your application has two requests open at once. The conversation keeps growing on its full history until the swap, so start the compaction request while the context window still has room for the turns that arrive meanwhile.
The following program is the loop from [Compact in a loop](https://platform.claude.com/docs/en/build-with-claude/compaction-on-demand#compact-in-a-loop) with the compaction request taken off the conversation's path. It has no PHP version, because the example depends on running two requests at once. The highlighted lines show where it differs from the loop, and the following list takes them in the order the program runs them.
<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)
# Set this near your real input budget. It is low here so a short conversation compacts.
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":
# Replace exactly the messages the compaction request held.
# Later turns stay after the block.
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",
max_tokens=8192,
system=SYSTEM,
betas=["compact-2026-09-04"],
messages=history,
)
history.append({"role": "assistant", "content": response.content})
# The next request sends this reply too, so count it.
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",
max_tokens=4096,
system=SYSTEM,
betas=["compact-2026-09-04"],
messages=history.copy(),
compaction={"type": "summarize"},
)
# Swap in a summary that is still on its way before you save
# or continue the conversation.
if pending is not None:
swap_in(history, pending.result(), sent)
executor.shutdown()
```
```typescript TypeScript
const client = new Anthropic();
// Set this near your real input budget. It is low here so a short conversation compacts.
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`);
// Replace exactly the messages the compaction request held. Later turns stay after the block.
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",
max_tokens: 8192,
system: systemPrompt,
betas: ["compact-2026-09-04"],
messages: history
});
history.push({ role: "assistant", content: response.content });
// The next request sends this reply too, so count it.
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",
max_tokens: 4096,
system: systemPrompt,
betas: ["compact-2026-09-04"],
messages: [...history],
compaction: { type: "summarize" }
});
// Mark the request settled either way. Awaiting it then returns the summary or throws.
const markSettled = () => {
settled = true;
};
pending.then(markSettled, markSettled);
}
}
// Swap in a summary that is still on its way before you save or continue the conversation.
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();
// Set this near your real input budget. It is low here so a short conversation compacts.
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");
// Replace exactly the messages the compaction request held. Later turns stay after the block.
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,
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(),
});
// The next request sends this reply too, so count it.
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,
MaxTokens = 4096,
System = SystemPrompt,
Betas = [AnthropicBeta.Compact2026_09_04],
Messages = [.. history],
Compaction = new BetaCompactionConfig(), // type defaults to "summarize"
});
}
}
// Swap in a summary that is still on its way before you save or continue the conversation.
if (pending is not null)
{
history = SwapIn(history, await pending, sent);
}
```
```go Go
ctx := context.Background()
client := anthropic.NewClient()
// Set this near your real input budget. It is low here so a short conversation compacts.
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?",
Cut at 300 lines. The page has the rest.
build-with-claude/compaction-keep-recent-turns New page · 490 lines, new page
## Compatibility ## Choose which turns to keep ## Compact the older turns and send the rest after the block ## Keep thinking valid in the kept turns
A whole new page. There's nothing to diff it against, so here is what it says.
---
title: Compaction that keeps recent turns
url: https://platform.claude.com/docs/en/build-with-claude/compaction-keep-recent-turns
description: Summarize the older turns of a conversation with on-demand compaction and send the most recent turns after the summary, word for word.
---
## Compatibility
- Status: Beta
- [Beta header](https://platform.claude.com/docs/en/api/beta-headers): `compact-2026-09-04`
- Supported models: `claude-fable-5-1`, `claude-mythos-5-1`, `claude-fable-5`, `claude-mythos-5`, `claude-mythos-preview`, `claude-opus-5`, `claude-opus-4-8`, `claude-opus-4-7`, `claude-opus-4-6`, `claude-sonnet-5`, `claude-sonnet-4-6`
- Platforms: Claude API (beta), Claude Platform on AWS (beta), Microsoft Foundry (beta); not available on Amazon Bedrock, Google Cloud
Keep-tail compaction keeps the last few turns of a conversation word for word after the summary. It changes two things in the [compaction loop](https://platform.claude.com/docs/en/build-with-claude/compaction-on-demand#compact-in-a-loop): which messages go into the compaction request, and what you send after the block. Everything in [Continue from the summary](https://platform.claude.com/docs/en/build-with-claude/compaction-on-demand#continue-from-the-summary) applies unchanged.
## Choose which turns to keep
No parameter sets which turns are kept. You pick a cut point in your history: the messages before it go into the compaction request, and the messages from it on are kept.
Kept turns go back to Claude at full length, so the more you keep, the less room the compaction frees.
Put the cut where no tool call is left open, with each tool call and its result on the same side. If the messages you send end in an `assistant` turn whose tool call has no result yet, the API rejects the compaction request.
## Compact the older turns and send the rest after the block
To keep a tail of recent turns word for word, leave those turns out of the compaction request. The API summarizes every message it is sent, so send only the older turns, then put the block in front of the turns you kept.
Send the kept turns exactly as they are in your history, thinking blocks included. Both requests carry the beta header, as in [Request a summary](https://platform.claude.com/docs/en/build-with-claude/compaction-on-demand#request-a-summary).
In the following example, the history holds two turns, and the cut keeps the second. The compaction request carries the first turn:
```json
{
"model": "claude-opus-5",
"max_tokens": 4096,
"messages": [
{
"role": "user",
"content": "I am building a recipe app. Help me name the main entities in the data model."
},
{
"role": "assistant",
"content": "Start with Recipe, Ingredient, and Step. Add a RecipeIngredient entry that holds the quantity and unit for each ingredient in a recipe."
}
],
"compaction": { "type": "summarize" }
}
```
The next request sends the returned block first, then the kept turn exactly as it was, then the new `user` message. [Continue from the summary](https://platform.claude.com/docs/en/build-with-claude/compaction-on-demand#continue-from-the-summary) shows a request that starts with a block.
The following program is the loop from [Compact in a loop](https://platform.claude.com/docs/en/build-with-claude/compaction-on-demand#compact-in-a-loop), changed to keep the last two turns. The highlighted lines show where it differs from the loop.
<CodeGroup exclude="shell">
```python Python
from anthropic.types.beta import BetaMessageParam
client = anthropic.Anthropic()
# Set this near your real input budget. It is low here so a short conversation compacts.
COMPACT_AT_TOKENS = 2500
SYSTEM = "You help design a recipe app's data model. Keep answers short."
KEEP_TURNS = 2
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?",
]
history: list[BetaMessageParam] = []
for turn, question in enumerate(QUESTIONS, start=1):
history.append({"role": "user", "content": question})
response = client.beta.messages.create(
model="claude-opus-5",
max_tokens=8192,
system=SYSTEM,
betas=["compact-2026-09-04"],
messages=history,
)
history.append({"role": "assistant", "content": response.content})
# The next request sends this reply too, so count it.
conversation_tokens = response.usage.input_tokens + response.usage.output_tokens
if conversation_tokens > COMPACT_AT_TOKENS and KEEP_TURNS < turn < len(QUESTIONS):
# A turn is one user message and one assistant reply,
# so the kept turns start with a user message.
split = -2 * KEEP_TURNS
older, recent = history[:split], history[split:]
summary = client.beta.messages.create(
model="claude-opus-5",
max_tokens=4096,
system=SYSTEM,
betas=["compact-2026-09-04"],
messages=older,
compaction={"type": "summarize"},
)
if summary.stop_reason == "compaction":
history = [{"role": "assistant", "content": summary.content}, *recent]
print(f"Kept {len(recent) // 2} turns after the block")
```
```typescript TypeScript
const client = new Anthropic();
// Set this near your real input budget. It is low here so a short conversation compacts.
const compactAtTokens = 2500;
const systemPrompt = "You help design a recipe app's data model. Keep answers short.";
const keepTurns = 2;
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?"
];
let history: Anthropic.Beta.Messages.BetaMessageParam[] = [];
for (const [index, question] of questions.entries()) {
const turn = index + 1;
history.push({ role: "user", content: question });
const response = await client.beta.messages.create({
model: "claude-opus-5",
max_tokens: 8192,
system: systemPrompt,
betas: ["compact-2026-09-04"],
messages: history
});
history.push({ role: "assistant", content: response.content });
// The next request sends this reply too, so count it.
const conversationTokens = response.usage.input_tokens + response.usage.output_tokens;
if (conversationTokens > compactAtTokens && turn > keepTurns && turn < questions.length) {
// A turn is one user message and one assistant reply, so the kept turns start with a user message.
const older = history.slice(0, -2 * keepTurns);
const recent = history.slice(-2 * keepTurns);
const summary = await client.beta.messages.create({
model: "claude-opus-5",
max_tokens: 4096,
system: systemPrompt,
betas: ["compact-2026-09-04"],
messages: older,
compaction: { type: "summarize" }
});
if (summary.stop_reason === "compaction") {
history = [{ role: "assistant", content: summary.content }, ...recent];
console.log(`Kept ${recent.length / 2} turns after the block`);
}
}
}
```
```csharp C#
using Anthropic.Models.Beta;
using Anthropic.Models.Beta.Messages;
using Model = Anthropic.Models.Messages.Model;
AnthropicClient client = new();
// Set this near your real input budget. It is low here so a short conversation compacts.
const int CompactAtTokens = 2500;
const string SystemPrompt = "You help design a recipe app's data model. Keep answers short.";
const int KeepTurns = 2;
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?",
];
List<BetaMessageParam> history = [];
foreach (var (index, question) in questions.Index())
{
var turn = index + 1;
history.Add(new() { Role = Role.User, Content = question });
var response = await client.Beta.Messages.Create(new MessageCreateParams
{
Model = Model.ClaudeOpus5,
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(),
});
// The next request sends this reply too, so count it.
var conversationTokens = response.Usage.InputTokens + response.Usage.OutputTokens;
if (conversationTokens > CompactAtTokens && turn > KeepTurns && turn < questions.Length)
{
// A turn is one user message and one assistant reply, so the kept turns start with a user message.
var older = history[..^(2 * KeepTurns)];
var recent = history[^(2 * KeepTurns)..];
var summary = await client.Beta.Messages.Create(new MessageCreateParams
{
Model = Model.ClaudeOpus5,
MaxTokens = 4096,
System = SystemPrompt,
Betas = [AnthropicBeta.Compact2026_09_04],
Messages = older,
Compaction = new BetaCompactionConfig(), // type defaults to "summarize"
});
if (summary.StopReason == BetaStopReason.Compaction)
{
history =
[
new()
{
Role = Role.Assistant,
Content = summary.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
},
.. recent,
];
Console.WriteLine($"Kept {recent.Count / 2} turns after the block");
}
}
}
```
```go Go
ctx := context.Background()
client := anthropic.NewClient()
// Set this near your real input budget. It is low here so a short conversation compacts.
const compactAtTokens = 2500
system := []anthropic.BetaTextBlockParam{{Text: "You help design a recipe app's data model. Keep answers short."}}
const keepTurns = 2
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
for i, question := range questions {
turn := i + 1
history = append(history, anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock(question)))
response, err := client.Beta.Messages.New(ctx, anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus5,
MaxTokens: 8192,
System: system,
Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaCompact2026_09_04},
Messages: history,
})
if err != nil {
log.Fatal(err)
}
history = append(history, response.ToParam())
// The next request sends this reply too, so count it.
conversationTokens := response.Usage.InputTokens + response.Usage.OutputTokens
if conversationTokens > compactAtTokens && turn > keepTurns && turn < len(questions) {
// A turn is one user message and one assistant reply, so the kept turns start with a user message.
split := len(history) - 2*keepTurns
older, recent := history[:split], history[split:]
summary, err := client.Beta.Messages.New(ctx, anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus5,
MaxTokens: 4096,
System: system,
Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaCompact2026_09_04},
Messages: older,
Compaction: anthropic.BetaCompactionConfigUnionParam{
OfSummarize: &anthropic.BetaSummarizeCompactionParam{},
},
})
if err != nil {
log.Fatal(err)
}
if summary.StopReason == anthropic.BetaStopReasonCompaction {
history = slices.Replace(history, 0, split, summary.ToParam())
fmt.Printf("Kept %d turns after the block\n", len(recent)/2)
}
}
}
```
```java Java
Cut at 300 lines. The page has the rest.
build-with-claude/compaction-on-demand New page · 821 lines, new page
## Compatibility ## How on-demand compaction works ## Request a summary ## Continue from the summary ### Compact again ## Compact in a loop ### When to compact ## Write your own summarization prompt ## Handle a missing summary or an error ### Errors ## Count compaction usage ## Limits and interactions with other features
A whole new page. There's nothing to diff it against, so here is what it says.
---
title: Compaction on demand
url: https://platform.claude.com/docs/en/build-with-claude/compaction-on-demand
description: Ask Claude to summarize a conversation when your application chooses, then continue from the summary.
---
## Compatibility
- Status: Beta
- [Beta header](https://platform.claude.com/docs/en/api/beta-headers): `compact-2026-09-04`
- Supported models: `claude-fable-5-1`, `claude-mythos-5-1`, `claude-fable-5`, `claude-mythos-5`, `claude-mythos-preview`, `claude-opus-5`, `claude-opus-4-8`, `claude-opus-4-7`, `claude-opus-4-6`, `claude-sonnet-5`, `claude-sonnet-4-6`
- Platforms: Claude API (beta), Claude Platform on AWS (beta), Microsoft Foundry (beta); not available on Amazon Bedrock, Google Cloud
With on-demand compaction, your application decides when a conversation is summarized: you send one request with the `compaction` parameter, and Claude returns a summary in place of a reply.
## How on-demand compaction works
A compaction request is separate from your conversation turns. You send the conversation as it stands with the `compaction` parameter, and the response contains a single `compaction` block. The block holds the summary as text you can read, and a signature. Send it in future requests exactly as it came.
From then on the block takes the place of the messages it summarizes. It goes first in `messages`, the summarized messages are removed, and your next turn follows it. Claude sees the summary where those messages were.

## Request a summary
Send the `compact-2026-09-04` beta header on the request that asks for the summary and on every later request that carries the signed block. To check whether a model supports on-demand compaction, call the [Models API](https://platform.claude.com/docs/en/api/beta/models/list) with the beta header and read each model's `capabilities.compaction`. You can't combine `compaction` with `context_management` on one request.
Send the conversation as it stands with `"compaction": {"type": "summarize"}`. The API summarizes every message in the request once, generates no reply after it, and returns the block alone with `stop_reason` `"compaction"`. Send the same `system` prompt and `tools` that you use for the rest of the conversation. The summarizer reads them, and if you keep turns after the block on a model with [preserved thinking](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking), the thinking in those turns stays valid only if `system` and `tools` match. The conversation in this example has no `system` prompt or tools, so the request sends neither:
<CodeGroup>
```bash cURL
# max_tokens caps the whole call, including any thinking, so allow several thousand tokens.
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: compact-2026-09-04" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-5",
"max_tokens": 4096,
"messages": [
{"role": "user", "content": "I am building a recipe app. Help me name the main entities in the data model."},
{"role": "assistant", "content": "Start with Recipe, Ingredient, and Step. Add a RecipeIngredient entry that holds the quantity and unit for each ingredient in a recipe."},
{"role": "user", "content": "Good. Now suggest field names for Recipe."}
],
"compaction": {"type": "summarize"}
}'
```
<MultiFileExample language="cli" label="CLI">
```bash CLI
ant beta:messages create --beta compact-2026-09-04 < request.yaml
```
<File filename="request.yaml">
```yaml
model: claude-opus-5
# max_tokens caps the whole call, including any thinking, so allow several thousand tokens.
max_tokens: 4096
messages:
- role: user
content: I am building a recipe app. Help me name the main entities in the data model.
- role: assistant
content: Start with Recipe, Ingredient, and Step. Add a RecipeIngredient entry that holds the quantity and unit for each ingredient in a recipe.
- role: user
content: Good. Now suggest field names for Recipe.
compaction:
type: summarize
```
</File>
</MultiFileExample>
```python Python
from anthropic.types.beta import BetaMessageParam
client = anthropic.Anthropic()
history: list[BetaMessageParam] = [
{
"role": "user",
"content": "I am building a recipe app. Help me name the main entities in the data model.",
},
{
"role": "assistant",
"content": "Start with Recipe, Ingredient, and Step. Add a RecipeIngredient entry that holds the quantity and unit for each ingredient in a recipe.",
},
{"role": "user", "content": "Good. Now suggest field names for Recipe."},
]
response = client.beta.messages.create(
model="claude-opus-5",
# max_tokens caps the whole call, including any thinking, so allow several thousand tokens.
max_tokens=4096,
betas=["compact-2026-09-04"],
messages=history,
compaction={"type": "summarize"},
)
print(f"Stop reason: {response.stop_reason}")
```
```typescript TypeScript
const client = new Anthropic();
const history: Anthropic.Beta.Messages.BetaMessageParam[] = [
{
role: "user",
content: "I am building a recipe app. Help me name the main entities in the data model."
},
{
role: "assistant",
content:
"Start with Recipe, Ingredient, and Step. Add a RecipeIngredient entry that holds the quantity and unit for each ingredient in a recipe."
},
{ role: "user", content: "Good. Now suggest field names for Recipe." }
];
const response = await client.beta.messages.create({
model: "claude-opus-5",
// max_tokens caps the whole call, including any thinking, so allow several thousand tokens.
max_tokens: 4096,
betas: ["compact-2026-09-04"],
messages: history,
compaction: { type: "summarize" }
});
console.log(`Stop reason: ${response.stop_reason}`);
```
```csharp C#
using Anthropic.Models.Beta;
using Anthropic.Models.Beta.Messages;
using Model = Anthropic.Models.Messages.Model;
AnthropicClient client = new();
List<BetaMessageParam> history =
[
new()
{
Role = Role.User,
Content = "I am building a recipe app. Help me name the main entities in the data model.",
},
new()
{
Role = Role.Assistant,
Content = "Start with Recipe, Ingredient, and Step. Add a RecipeIngredient entry that holds the quantity and unit for each ingredient in a recipe.",
},
new() { Role = Role.User, Content = "Good. Now suggest field names for Recipe." },
];
var response = await client.Beta.Messages.Create(new MessageCreateParams
{
Model = Model.ClaudeOpus5,
// max_tokens caps the whole call, including any thinking, so allow several thousand tokens.
MaxTokens = 4096,
Betas = [AnthropicBeta.Compact2026_09_04],
Messages = history,
Compaction = new BetaCompactionConfig(), // type defaults to "summarize"
});
Console.WriteLine($"Stop reason: {response.StopReason?.Raw()}");
```
```go Go
client := anthropic.NewClient()
history := []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("I am building a recipe app. Help me name the main entities in the data model.")),
{
Role: anthropic.BetaMessageParamRoleAssistant,
Content: []anthropic.BetaContentBlockParamUnion{anthropic.NewBetaTextBlock("Start with Recipe, Ingredient, and Step. Add a RecipeIngredient entry that holds the quantity and unit for each ingredient in a recipe.")},
},
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Good. Now suggest field names for Recipe.")),
}
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus5,
// max_tokens caps the whole call, including any thinking, so allow several thousand tokens.
MaxTokens: 4096,
Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaCompact2026_09_04},
Messages: history,
Compaction: anthropic.BetaCompactionConfigUnionParam{
OfSummarize: &anthropic.BetaSummarizeCompactionParam{},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println("Stop reason:", response.StopReason)
```
```java Java
import com.anthropic.models.beta.AnthropicBeta;
import com.anthropic.models.beta.messages.BetaCompactionConfig;
import com.anthropic.models.beta.messages.MessageCreateParams;
void main() {
var client = AnthropicOkHttpClient.fromEnv();
var params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5)
// max_tokens caps the whole call, including any thinking, so allow several thousand tokens.
.maxTokens(4096)
.addBeta(AnthropicBeta.COMPACT_2026_09_04)
.addUserMessage("I am building a recipe app. Help me name the main entities in the data model.")
.addAssistantMessage("Start with Recipe, Ingredient, and Step. Add a RecipeIngredient entry that holds the quantity and unit for each ingredient in a recipe.")
.addUserMessage("Good. Now suggest field names for Recipe.")
.compaction(BetaCompactionConfig.builder().build()) // type defaults to "summarize"
.build();
var response = client.beta().messages().create(params);
response.stopReason().ifPresent(reason -> IO.println("Stop reason: " + reason));
}
```
```php PHP
use Anthropic\Beta\AnthropicBeta;
use Anthropic\Beta\Messages\BetaCompactionConfig;
use Anthropic\Beta\Messages\BetaMessageParam;
use Anthropic\Beta\Messages\BetaMessageParam\Role;
$client = new Client();
$history = [
BetaMessageParam::with(
role: Role::USER,
content: 'I am building a recipe app. Help me name the main entities in the data model.',
),
BetaMessageParam::with(
role: Role::ASSISTANT,
content: 'Start with Recipe, Ingredient, and Step. Add a RecipeIngredient entry that holds the quantity and unit for each ingredient in a recipe.',
),
BetaMessageParam::with(role: Role::USER, content: 'Good. Now suggest field names for Recipe.'),
];
$response = $client->beta->messages->create(
model: Model::CLAUDE_OPUS_5,
// max_tokens caps the whole call, including any thinking, so allow several thousand tokens.
maxTokens: 4096,
betas: [AnthropicBeta::COMPACT_2026_09_04],
messages: $history,
compaction: BetaCompactionConfig::with(), // type defaults to 'summarize'
);
echo "Stop reason: {$response->stopReason}", PHP_EOL;
```
```ruby Ruby
client = Anthropic::Client.new
history = [
{
role: "user",
content: "I am building a recipe app. Help me name the main entities in the data model."
},
{
role: "assistant",
content: "Start with Recipe, Ingredient, and Step. Add a RecipeIngredient entry that holds the quantity and unit for each ingredient in a recipe."
},
{ role: "user", content: "Good. Now suggest field names for Recipe." }
]
response = client.beta.messages.create(
model: Anthropic::Model::CLAUDE_OPUS_5,
# max_tokens caps the whole call, including any thinking, so allow several thousand tokens.
max_tokens: 4096,
betas: [Anthropic::AnthropicBeta::COMPACT_2026_09_04],
messages: history,
compaction: { type: "summarize" }
)
puts "Stop reason: #{response.stop_reason}"
```
</CodeGroup>
```json Response
{
"id": "msg_013Zva2CMHLNnXjNJJKqJ2EF",
"type": "message",
"role": "assistant",
"model": "claude-opus-5",
"content": [
{
"type": "compaction",
"content": "Summary of the conversation: the user is designing the data model for a recipe app. The entities agreed so far are Recipe, Ingredient, Step, and RecipeIngredient, which holds the quantity and unit. The user then asked for field names for Recipe.",
"signature": "EuYBCkQY..."
}
],
"stop_reason": "compaction",
"usage": {
"input_tokens": 0,
"output_tokens": 0,
"iterations": [{ "type": "compaction", "input_tokens": 144, "output_tokens": 276 }]
}
}
```
The summarization call uses the request's model, `system`, `tools`, thinking settings, and `max_tokens`. The summarizer reads the tool definitions but never runs a tool, and the response carries no thinking. `max_tokens` caps the whole call, including any thinking the model does before it writes the summary, so allow several thousand tokens. [Count compaction usage](https://platform.claude.com/docs/en/build-with-claude/compaction-on-demand#understanding-usage) shows how the call is billed.
If the last `assistant` turn ends in a tool call with no result yet, the API rejects the request. Send that turn's tool results first. Also leave out `stop_sequences`, structured-output `output_config.format`, and a `tool_choice` of type `any` or `tool`. They would do nothing on a summarization call, and the API rejects them. The conversation must still fit the model's context window, so compact before you outgrow it, not after.
When you stream the response, the block arrives whole. You get one `content_block_start` event carrying the complete block, then `content_block_stop`, with no `content_block_delta` events. `ping` events can arrive before or between them.
Cut at 300 lines. The page has the rest.
build-with-claude/compaction-thinking-blocks New page · 688 lines, new page
## Compatibility ## Conditions for kept thinking to stay valid ## Compact again without breaking older thinking ## Change the system prompt or tools ## Check that the kept thinking held
A whole new page. There's nothing to diff it against, so here is what it says.
---
title: Compaction and preserved thinking
url: https://platform.claude.com/docs/en/build-with-claude/compaction-thinking-blocks
description: When thinking blocks in turns kept after on-demand compaction stay valid on models with preserved thinking, and how to check.
---
## Compatibility
- Status: Beta
- [Beta header](https://platform.claude.com/docs/en/api/beta-headers): `compact-2026-09-04`
- Supported models: `claude-fable-5-1`, `claude-mythos-5-1`, `claude-fable-5`, `claude-mythos-5`, `claude-mythos-preview`, `claude-opus-5`, `claude-opus-4-8`, `claude-opus-4-7`, `claude-opus-4-6`, `claude-sonnet-5`, `claude-sonnet-4-6`
- Platforms: Claude API (beta), Claude Platform on AWS (beta), Microsoft Foundry (beta); not available on Amazon Bedrock, Google Cloud
Skip this page unless you send thinking blocks back to a model with [preserved thinking](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking) and keep turns after the compaction block. Kept turns are the turns that follow the block: recent turns you left out of the compaction request, as in [Compaction that keeps recent turns](https://platform.claude.com/docs/en/build-with-claude/compaction-keep-recent-turns), or turns that arrived while the summary was being written, as in [Compaction in the background](https://platform.claude.com/docs/en/build-with-claude/compaction-background).
Models with preserved thinking check earlier thinking blocks against the conversation that produced them. A summary replaces part of that conversation, but the check accepts the swap when the API wrote the summary, so the thinking in kept turns can stay valid.
## Conditions for kept thinking to stay valid
The thinking blocks in kept turns stay valid while all of these hold:
* **The compaction request runs on a model with preserved thinking.** This condition covers every compaction request since a thinking block was produced, not only the most recent one. One way to meet it is to send every compaction request to the model the conversation uses.
* **The kept turns directly follow the summarized messages, and you send them unchanged.** Send each kept message exactly as it is in your history. Don't skip or add a message between the last summarized message and the first kept one. The first kept message must also have a different role from the last summarized message, and it can't be a mid-conversation `role: "system"` message. Otherwise, the API merges it into the last summarized message. One way to get the first kept message right is to compact exactly the `messages` of a request you already sent. The kept turns then start with Claude's reply to it.
* **`system` and the `tools` not marked `defer_loading: true` don't change.** They are the same on the compaction request as on the requests that produced the kept thinking, and they stay the same on the requests that follow. [Change the system prompt or tools](https://platform.claude.com/docs/en/build-with-claude/compaction-thinking-blocks#change-the-system-prompt-or-tools) covers how to change them safely.
If a condition doesn't hold, nothing fails when you compact, and the API accepts the block on later requests either way. The failure comes on the first later request that sends the kept thinking where the API enforces the check: a 400 error by default, or dropped thinking blocks if the request sets `thinking.block_binding.prefix_mismatch_behavior` to `"drop_block"`. In the Message Batches API, an item that leaves the field unset drops the blocks instead. [What the API does with an invalid block](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#mismatch-behavior) describes both outcomes, and [When the API enforces the check](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#enforcement) says which requests are checked.
## Compact again without breaking older thinking
You can [compact again](https://platform.claude.com/docs/en/build-with-claude/compaction-on-demand#compact-again) and keep turns: the new block covers the old summary and every message that follows it in the compaction request, and any turns you leave out of that request are kept turns of the new block.
The first of the [conditions for kept thinking](https://platform.claude.com/docs/en/build-with-claude/compaction-thinking-blocks#conditions-for-kept-thinking-to-stay-valid) counts every compaction since a thinking block was produced, so a turn that you keep through two compactions needs both to have run on a model with preserved thinking.
Compactions from before a thinking block was produced don't count against it. Thinking produced after a block is in place is bound to that block, and it stays valid through later compactions that meet the conditions.
## Change the system prompt or tools
A later request can use a different `system`, different `tools`, or a different model than the compaction request, and the API still accepts the block. Such a change can invalidate the thinking in the kept turns, but it has no other effect.
To change `system` or `tools` without invalidating any kept thinking, compact the whole conversation first, so no turns are kept. Then change them on the next request.
To add an instruction or change the available tools without touching `system` or `tools`, append the change to `messages`, as described in [Make changes without editing the prefix](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#replace-prefix-edits).
Mid-conversation system messages inside the summarized turns are summarized too, so their instructions and tool changes stop applying after the swap. To keep one in force, state it again in a `role: "system"` message directly after the first new `user` turn that follows the kept turns. A system message placed between the block and the kept turns breaks their thinking.
## Check that the kept thinking held
The compaction response doesn't say whether the kept thinking holds. The first request after the swap does. To check in your tests:
1. Have a short conversation with thinking on. Use a model on which the API runs the check (see [When the API enforces the check](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#enforcement)), and use it for every step, because a model that can't read a thinking block drops it with no error.
2. Compact the older turns, and keep at least one turn that holds a thinking block.
3. Send the next request, with the block first, then the kept turn, then a new `user` message, and with `thinking.block_binding.prefix_mismatch_behavior` set to `"error"`.
4. Read the result. A 200 response whose `input_transformations` array is empty means no thinking block failed the check or was dropped. A 400 error that says the block is bound to a different conversation means one did. The message starts with the path of the first block that failed, and [What the API does with an invalid block](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#mismatch-behavior) shows it in full.
The `prefix_mismatch_behavior` field needs the `thinking-binding-controls-2026-08-01` beta header in addition to [the `compact-2026-09-04` beta header](https://platform.claude.com/docs/en/build-with-claude/compaction-on-demand#request-a-summary). Setting the field also opts the request into the check on accounts where the check isn't on by default.
The following program runs the four steps. It prints how many thinking blocks the kept turn holds and how many entries `input_transformations` has; no entries means the kept thinking held:
<CodeGroup exclude="shell">
```python Python
from anthropic.types.beta import BetaMessageParam, BetaThinkingConfigParam
client = anthropic.Anthropic()
# Claude Fable 5.1 is the first model that checks sent-back thinking against the conversation.
MODEL = "claude-fable-5-1"
BETAS = ["compact-2026-09-04", "thinking-binding-controls-2026-08-01"]
SYSTEM = "You help plan a recipe app's release. Keep answers short."
# With "error", a thinking block that fails the check makes the request fail with a 400.
THINKING: BetaThinkingConfigParam = {
"type": "adaptive",
"block_binding": {"prefix_mismatch_behavior": "error"},
}
# 1. Have a short conversation with thinking on.
history: list[BetaMessageParam] = [
{"role": "user", "content": "What are the main entities in the app's data model?"}
]
first = client.beta.messages.create(
model=MODEL,
max_tokens=8192,
system=SYSTEM,
betas=BETAS,
thinking=THINKING,
messages=history,
)
history += [
{"role": "assistant", "content": first.content},
{
"role": "user",
"content": "Testing starts on Tuesday, March 3, 2026, takes 10 weekdays, and pauses on March 9 and March 16. On which date does it end?",
},
]
second = client.beta.messages.create(
model=MODEL,
max_tokens=8192,
system=SYSTEM,
betas=BETAS,
thinking=THINKING,
messages=history,
)
history.append({"role": "assistant", "content": second.content})
thinking_blocks = sum(block.type == "thinking" for block in second.content)
print(f"Thinking blocks in the kept turn: {thinking_blocks}")
# 2. Summarize the first turn. The second turn stays out of the request.
summary = client.beta.messages.create(
model=MODEL,
max_tokens=4096,
system=SYSTEM,
betas=BETAS,
thinking=THINKING,
messages=history[:2],
compaction={"type": "summarize"},
)
if summary.stop_reason != "compaction":
raise SystemExit(f"No summary: {summary.stop_reason}")
# 3. Put the block in front of the kept turn and ask the next question.
history = [
{"role": "assistant", "content": summary.content},
*history[2:],
{"role": "user", "content": "Which day should the release go out?"},
]
third = client.beta.messages.create(
model=MODEL,
max_tokens=8192,
system=SYSTEM,
betas=BETAS,
thinking=THINKING,
messages=history,
)
# 4. A 200 with no dropped blocks means the kept thinking held.
print(f"Dropped thinking blocks: {len(third.input_transformations)}")
```
```typescript TypeScript
const client = new Anthropic();
// Claude Fable 5.1 is the first model that checks sent-back thinking against the conversation.
const model: Anthropic.Model = "claude-fable-5-1";
const betas: Anthropic.Beta.AnthropicBeta[] = [
"compact-2026-09-04",
"thinking-binding-controls-2026-08-01"
];
const systemPrompt = "You help plan a recipe app's release. Keep answers short.";
// With "error", a thinking block that fails the check makes the request fail with a 400.
const thinking: Anthropic.Beta.Messages.BetaThinkingConfigParam = {
type: "adaptive",
block_binding: { prefix_mismatch_behavior: "error" }
};
// 1. Have a short conversation with thinking on.
let history: Anthropic.Beta.Messages.BetaMessageParam[] = [
{ role: "user", content: "What are the main entities in the app's data model?" }
];
const first = await client.beta.messages.create({
model,
max_tokens: 8192,
system: systemPrompt,
betas,
thinking,
messages: history
});
history.push(
{ role: "assistant", content: first.content },
{
role: "user",
content:
"Testing starts on Tuesday, March 3, 2026, takes 10 weekdays, and pauses on March 9 and March 16. On which date does it end?"
}
);
const second = await client.beta.messages.create({
model,
max_tokens: 8192,
system: systemPrompt,
betas,
thinking,
messages: history
});
history.push({ role: "assistant", content: second.content });
const thinkingBlocks = second.content.filter((block) => block.type === "thinking").length;
console.log(`Thinking blocks in the kept turn: ${thinkingBlocks}`);
// 2. Summarize the first turn. The second turn stays out of the request.
const summary = await client.beta.messages.create({
model,
max_tokens: 4096,
system: systemPrompt,
betas,
thinking,
messages: history.slice(0, 2),
compaction: { type: "summarize" }
});
if (summary.stop_reason !== "compaction") {
throw new Error(`No summary: ${summary.stop_reason}`);
}
// 3. Put the block in front of the kept turn and ask the next question.
history = [
{ role: "assistant", content: summary.content },
...history.slice(2),
{ role: "user", content: "Which day should the release go out?" }
];
const third = await client.beta.messages.create({
model,
max_tokens: 8192,
system: systemPrompt,
betas,
thinking,
messages: history
});
// 4. A 200 with no dropped blocks means the kept thinking held.
console.log(`Dropped thinking blocks: ${third.input_transformations?.length ?? 0}`);
```
```csharp C#
using Anthropic.Models.Beta;
using Anthropic.Models.Beta.Messages;
using Model = Anthropic.Models.Messages.Model;
AnthropicClient client = new();
// Claude Fable 5.1 is the first model that checks sent-back thinking against the conversation.
const Model ModelId = Model.ClaudeFable5_1;
AnthropicBeta[] betas = [AnthropicBeta.Compact2026_09_04, AnthropicBeta.ThinkingBindingControls2026_08_01];
const string SystemPrompt = "You help plan a recipe app's release. Keep answers short.";
// With "error", a thinking block that fails the check makes the request fail with a 400.
BetaThinkingConfigAdaptive thinking = new()
{
BlockBinding = new() { PrefixMismatchBehavior = BetaThinkingPrefixMismatchBehavior.Error },
};
// 1. Have a short conversation with thinking on.
List<BetaMessageParam> history =
[
new() { Role = Role.User, Content = "What are the main entities in the app's data model?" },
];
var first = await client.Beta.Messages.Create(new MessageCreateParams
{
Model = ModelId,
MaxTokens = 8192,
System = SystemPrompt,
Betas = [.. betas],
Thinking = thinking,
Messages = history,
});
history.AddRange(
[
new()
{
Role = Role.Assistant,
Content = first.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
},
new()
{
Role = Role.User,
Content = "Testing starts on Tuesday, March 3, 2026, takes 10 weekdays, and pauses on March 9 and March 16. On which date does it end?",
},
]);
var second = await client.Beta.Messages.Create(new MessageCreateParams
{
Model = ModelId,
MaxTokens = 8192,
System = SystemPrompt,
Betas = [.. betas],
Thinking = thinking,
Messages = history,
});
history.Add(new()
{
Role = Role.Assistant,
Content = second.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
});
var thinkingBlocks = second.Content.Count(block => block.TryPickThinking(out _));
Console.WriteLine($"Thinking blocks in the kept turn: {thinkingBlocks}");
// 2. Summarize the first turn. The second turn stays out of the request.
var summary = await client.Beta.Messages.Create(new MessageCreateParams
{
Model = ModelId,
MaxTokens = 4096,
System = SystemPrompt,
Betas = [.. betas],
Thinking = thinking,
Messages = history[..2],
Compaction = new BetaCompactionConfig(), // type defaults to "summarize"
});
if (summary.StopReason != BetaStopReason.Compaction)
{
throw new InvalidOperationException($"No summary: {summary.StopReason?.Raw()}");
}
// 3. Put the block in front of the kept turn and ask the next question.
history =
[
new()
{
Role = Role.Assistant,
Cut at 300 lines. The page has the rest.
build-with-claude/compaction-threshold New page · 3457 lines, new page
## Compatibility ## How compaction works ## Basic usage ## Parameters ### Trigger configuration ### Custom summarization instructions ### Pausing after compaction #### Enforcing a total token budget ## Working with compaction blocks ### Passing compaction blocks back ### Streaming ### Prompt caching #### Maximizing cache hits with system prompts ## Understanding usage ## Combining with other features ### Server tools ### Token counting ## Examples ## Current limitations ## Next steps
A whole new page. There's nothing to diff it against, so here is what it says.
---
title: Compaction at a token threshold
url: https://platform.claude.com/docs/en/build-with-claude/compaction-threshold
description: Have the API summarize older context automatically, inside an ordinary request, when the conversation reaches a token threshold you set.
---
## Compatibility
- Status: Beta
- [Beta header](https://platform.claude.com/docs/en/api/beta-headers): `compact-2026-01-12`
- [ZDR](https://platform.claude.com/docs/en/manage-claude/api-and-data-retention): eligible (excludes [Covered Models](https://platform.claude.com/docs/en/manage-claude/api-and-data-retention#model-specific-data-retention-requirements))
- Supported models: `claude-fable-5-1`, `claude-mythos-5-1`, `claude-fable-5`, `claude-mythos-5`, `claude-mythos-preview`, `claude-opus-5`, `claude-opus-4-8`, `claude-opus-4-7`, `claude-opus-4-6`, `claude-sonnet-5`, `claude-sonnet-4-6`
- Platforms: Claude API (beta), Claude Platform on AWS (beta), Amazon Bedrock (beta), Google Cloud (beta), Microsoft Foundry (beta)
Threshold compaction is the automatic kind of compaction: you set a token threshold on your ordinary requests, and the API summarizes older context partway through a request once the threshold is reached. It is supported alongside on-demand compaction, where you decide when the summary is written (see [Compaction on demand](https://platform.claude.com/docs/en/build-with-claude/compaction-on-demand)). To choose between them, see [Choose how to compact](https://platform.claude.com/docs/en/build-with-claude/compaction#choose-how-to-compact).
Compaction extends the effective context length for long-running conversations and tasks by automatically summarizing older context when approaching the context window limit. It also keeps the active context small: as a conversation grows, response quality degrades, so compaction replaces older content with a concise summary.
<Tip>
For a deeper look at why long contexts degrade and how compaction helps, see [Effective context engineering](https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents).
</Tip>
This is ideal for:
* Chat-based, multi-turn conversations where you want users to use one chat for a long period of time
* Task-oriented prompts that require a lot of follow-up work (often tool use) that might exceed the context window
## How compaction works
When compaction is enabled, Claude automatically summarizes your conversation when it reaches the configured token threshold. The API:
1. Detects when input tokens reach your specified trigger threshold.
2. Generates a summary of the current conversation.
3. Creates a `compaction` block containing the summary.
4. Continues the response with the compacted context.
On subsequent requests, append the response to your messages. The API automatically drops all content blocks prior to the `compaction` block, continuing the conversation from the summary.

## Basic usage
Enable compaction by adding the `compact_20260112` strategy to `context_management.edits` in your Messages API request.
<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: compact-2026-01-12" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-5",
"max_tokens": 4096,
"messages": [
{
"role": "user",
"content": "Help me build a website"
}
],
"context_management": {
"edits": [
{
"type": "compact_20260112"
}
]
}
}'
```
```bash CLI
ant beta:messages create --beta compact-2026-01-12 <<'YAML'
model: claude-opus-5
max_tokens: 4096
messages:
- role: user
content: Help me build a website
context_management:
edits:
- type: compact_20260112
YAML
```
```python Python
client = anthropic.Anthropic()
messages = [{"role": "user", "content": "Help me build a website"}]
response = client.beta.messages.create(
betas=["compact-2026-01-12"],
model="claude-opus-5",
max_tokens=4096,
messages=messages,
context_management={"edits": [{"type": "compact_20260112"}]},
)
# Append the response (including any compaction block) to continue the conversation
messages.append({"role": "assistant", "content": response.content})
```
```typescript TypeScript
const client = new Anthropic();
const messages: Anthropic.Beta.Messages.BetaMessageParam[] = [
{ role: "user", content: "Help me build a website" }
];
const response = await client.beta.messages.create({
betas: ["compact-2026-01-12"],
model: "claude-opus-5",
max_tokens: 4096,
messages,
context_management: {
edits: [
{
type: "compact_20260112"
}
]
}
});
// Append the response (including any compaction block) to continue the conversation
messages.push({
role: "assistant",
content: response.content
});
```
```csharp C#
AnthropicClient client = new();
var messages = new List<BetaMessageParam>
{
new() { Role = Role.User, Content = "Help me build a website" }
};
var parameters = new MessageCreateParams
{
Betas = ["compact-2026-01-12"],
Model = "claude-opus-5",
MaxTokens = 4096,
Messages = messages,
ContextManagement = new BetaContextManagementConfig
{
Edits = [new BetaCompact20260112Edit()]
}
};
var response = await client.Beta.Messages.Create(parameters);
// Append the response (including any compaction block) to continue the conversation
messages.Add(new BetaMessageParam
{
Role = Role.Assistant,
Content = response.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList()
});
Console.WriteLine(response);
```
```go Go
client := anthropic.NewClient()
messages := []anthropic.BetaMessageParam{
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Help me build a website")),
}
response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus5,
MaxTokens: 4096,
Messages: messages,
ContextManagement: anthropic.BetaContextManagementConfigParam{
Edits: []anthropic.BetaContextManagementConfigEditUnionParam{
{OfCompact20260112: &anthropic.BetaCompact20260112EditParam{}},
},
},
Betas: []anthropic.AnthropicBeta{"compact-2026-01-12"},
})
if err != nil {
log.Fatal(err)
}
// Append the response (including any compaction block) to continue the conversation
messages = append(messages, response.ToParam())
fmt.Println(response)
```
```java Java
import com.anthropic.models.beta.messages.BetaContextManagementConfig;
import com.anthropic.models.beta.messages.BetaCompact20260112Edit;
// ...
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.addBeta("compact-2026-01-12")
.model("claude-opus-5")
.maxTokens(4096L)
.addUserMessage("Help me build a website")
.contextManagement(BetaContextManagementConfig.builder()
.addEdit(BetaCompact20260112Edit.builder().build())
.build())
.build();
BetaMessage response = client.beta().messages().create(params);
// Append the response (including any compaction block) to continue the conversation
// by including it in the next request's messages
System.out.println(response);
```
```php PHP
$client = new Client();
$messages = [
['role' => 'user', 'content' => 'Help me build a website']
];
$response = $client->beta->messages->create(
maxTokens: 4096,
messages: $messages,
model: 'claude-opus-5',
betas: ['compact-2026-01-12'],
contextManagement: [
'edits' => [
['type' => 'compact_20260112']
]
]
);
// Append the response (including any compaction block) to continue the conversation
$messages[] = ['role' => 'assistant', 'content' => $response->content];
echo json_encode($response, JSON_PRETTY_PRINT), PHP_EOL;
```
```ruby Ruby
client = Anthropic::Client.new
messages = [
{ role: "user", content: "Help me build a website" }
]
response = client.beta.messages.create(
betas: ["compact-2026-01-12"],
model: "claude-opus-5",
max_tokens: 4096,
messages: messages,
context_management: {
edits: [{ type: "compact_20260112" }]
}
)
# Append the response (including any compaction block) to continue the conversation
messages << { role: "assistant", content: response.content }
puts response
```
</CodeGroup>
## Parameters
| Parameter | Type | Default | Description |
| ------------------------ | ------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Required | Must be `"compact_20260112"` |
| `trigger` | object | `{"type": "input_tokens", "value": 150000}` | When to trigger compaction. `input_tokens` is the only supported trigger type. `value` must be at least 50,000 tokens. |
| `pause_after_compaction` | boolean | `false` | Whether to pause after generating the compaction summary |
| `instructions` | string | `null` | Custom summarization prompt. Completely replaces the default prompt when provided. |
### Trigger configuration
Configure when compaction triggers using the `trigger` parameter:
<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: compact-2026-01-12" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-5",
"max_tokens": 4096,
"messages": [
{
"role": "user",
"content": "Hello, Claude"
}
],
"context_management": {
"edits": [
{
"type": "compact_20260112",
"trigger": {
"type": "input_tokens",
"value": 150000
}
}
]
}
}'
Cut at 300 lines. The page has the rest.