Set Thinking Budgets
Controlling How Hard Claude Works
Lesson 151 showed how to turn thinking on. This lesson covers the dial that controls how much effort goes into a response. On current models, that dial is the effort parameter. On older models, and in the legacy configuration, the dial was a token budget for reasoning. Understanding both matters, because existing code and older examples still use budgets, and the migration from one to the other changes the behavior of every request.
The main idea is simple. More effort or a larger budget usually produces more careful reasoning, at a higher cost and with more latency. Less effort is faster and cheaper, and it can be the right choice for simple tasks. The skill is matching the setting to the task, and checking that the match holds with measurements rather than assumptions.
The Effort Parameter
Effort is set in output_config, not inside the thinking object:
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=4096,
thinking={"type": "adaptive"},
output_config={"effort": "high"},
messages=[...],
)
The documentation describes effort levels that range from the lowest, which minimizes thinking and skips it for simple tasks, through to higher levels that allow deeper reasoning. Medium is the default on Claude Opus 5.5 and Claude Haiku 5.5. Not every level is accepted by every model, and the top levels are limited to specific models, so the effort documentation's support table is the authority for any given model.
Effort is more than a thinking setting. The documentation notes that it applies to all output tokens: text, tool calls, and thinking. A low effort therefore produces terser text, and fewer and simpler tool calls, not only less reasoning. That breadth is useful, and it is also a reason to test the effect on the whole response rather than on its reasoning alone.
Effort Works Through Adaptive Thinking
The effort level is a steering signal, not a hard limit. Claude decides how much to think for each request, and the level shifts that decision. A simple question at high effort may still receive a short answer, and a hard question at low effort may still receive careful reasoning when the model judges it necessary. The level sets the default disposition, and the model adjusts within it.
This is different from a budget, which is a ceiling on reasoning tokens. A budget says "reason up to this many tokens". Effort says "work this hard, and decide how much reasoning that needs".
The Legacy Budget
On models that still accept manual extended thinking, the configuration sets an explicit reasoning budget:
thinking={"type": "enabled", "budget_tokens": 10000}
budget_tokens sets the target for reasoning tokens, and it must be at least 1024 and less than max_tokens, unless interleaved thinking is in use. It is deprecated on some models and not accepted on others (Lesson 151 lists which). The budget is a maximum that the model aims to use, not a guarantee that it will use all of it.
Budgets have one practical advantage: they are predictable. A budget of 10,000 bounds the reasoning cost of a request. Adaptive thinking is less predictable in cost, because the model decides how much to think, which is why cost budgets belong in monitoring (Lessons 125 and 126) rather than in the configuration alone.
Migrating from a Budget to an Effort
The documentation gives a direct mapping for migration. Remove budget_tokens, set thinking to adaptive, and choose an effort level that approximates the old depth. The example below turns a legacy budget into a proposed effort level, using a simple band table as a starting point:
"""thinking_budgets.py: move from manual budgets to effort levels, and pick effort per task."""
BUDGET_BANDS = [ # (upper bound on the old budget in tokens, proposed effort)
(2048, "low"),
(8192, "medium"),
(16000, "high"),
(float("inf"), "max"),
]
TASK_EFFORT = {
"classify": "low",
"extract": "low",
"summarize": "medium",
"draft": "medium",
"analyze": "high",
"debug": "high",
"plan": "high",
}
def budget_to_effort(budget: int) -> str:
"""A starting point for migration only. Confirm each choice by measuring quality and cost."""
for upper, effort in BUDGET_BANDS:
if budget <= upper:
return effort
return "max"
def effort_for_task(task: str) -> str:
return TASK_EFFORT.get(task, "medium")
if __name__ == "__main__":
for budget in [1024, 4096, 10000, 32000]:
print(f"budget {budget} -> effort {budget_to_effort(budget)}")
for task in ["classify", "summarize", "debug", "poem"]:
print(f"task {task} -> effort {effort_for_task(task)}")
Output:
budget 1024 -> effort low
budget 4096 -> effort medium
budget 10000 -> effort high
budget 32000 -> effort max
task classify -> effort low
task summarize -> effort medium
task debug -> effort high
task poem -> effort medium
How It Works
- The bands turn a number into a category. A budget of 10,000 falls in the band up to 16,000, so it maps to high. The documentation's own migration example uses high for a legacy budget of 10,000, which agrees with this table.
- The task table sets effort by purpose. Classification and extraction are routine, so they run at low effort. Analysis and debugging benefit from more reasoning, so they run at high.
- Unknown tasks fall back to medium, which is the default on the current flagship model, so a new task is not silently set to an extreme.
- The mapping is a starting point, not a conversion. A budget and an effort level are not equivalent measures, so the table must be checked against real results.
Measuring the Effect
Choosing an effort level is an empirical question. The right test runs the same set of representative tasks at two or three levels, and compares three things: the quality of the answers, the cost of the tokens, and the latency. The quality measure depends on the task, and it may be a rubric, a set of expected labels (Lesson 110), or a human review of a sample.
A measurement harness can be as small as a loop that records the usage fields for each run, so cost comparisons come from data. The harness below records usage for one prompt at each effort level, using a function that stands in for the API call so the structure can be shown offline:
"""effort_trials.py: record cost for the same prompt at several effort levels."""
from dataclasses import dataclass
@dataclass
class Trial:
effort: str
input_tokens: int
output_tokens: int
def run_trials(call, prompt: str, levels: list[str]) -> list[Trial]:
trials = []
for level in levels:
usage = call(prompt, level) # Returns the response's usage fields
trials.append(Trial(level, usage["input_tokens"], usage["output_tokens"]))
return trials
def fake_call(prompt: str, level: str) -> dict:
output = {"low": 180, "medium": 520, "high": 1400}[level]
return {"input_tokens": 90, "output_tokens": output}
if __name__ == "__main__":
for trial in run_trials(fake_call, "Plan a three-day study schedule.", ["low", "medium", "high"]):
print(f"{trial.effort:<6} input={trial.input_tokens} output={trial.output_tokens}")
Output:
low input=90 output=180
medium input=90 output=520
high input=90 output=1400
How It Works
run_trials()runs one prompt at several levels, and keeps the usage from each run. The same prompt makes the comparison fair.- The usage fields are the cost record. Output tokens rise sharply from low to high in this example, which is typical, and the input tokens stay the same because the prompt is the same.
- The fake call stands in for the API. The real call would pass the level as
output_config={"effort": level}and readresponse.usage. - Quality is measured separately, on the same outputs. A harness that records only cost can make a cheap setting look good when its answers are poor.
Choosing the Level
A workable policy starts with the lowest level that meets the quality bar on the representative tasks, and raises it only when a measured gap appears. Routine work such as classification, extraction, and formatting rarely needs more. Work where a wrong answer is expensive, such as analysis, code review, or planning, often justifies a higher level, because the extra reasoning reduces errors that are costly to fix later.
The cost of a higher level is paid on every request, so the decision should be revisited as prices and models change. Lesson 126 described how to estimate the break-even for caching, and the same discipline applies to effort: a setting that adds output tokens on every call needs to earn its place.
Summary
Effort is the current control for how hard Claude works, and it applies to text, tool calls, and thinking alike. A budget is the legacy control, a ceiling on reasoning that is predictable but deprecated on newer models. Migration maps budgets to effort levels, with the mapping treated as a starting point to be measured. Choosing a level is an empirical decision, made by comparing quality and cost across representative tasks. The next lesson turns to the thinking blocks themselves, which carry the reasoning these settings produce.