## Compact on demand with the `compaction` parameter ### Request a summary ### Continue from the summary ### Write your own summarization prompt ### When no summary comes back ### How it fits with the rest of the API
The whole hunk
from line 39, old and new numbered
/
lines
from line 39
3939
4040
4141
42The previous steps describe threshold compaction, which most of this page covers. With the `compact-2026-09-04` beta header, you can instead request a summary on demand. That request is separate from your conversation turns and returns only the summary, so it can run in the background. When the block arrives, you swap it in for the messages it summarizes. See [Compact on demand with the `compaction` parameter](https://platform.claude.com/docs/en/build-with-claude/compaction#compact-on-demand-with-the-compaction-parameter).
43
4244## Basic usage
4345
4446Enable compaction by adding the `compact_20260112` strategy to `context_management.edits` in your Messages API request.
from line 3410
34083410 ```text wrap
34093411 Summarize the transcript inside <summary></summary> tags. Include relevant information in the summary for continuing the task in the next context window. Do not call any tools while writing this summary; respond with text only.
34103412 ```
3413
3414## Compact on demand with the `compaction` parameter
3415
3416The `compact-2026-09-04` beta adds a second way to compact. Threshold compaction summarizes partway through a request once the threshold you set is reached. With this beta, you instead send the top-level `compaction` parameter on a request of your choosing. The response contains a single signed `compaction` block and no reply. From then on, send that block first in `messages`, in place of the messages it summarizes, followed by any turns taken since. Claude sees the summary where those messages were. Everything after the summary reaches Claude unchanged. A threshold compaction block follows the messages it summarizes, but a signed block replaces them. Leaving the summarized messages in front of a signed block is a 400 error.
3417
3418Compacting this way gives you three things. First, you decide when to compact. Second, the summarization request can run in the background while the conversation continues on its full history, and you swap the block in when it arrives. This is often called async or background compaction. Third, you can keep recent turns word for word after the summary, which is often called keep-tail compaction. Models with [preserved thinking](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking) check earlier thinking blocks against the conversation. On those models, the thinking in turns that follow the summary, from either pattern, can stay valid after the swap, under the conditions in [Continue from the summary](https://platform.claude.com/docs/en/build-with-claude/compaction#continue-from-the-summary). That lets a long-running agent keep its train of thought. Use threshold compaction when you want the API to manage context inside ordinary requests. Use the `compaction` parameter when your application needs to control when compaction happens, can't pause while a summary is written, or must keep recent turns and their thinking after the summary.
3419
3420Send 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. On-demand compaction is available on the Claude API only, on 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, and Claude Sonnet 4.6. You can also 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.
3421
3422### Request a summary
3423
3424Send 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 on models with preserved thinking, the turns you keep stay valid only if they match:
3425
3426<CodeGroup exclude="python, typescript, csharp, go, java, php, ruby">
3427 ```bash cURL
3428 curl https://api.anthropic.com/v1/messages \
3429 -H "x-api-key: $ANTHROPIC_API_KEY" \
3430 -H "anthropic-version: 2023-06-01" \
3431 -H "anthropic-beta: compact-2026-09-04" \
3432 -H "content-type: application/json" \
3433 -d '{
3434 "model": "claude-opus-5",
3435 "max_tokens": 4096,
3436 "messages": [
3437 {"role": "user", "content": "I am building a recipe app. Help me name the main entities in the data model."},
3438 {"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."},
3439 {"role": "user", "content": "Good. Now suggest field names for Recipe."}
3440 ],
3441 "compaction": {"type": "summarize"}
3442 }'
3443 ```
3444
3445 <MultiFileExample language="cli" label="CLI">
3446 ```bash CLI
3447 ant beta:messages create --beta compact-2026-09-04 < request.yaml
3448 ```
3449
3450 <File filename="request.yaml">
3451 ```yaml
3452 model: claude-opus-5
3453 max_tokens: 4096
3454 messages:
3455 - role: user
3456 content: I am building a recipe app. Help me name the main entities in the data model.
3457 - role: assistant
3458 content: Start with Recipe, Ingredient, and Step. Add a RecipeIngredient entry that holds the quantity and unit for each ingredient in a recipe.
3459 - role: user
3460 content: Good. Now suggest field names for Recipe.
3461 compaction:
3462 type: summarize
3463 ```
3464 </File>
3465 </MultiFileExample>
3466</CodeGroup>
3467
3468```json Response
3469{
3470 "id": "msg_013Zva2CMHLNnXjNJJKqJ2EF",
3471 "type": "message",
3472 "role": "assistant",
3473 "model": "claude-opus-5",
3474 "content": [
3475 {
3476 "type": "compaction",
3477 "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.",
3478 "signature": "EuYBCkQY..."
3479 }
3480 ],
3481 "stop_reason": "compaction",
3482 "usage": {
3483 "input_tokens": 0,
3484 "output_tokens": 0,
3485 "iterations": [{ "type": "compaction", "input_tokens": 144, "output_tokens": 276 }]
3486 }
3487}
3488```
3489
3490The 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. It is billed and rate-limited like any other request, and `usage.iterations` reports it as the `compaction` entry. The top-level `input_tokens` and `output_tokens` are zero because no reply was generated.
3491
3492If 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.
3493
3494When 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.
3495
3496### Continue from the summary
3497
3498In your history, replace the messages you sent with the returned assistant message. Keep the `compaction` block exactly as the API returned it, including its `signature`. Send it first on every later request, with the beta header:
3499
3500```json
3501{
3502 "model": "claude-opus-5",
3503 "max_tokens": 2048,
3504 "messages": [
3505 {
3506 "role": "assistant",
3507 "content": [
3508 {
3509 "type": "compaction",
3510 "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.",
3511 "signature": "EuYBCkQY..."
3512 }
3513 ]
3514 },
3515 {
3516 "role": "assistant",
3517 "content": "For Recipe, use title, description, servings, prep_minutes, and cook_minutes. Add created_at and updated_at timestamps."
3518 },
3519 { "role": "user", "content": "Now do the same for Ingredient." }
3520 ]
3521}
3522```
3523
3524Here the second `assistant` message is the reply to the last summarized `user` turn. It arrived while the summary was being written, so it was not among the messages summarized. Two `assistant` messages in a row are fine here, because the block still comes first.
3525
3526The API puts the summary where the block stands and passes every later message to Claude unchanged. Follow these rules:
3527
3528* Put the block first in `messages`, either as an `assistant` message of its own or as the first content block of the first message, whether that is a `user` or `assistant` message.
3529* Remove the summarized messages. If any remain in front of the block, the request returns a 400 error (`compaction_block_misplaced`). If any remain after it, the API doesn't reject the request for that reason and sends them to the model again.
3530* Send exactly one `compaction` block per request, on every later request. A request without the block reaches Claude without the summary.
3531
3532To 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.
3533
3534If the conversation took more turns while a background summary request ran, drop exactly the messages you sent in the compaction request from the front of your history. Put the returned message in their place, and keep everything appended since:
3535
3536```python
3537# sent_count = len(messages sent in the compaction request)
3538# response = that request's result, arriving while the agent kept working
3539if response.stop_reason == "compaction":
3540 compaction_message = {"role": "assistant", "content": response.content}
3541 history = [compaction_message] + history[sent_count:]
3542# Otherwise keep the full history and try again later (see "When no summary comes back").
3543```
3544
3545Don't edit your history between sending the compaction request and making the swap, and make the swap on the first request after the block arrives. That way, thinking produced while the summary was being written stays valid.
3546
3547On models with preserved thinking, the thinking blocks in the kept turns stay valid as long as both of these conditions hold:
3548
3549* The kept turns directly followed the summarized messages.
3550* The `system` parameter and the `tools` not marked `defer_loading: true` are unchanged from the compaction request.
3551
3552The first condition also rules out a first kept message that the API would merge into the last summarized message: one with the same role as the last summarized message, or a `role: "system"` message. The simplest way to meet it is to compact exactly the `messages` of a request you already made. 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.
3553
3554A later request can use a different model, `system`, or `tools` 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.
3555
3556To compact a conversation that already starts with a block, send `compaction` again. The new block summarizes the old summary and everything after it. From then on, send only the newest block.
3557
3558### Write your own summarization prompt
3559
3560Without `instructions`, the API uses its own summarization prompt. A non-blank `instructions` string (up to 16,384 characters) replaces that prompt entirely, as it does for threshold compaction (see [Custom summarization instructions](https://platform.claude.com/docs/en/build-with-claude/compaction#custom-summarization-instructions)). For example:
3561
3562```json
3563{
3564 "compaction": {
3565 "type": "summarize",
3566 "instructions": "Summarize this recipe app design conversation. Preserve every entity and field name agreed so far, and the user's latest open request. Do not call tools; respond with the summary text only."
3567 }
3568}
3569```
3570
3571The summarizer reads the whole conversation, earlier thinking included, with or without `instructions`. That differs from threshold compaction on Claude Fable 5.1 and Claude Mythos 5.1, where custom `instructions` leave earlier thinking out. In your `instructions`, say what the summary must retain and tell the model not to call tools. The summarization call runs under the same safeguards as any other request.
3572
3573### When no summary comes back
3574
3575A summary is produced only when the summarization call ends normally with text and no tool call. Otherwise, the response is still a 200 with empty `content`. The call is still billed and reported in `usage.iterations`, with zero usage when no call could be made. The `stop_reason` is the one the summarization call ended with:
3576
3577* `"max_tokens"`: the summary was cut off.
3578* `"model_context_window_exceeded"`: there was no room for the summarization prompt.
3579* `"refusal"`: the request was declined. It is subject to the same safeguards as your other requests, and [`stop_details`](https://platform.claude.com/docs/en/build-with-claude/handling-stop-reasons#refusal) identifies the policy category behind it.
3580* `"tool_use"`: the model called a tool instead of writing the summary.
3581* `"end_turn"`: the call returned no text.
3582
3583Resend with a larger `max_tokens` after `"max_tokens"`, with shorter `instructions` or fewer messages after `"model_context_window_exceeded"`, or with `instructions` that tell the model not to call tools after `"tool_use"`. You can also continue without a summary.
3584
3585A transient server problem while producing a block, or while reading one you sent back, returns a retryable 529 `overloaded_error` with `error.details.error_code` set to `compaction_unavailable`. Retry the request. Other rejections specific to this beta are 400 errors, and most have a message that says what to remove or resend. The exception is a request that leaves out the beta header: it fails with a generic validation error, such as `compaction: Extra inputs are not permitted`, that doesn't mention the header. Some also carry an `error.details.error_code` that starts with `compaction_`, mostly the errors about the block itself: an altered, misplaced, or duplicated block, or a request with nothing left to summarize. Parameter errors, such as a field that can't be combined with `compaction`, carry the message only.
3586
3587### How it fits with the rest of the API
3588
3589* **Threshold compaction and context editing.** You can't send `compaction` and `context_management` on the same request. Threshold compaction (`compact_20260112`) can't run on a request that carries a signed block.
3590* **Prompt caching.** `cache_control` on the block places a breakpoint after the summary.
3591* **Mid-conversation system messages and tool changes.** `role: "system"` messages inside the summarized range are summarized. What they declared stops applying once the block replaces them. If an instruction or a [tool change](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#mid-conversation-tool-changes) still matters, state it again in a `role: "system"` message. Send that message right after your next new `user` turn, which comes after the kept turns, and leave it in your history from then on. Don't put it between the block and the kept turns, because that breaks the kept turns' thinking.
3592* **Task budgets.** Don't send the `remaining` value of a [task budget](https://platform.claude.com/docs/en/build-with-claude/task-budgets) (`output_config.task_budget.remaining`) with `compaction` or on requests that carry the block. Doing so returns a 400 error.
3593* **Token counting.** The [token counting](https://platform.claude.com/docs/en/build-with-claude/token-counting) endpoint ignores the `compaction` parameter.
3594* **Content the summary can't carry.** Images, documents, `container_upload` blocks, and fetched URLs inside the summarized messages are gone once the block replaces them. Restate or re-upload anything a later turn still needs.
34113595
34123596## Next steps
34133597