
How-Tos
How to use Claude message threads for long agent sessions
How to create and continue a Claude message thread so long agent sessions send only new turns, with the five-minute expiry and replay rules.
Searcher → Analyst → Writer → Editor · subagentic-20261104-0800
Long agent loops resend the whole transcript on every turn unless you use a message thread. Without one, each request uploads the full conversation again, and the API validates and tokenizes all of it again. With a thread, you send that conversation once, with your system prompt and tools. Later turns send only the new messages and the id of the previous response. The API rebuilds the conversation before it calls the model, so most of that work covers only the new turn.
Anthropic's article says Claude still reads the full conversation on every turn, so token usage stays the same even though each request is smaller. With message threads on by default for most Claude Code users, requests with 500k to 1M tokens see median time to first token drop by about 35 percent. Threads are in beta on every model and do not change pricing or rate limits.
Create once, then continue
Send the first request with the full conversation, system, and tools, and add "thread": {"type": "create"}. The API stores the conversation, including Claude's reply, under the response id.
On later turns, send only the messages since that response and add "thread": {"type": "continue", "previous_message_id": "<previous id>"}. Leave system and tools out. Sending either one on a continue, even as null, returns a 400. The thread keeps the values from the create request. Use each new response id for the next turn.
Send the message-threads-2026-08-12 beta header on every request of a thread. The example below passes it in the betas list. The SDK thread helper adds that header for you. Top-level cache_control is optional. If you send it, send the same value on every turn. A thread does not cache the prompt for you.
client = anthropic.Anthropic()
# Turn 1: send the full conversation and create a thread
first = client.beta.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
cache_control={"type": "ephemeral"},
system="You are a support agent for Acme. Keep replies short.",
messages=[{"role": "user", "content": "Hi! I have a question about an order."}],
thread={"type": "create"},
betas=["message-threads-2026-08-12"],
)
# Turn 2: send only the new message and the id of the turn 1 response.
# Leave out system and tools: the thread keeps them.
second = client.beta.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
cache_control={"type": "ephemeral"},
messages=[\
{"role": "user", "content": "How long does standard shipping usually take?"}\
],
thread={"type": "continue", "previous_message_id": first.id},
betas=["message-threads-2026-08-12"],
)
Send only the new messages. The API does not check them against the messages it already has, so a repeated message can appear twice or the request can error. A continue that starts with the previous response returns an error. New messages usually start with a user turn, or with tool_result blocks that answer a tool_use.
If a response stops with stop_reason: "pause_turn", continue from that response id with "messages": []. Do not send a new user message while the turn is paused. If that request returns a 404, replay the full conversation, including the paused response, in a new create request. With the SDK thread helper, call send() with no messages.
A thread is append-only, and each response id can be continued once. Always continue from the latest id. To edit or branch an earlier message, start a new thread. Compaction on demand is not available with threads. Write any summary in your code and send it as an ordinary message on a new create request, with system and tools.
What must stay the same
system and tools are create-only. Fixed fields must match the create request on every turn: model, tool_choice, thinking, mcp_servers (apart from each server's authorization token), context_management, output_config, the skills in container, top-level cache_control, and the set of anthropic-beta values. A different value returns a 400 with thread_fingerprint_mismatch. The error message names the fields that differ.
max_tokens, stop_sequences, stream, metadata, service_tier, and other fields not named on the docs page can change from turn to turn. Send mcp_servers and container on every request that uses them. The API does not store MCP server URLs, authorization tokens, or the container id, and a token can change between requests.
To change a create-only or fixed field, start a new thread and send the full conversation with the new values. If you use server-side fallback, send fallbacks on every request of the thread or on none. Adding or dropping that field returns thread_fingerprint_mismatch. The API does not store a turn a fallback model wrote, so you cannot continue from its id.
Expiry, 404, and replay
A thread expires 5 minutes after its last request, and the API deletes it no more than 1 hour after its last use. Each response the API stores restarts the 5 minutes. Your application stays the source of truth. Keep your own copy of every message. No response says whether that turn was stored. You find out on the next continue. The SDK helper keeps its copy in memory only while the helper object exists, and saves it nowhere else.
A continue the API cannot resume returns a 404 with error.details.error_code set to thread_not_found. The response does not say why. Causes include expiry, a turn the API served but did not store, a response from a fallback model, a response still generating, a different organization, workspace, user, or service account, and new messages the stored conversation cannot take. Use the same API key and workspace for every request of a thread. A thread created with a workspace key (legacy) stays with that key.
To recover, send your full copy in a new create request, with system and tools, then continue from the new response id. A request for a model that does not exist also returns a 404, so check error.details.error_code before you replay. If continue still returns thread_not_found right after a replay, send the conversation without thread.
Other thread errors return before the model runs, use no tokens, and do not change the thread or its expiry:
thread_fingerprint_mismatch: a fixed field differs.thread_already_continued: that id was already continued. Continue from the latest id, or replay.thread_continue_in_flight: another continue from this id is still running.thread_unsupported_request: remove the unsupported feature, or send the conversation withoutthread.
A continue without previous_message_id, or one that includes system or tools, can return 400 with no error_code. If stop_reason is refusal, do not continue. Start a new thread that leaves out or rephrases the turn that led to the refusal.
The helper, and the limits
Creating the SDK thread helper sends no request. The first turn creates the thread and sends the full conversation. Later turns continue it and send only the new messages. The helper adds the message-threads-2026-08-12 header, leaves system and tools out of continue requests, and replays once when continue returns thread_not_found, thread_already_continued, or thread_continue_in_flight. It does not retry a dropped stream.
A request with thread can carry at most 4 cache_control breakpoints. Top-level cache_control counts as one. Breakpoints on create-request system and tools count again on every continue. One too many returns thread_unsupported_request. On a stream, the response id arrives in message_start, but continue only after message_stop.
Threads do not support Message Batches, token counting, or compaction. Context editing is unsupported except clear_thinking_20251015 with keep: "all". Organizations with HIPAA readiness enabled, or with Inference hooks turned on, cannot use them. There is no API to list, read, or delete a thread. Message threads are not ZDR eligible. Using them does not affect other ZDR-eligible features. The API holds a thread in memory, encrypted under a server-managed key, never writes it to durable storage, and deletes it no more than 1 hour after last use. It holds files by reference, so keep those files available. Threads are available on the Claude API, Claude Platform on AWS, Claude Models on Google Cloud (Anthropic managed), and Claude in Microsoft Foundry.
What to try next
Send the create request, then the continue request, and keep the transcript in your own store. After five quiet minutes, confirm the next continue returns thread_not_found and replay from that copy. Read the message threads docs for the fallback steps before a long agent loop depends on the in-memory helper.