DEV Community

Cover image for Claude message threads: send only new messages on long agent loops
Sueun Cho
Sueun Cho

Posted on Edited on

Claude message threads: send only new messages on long agent loops

Message threads landed in the Claude Messages API in beta on Nov. 2, 2026. The API keeps the conversation on the server, so each later request sends only the new messages. Here is how to turn it on, what the request looks like before and after, what it speeds up, and which expiry rules you have to design around.

Turn it on

Every request of a thread needs the message-threads-2026-08-12 beta header and a thread parameter. The first request sends the full conversation and {"type": "create"}. Each later request sends {"type": "continue", "previous_message_id": ...} with the id of the latest response.

import anthropic

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: 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"],
)
Enter fullscreen mode Exit fullscreen mode

The SDK also has a thread helper, client.beta.messages.thread(...). It adds the beta header, builds the create and continue requests, and keeps the conversation in memory while the helper object exists.

Compare the request before and after

Without a thread, the Messages API is stateless, so you send the full conversational history every time:

import anthropic

client = anthropic.Anthropic()

system_prompt = "You are a support agent for Acme. Keep replies short."

# Turn 1: plain request, no thread
first = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    system=system_prompt,
    messages=[{"role": "user", "content": "Hi! I have a question about an order."}],
)

# Turn 2: resend system and the whole history
second = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    system=system_prompt,
    messages=[
        {"role": "user", "content": "Hi! I have a question about an order."},
        {"role": "assistant", "content": first.content},
        {"role": "user", "content": "How long does standard shipping usually take?"},
    ],
)
Enter fullscreen mode Exit fullscreen mode

With a thread, only the create request carries system and tools. Sending either one on a continue request, even as null, returns a 400. A group of fixed fields (model, tool_choice, thinking, output_config, context_management, the top-level cache_control and the set of anthropic-beta values, among others) must match the create request, or you get a 400 with thread_fingerprint_mismatch. max_tokens, stop_sequences, stream and metadata can change per turn. To change a fixed field, start a new thread.

On a continue request, send only new messages. The API does not check them against what it holds, so a message you resend can appear twice or trigger an error.

Dark code-editor style side-by-side diagram. Left: a 'stateless request' with a turn 1 box \(system, user\), a down arrow labeled 'turn 2', and a turn 2 box \(system, user, assistant, user\), with a bracket labeled 'full history every turn'. Right: a 'create' box \(system, tools, user\), a down arrow labeled 'previous response id' to a 'continue: new messages only' box \(user\), a clock beside it, and an orange arrow looping up to the create box labeled 'replay: new create'. A small sloth mascot with a 'Sueun' cap sits in the bottom right.

Know what it speeds up and what it doesn't

Without a thread, every turn uploads the whole conversation and the API validates and tokenizes all of it again. With a thread, most of that work covers only the new turn, so requests are smaller and time to first token stays lower and steadier as the conversation grows. Anthropic reports that with 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%.

Token usage does not change. Claude still reads the full conversation on every turn, and pricing and rate limits are unchanged. Prompt caching is not automatic either: you choose breakpoints, for example a top-level cache_control sent on every request of the thread.

Design around expiry and replay

A thread is best-effort. It expires 5 minutes after its last request, each stored response restarts those 5 minutes, and the API deletes a thread no more than 1 hour after its last use. When it can't continue, you get a 404 with error.details.error_code set to thread_not_found, and the response does not say why. Expiry is one cause. A turn the API served but did not store, a response still generating, a fallback model's response, or a different organization, workspace or user are others.

No response tells you whether a turn was stored. You find out on the next continue. So keep your own copy of every message and build the replay path from day one: send the full conversation with system and tools in a new create request, then continue from the new id. The SDK helper replays for you on thread_not_found, thread_already_continued and thread_continue_in_flight.

Three more traps:

  • Each response id can be continued once. Always continue from the latest id.
  • When streaming, the id arrives in message_start, but continue only after message_stop.
  • After stop_reason: "refusal", don't continue. Start a new thread that omits or rephrases that turn.

Threads also don't work with Message Batches or token counting, and they are not ZDR or HIPAA eligible. There is no API to list, read or delete a thread.

When I'd use it

I'd turn it on for active agent loops where each turn follows the last within minutes and the context is big: coding agents, computer use with screenshots, document-heavy work. I'd skip it where turns are more than 5 minutes apart or where ZDR or HIPAA rules apply. And I'd write the replay path before the happy path, because the thread is a cache of your conversation, not the record of it.

Top comments (0)