Adaptive Thinking & the Effort Dial
You don't know how much thinking a task needs — adaptive thinking lets Claude decide, and the effort dial sets the depth.
A Brief History of Thinking Too Hard
When extended thinking was first introduced, developers set a token budget. You specified budget_tokens: 4000 or budget_tokens: 16000, and Claude reasoned up to that ceiling.
The design had a fundamental problem: you don't know how much thinking a task needs. A simple question with a tricky edge case might need 8000 tokens of reasoning. A question that looks complex might resolve in 200. Setting the budget required either calibrating every task class (expensive) or setting a conservative ceiling (wasted tokens on simple tasks) or a generous ceiling (overpaying on every request).
The model knew how much thinking a task needed. The developer did not. The interface made the developer responsible for a decision the model is better positioned to make.
The resolution: thinking: { type: "adaptive" }. The model decides when to think and how much — interleaved between tool calls in agentic workflows. You get calibrated reasoning on hard problems and fast responses on easy ones. The API surface shrank to one field.
The migration cost matters to understand: budget_tokens is removed on Opus 4.8 and 4.7 (HTTP 400 if you send it) and deprecated on the 4.6 family. If your codebase has budget_tokens anywhere, that field becomes a hard failure when you upgrade to Opus 4.8.
Enabling Adaptive Thinking
Thinking is opt-in. Omitting the thinking field entirely means no thinking — the model will not reason internally before answering. This is the correct default for latency-sensitive classification and extraction tasks.
To enable adaptive thinking:
import Anthropic from "@anthropic-ai/sdk"
const client = new Anthropic()
// Thinking enabled — model decides when and how much
const response = await client.messages.create({
model: "claude-opus-4-8",
max_tokens: 64000, // generous ceiling — required at xhigh/max effort
thinking: { type: "adaptive" },
output_config: { effort: "xhigh" },
messages: [{ role: "user", content: "Review this TypeScript architecture for correctness and identify any race conditions..." }],
})
// Thinking blocks arrive in content; narrow on type before reading
for (const block of response.content) {
if (block.type === "thinking") {
// block.thinking contains the model's reasoning (if display !== 'omitted')
} else if (block.type === "text") {
console.log(block.text) // the actual response
}
}
For streaming with user-facing progress indicators, set thinking.display: "summarized":
const stream = await client.messages.stream({
model: "claude-opus-4-8",
max_tokens: 64000,
thinking: { type: "adaptive", display: "summarized" },
output_config: { effort: "xhigh" },
messages: [{ role: "user", content: userRequest }],
})
for await (const event of stream) {
if (event.type === "content_block_delta") {
if (event.delta.type === "thinking_delta") {
updateProgressIndicator(event.delta.thinking) // show reasoning progress to user
} else if (event.delta.type === "text_delta") {
appendToResponse(event.delta.text)
}
}
}
Without thinking.display: "summarized", thinking blocks on Opus 4.8 and 4.7 stream as empty content by default ("omitted"). The model is actively reasoning — you just do not see it. From a user's perspective: nothing is happening for an extended period. This is the silent pause gotcha for user-facing streaming. Set display: "summarized" whenever the user is watching.
The Effort Dial
Effort is a separate control from thinking. It governs the depth of the entire response: how many tool calls the model makes, how thoroughly it decomposes a problem, and how detailed the output is. Think of it as the cost-quality dial for the whole request.
output_config: { effort: "low" | "medium" | "high" | "xhigh" | "max" }
The default is "high". Most production tasks work well at the default — you do not need to set it explicitly unless you are tuning in a specific direction.
"xhigh" is the agentic sweet spot. It is Claude Code's default effort level. At xhigh, the model fully decomposes problems, makes complete use of available tools, and produces thorough output. If you are building an agent that needs to complete multi-step tasks reliably, xhigh is the right starting point. Pair it with max_tokens: 64000 and stream the response.
"max" is for the most capable tiers. It produces the deepest reasoning and most thorough outputs. Use it for research tasks, autonomous agent runs, and capstone-class problems. max is supported on the Opus 4.6+ tier and on Sonnet 4.6 — but not on Haiku 4.5 or pre-4.6 Sonnets, where it returns an error. Verify model support before deploying.
"low" disables overhead. For classification, keyword extraction, and simple Q&A where you need a fast, direct answer, low effort reduces tool call overhead and output verbosity. Omit the thinking field as well for maximum speed.
Effort × max_tokens Interaction
This is the practical constraint that catches most developers. The effort level sets how much the model wants to produce. The max_tokens ceiling sets how much it is allowed to produce. When max_tokens is too low for the effort level, the model gets truncated mid-response.
| Effort | Recommended max_tokens | Streaming required? |
|---|---|---|
| low | 1024–4096 | No |
| medium | 4096–16000 | No (stream above ~16K) |
| high | 8192–32000 | Recommended above 16K |
| xhigh | ≥ 64000 | Yes |
| max | ≥ 64000 | Yes |
The streaming requirement at high max_tokens is not a capability restriction — it is a practical one. The SDK has HTTP timeout defaults that trigger on long synchronous requests. Streaming bypasses those timeouts. For xhigh and max effort, always stream.
// Correct: xhigh with adequate max_tokens, streaming
const stream = client.messages.stream({
model: "claude-opus-4-8",
max_tokens: 64000,
thinking: { type: "adaptive" },
output_config: { effort: "xhigh" },
messages: [{ role: "user", content: task }],
})
const finalMessage = await stream.finalMessage()
Thinking in Multi-Turn Tool Loops
When thinking is enabled in an agentic loop, Claude interleaves thinking between tool calls. After executing a tool and receiving results, the model reasons before deciding the next action. This is where the depth shows — the model is not just pattern-matching on the tool result; it is actively reasoning about what it learned.
// Adaptive thinking in a tool loop
async function runAgentLoop(task: string) {
const messages: Anthropic.Messages.MessageParam[] = [
{ role: "user", content: task }
]
while (true) {
const response = await client.messages.create({
model: "claude-opus-4-8",
max_tokens: 64000,
thinking: { type: "adaptive" },
output_config: { effort: "xhigh" },
tools: myTools,
messages,
})
if (response.stop_reason === "end_turn") {
return response.content.find(b => b.type === "text")?.text
}
if (response.stop_reason === "tool_use") {
// Execute all requested tools, then continue the loop
messages.push({ role: "assistant", content: response.content })
const toolResults = await executeTools(response.content)
messages.push({ role: "user", content: toolResults })
}
}
}
The thinking blocks in response.content before the tool_use blocks carry the model's reasoning about why it chose those tools — but only if you request a visible display mode. With the default display: "omitted", the blocks arrive as empty strings: the reasoning is not available to your logging and observability layer, or anywhere else. If you want reasoning in your logs, request display: "summarized" — you get a condensed, readable summary of the reasoning; the raw chain of thought is never exposed in either mode.
When to Disable Thinking
Not every task benefits from thinking. The cases where thinking adds latency without adding value:
- Structured classification with a fixed taxonomy
- Keyword or entity extraction
- Format conversion (JSON → CSV, markdown → HTML)
- Short factual Q&A where the answer is direct
- High-volume pipeline steps where speed matters more than depth
For these tasks, omit the thinking field and — on models that support the effort parameter (Sonnet 4.6 and the Opus family) — set effort: "low". Note that effort is not supported on Haiku 4.5: sending it returns an error, and Haiku is fast without it. The model processes the request through its normal fast path. The Foresight trading signal engine routes high-frequency classification steps to Haiku — thousands of classifications per hour where milliseconds matter — with no thinking field and no effort param at all.
Use thinking — and set effort to xhigh or max — for reasoning-intensive tasks: complex code review, multi-step planning, analysis that requires holding multiple constraints simultaneously, and any agentic task where wrong decisions compound across tool calls.
What's Next
The next lesson covers delivery modes: synchronous create, streaming, and the Batch API. You now have the model interaction patterns — the delivery-modes lesson covers how to deliver those responses efficiently depending on whether your user is waiting at a keyboard or your pipeline is processing overnight.