작업 예산
작업 예산 (Task budgets)
작업 예산(task budgets)은 긴 에이전트 실행 동안 전체 에이전트 루프에 쓸 수 있는 토큰을 Claude에게 알려주는 기능이에요. 모델은 실시간으로 줄어드는 카운트다운을 보고 작업 우선순위를 정하고, 예산이 소진되어 가면서도 자연스럽게 마무리할 수 있어요. beta 기능이라 task-budgets-2026-03-13 베타 헤더를 설정해 사용해요.
출처: 문서
본문
Task budgets let you tell Claude how many tokens it has for a full agentic loop, including thinking, tool calls, tool results, and output. The model sees a running countdown and uses it to prioritize work and finish gracefully as the budget is consumed.
작업 예산을 쓰면 Claude에게 사고(thinking), 도구 호출, 도구 결과, 출력을 포함한 전체 에이전트 루프에 쓸 수 있는 토큰 수를 알려줘요. 모델은 실시간으로 줄어드는 카운트다운을 보고, 그 신호를 바탕으로 작업 우선순위를 정하고 예산이 소진되어 가면서 자연스럽게 마무리해요.
작업 예산을 언제 쓸까요
작업 예산은 Claude가 여러 번 도구 호출과 결정을 내린 뒤 최종 출력을 완성해 다음 사람의 응답을 기다리는 에이전트 워크플로에서 가장 잘 작동해요. 이런 상황에 쓰세요:
- 장기간 걸리는 과제에서 Claude가 토큰 소비를 스스로 조절하길 원할 때.
- 과제당 예측 가능한 비용이나 지연 시간 상한을 강제하고 싶을 때.
- 모델이 예산에 가까워지면 작업 중간에 끊기는 대신 자연스럽게 마무리(결과 요약, 진행 보고)하길 원할 때.
작업 예산은 effort 파라미터와 상호 보완적이에요: effort는 Claude가 각 단계를 얼마나 철저히 추론할지 제어하고, 작업 예산은 에이전트 루프 전체에서 Claude가 할 수 있는 총 작업량을 제한해요.
작업 예산 설정하기
output_config에 task_budget을 추가하고 베타 헤더를 포함하세요:
ant beta:messages create --beta task-budgets-2026-03-13 \
--stream --format jsonl <<'YAML' | jq 'select(.type == "message_delta").usage'
model: claude-opus-5-5
max_tokens: 128000
messages:
- role: user
content: Review the codebase and propose a refactor plan.
output_config:
effort: high
task_budget:
type: tokens
total: 64000
YAML
client = anthropic.Anthropic()
with client.beta.messages.stream(
model="claude-opus-5-5",
max_tokens=128000,
output_config={
"effort": "high",
"task_budget": {"type": "tokens", "total": 64000},
},
messages=[
{"role": "user", "content": "Review the codebase and propose a refactor plan."}
],
betas=["task-budgets-2026-03-13"],
) as stream:
response = stream.get_final_message()
print(response.usage)
const client = new Anthropic();
const stream = client.beta.messages.stream({
model: "claude-opus-5-5",
max_tokens: 128000,
output_config: {
effort: "high",
task_budget: { type: "tokens", total: 64000 }
},
messages: [{ role: "user", content: "Review the codebase and propose a refactor plan." }],
betas: ["task-budgets-2026-03-13"]
});
const response = await stream.finalMessage();
console.log(response.usage);
var client = new AnthropicClient();
var responseUpdates = client.Beta.Messages.CreateStreaming(new MessageCreateParams
{
Model = Messages::Model.ClaudeOpus5_5,
MaxTokens = 128000,
Messages = [new() { Role = Role.User, Content = "Review the codebase and propose a refactor plan." }],
OutputConfig = new BetaOutputConfig
{
Effort = Effort.High,
TaskBudget = new BetaTokenTaskBudget { Total = 64000 },
},
Betas = ["task-budgets-2026-03-13"],
});
var response = await responseUpdates.Aggregate();
Console.WriteLine(response.Usage);
client := anthropic.NewClient()
stream := client.Beta.Messages.NewStreaming(context.TODO(), anthropic.BetaMessageNewParams{
Model: anthropic.ModelClaudeOpus5_5,
MaxTokens: 128000,
Betas: []anthropic.AnthropicBeta{"task-budgets-2026-03-13"},
Messages: []anthropic.BetaMessageParam{{
Role: anthropic.BetaMessageParamRoleUser,
Content: []anthropic.BetaContentBlockParamUnion{{
OfText: &anthropic.BetaTextBlockParam{Text: "Review the codebase and propose a refactor plan."},
}},
}},
OutputConfig: anthropic.BetaOutputConfigParam{
Effort: anthropic.BetaOutputConfigEffortHigh,
TaskBudget: anthropic.BetaTokenTaskBudgetParam{
Total: 64000,
},
},
})
message := anthropic.BetaMessage{}
for stream.Next() {
event := stream.Current()
if err := message.Accumulate(event); err != nil {
panic(err)
}
}
if stream.Err() != nil {
panic(stream.Err())
}
fmt.Printf("Usage: input_tokens=%d, output_tokens=%d\n", message.Usage.InputTokens, message.Usage.OutputTokens)
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5_5)
.maxTokens(128000L)
.addUserMessage("Review the codebase and propose a refactor plan.")
.outputConfig(BetaOutputConfig.builder()
.effort(BetaOutputConfig.Effort.HIGH)
.taskBudget(BetaTokenTaskBudget.builder().total(64000L).build())
.build())
.addBeta("task-budgets-2026-03-13")
.build();
BetaMessageAccumulator accumulator = BetaMessageAccumulator.create();
try (StreamResponse<BetaRawMessageStreamEvent> stream =
client.beta().messages().createStreaming(params)) {
stream.stream().forEach(accumulator::accumulate);
}
BetaMessage response = accumulator.message();
IO.println(response.usage());
use Anthropic\Beta\Messages\BetaRawMessageDeltaEvent;
$client = new Client();
$stream = $client->beta->messages->createStream(
model: 'claude-opus-5-5',
maxTokens: 128000,
messages: [
['role' => 'user', 'content' => 'Review the codebase and propose a refactor plan.'],
],
outputConfig: [
'effort' => 'high',
'taskBudget' => ['type' => 'tokens', 'total' => 64000],
],
betas: ['task-budgets-2026-03-13'],
);
// The final message_delta event carries the cumulative token usage for the request.
$usage = null;
foreach ($stream as $event) {
if ($event instanceof BetaRawMessageDeltaEvent) {
$usage = $event->usage;
}
}
echo $usage;
client = Anthropic::Client.new
stream = client.beta.messages.stream(
model: "claude-opus-5-5",
max_tokens: 128_000,
messages: [
{ role: "user", content: "Review the codebase and propose a refactor plan." }
],
output_config: {
effort: :high,
task_budget: { type: :tokens, total: 64_000 }
},
betas: ["task-budgets-2026-03-13"]
)
response = stream.accumulated_message
puts response.usage
task_budget 객체는 세 가지 필드를 가져요:
type: 항상"tokens".total: Claude가 에이전트 루프에서 쓸 수 있는 토큰 수. thinking, 도구 호출, 도구 결과, 출력을 포함해요.remaining(선택): 이전 요청에서 이어받은 예산 잔액. 생략하면total로 기본 설정돼요.
예산 카운트다운이 동작하는 방식
Claude는 대화 전체에 걸쳐 서버 측에서 주입된 예산 카운트다운 마커를 봐요. 이 마커는 현재 에이전트 루프에서 남은 토큰 수를 보여주고, 모델이 thinking·도구 호출·출력을 생성하고 도구 결과를 처리하면서 갱신돼요. Claude는 이 신호로 페이스를 조절하고 예산이 소진되면서 자연스럽게 마무리해요.
턴(turn)으로 치는 것
예산은 한 번의 에이전트 턴(agentic loop라고도 불러요)을 덮어요: 도구 결과를 담지 않은 하나의 사용자 메시지에 응답해 Claude가 하는 모든 것. 턴은 여러 요청에 걸쳐 있을 수 있어요.
도구 결과를 담지 않은 사용자 메시지는 새 예산으로 새 턴을 시작해요. 오늘 기준으로 카운트다운은 여전히 이전 턴들의 기록이 컨텍스트에 남아 있는 한 그것들을 세어요. 흔한 경우는 예산이 소진돼서 Claude가 턴을 끝낸 뒤의 후속 요청이에요:
{ "role": "user", "content": "Continue." }
tool_result 블록을 담은 사용자 메시지는 현재 턴을 이어가요. 클라이언트가 그 턴에 속한 도구 호출을 해결하고 있기 때문이에요:
{
"role": "user",
"content": [
{ "type": "tool_result", "tool_use_id": "toolu_01", "content": "<npm audit output>" }
]
}
메시지가 도구 결과 옆에 새 콘텐츠를 추가하더라도 마찬가지예요:
{
"role": "user",
"content": [
{ "type": "tool_result", "tool_use_id": "toolu_01", "content": "<npm audit output>" },
{ "type": "text", "text": "Also check the Dockerfile." }
]
}
턴 중간에 서버 측 compaction이 일어나도 예산은 초기화되지 않아요: compaction 전에 턴이 소비한 토큰은 여전히 예산에 반영돼요. 턴이 시작되기 전의 토큰은 세지 않아요. 턴 시작 시 compaction이 그 토큰들을 요약하더라도 마찬가지예요. 오늘 기준으로 이 제외는 서버 측 compaction을 넘겨 이어지는 예산에만 적용되고, 이전 턴들의 기록은 컨텍스트에 남아 있는 한 여전히 세요.
예시로 보는 요청별 예산 계산
작업 예산은 Claude가 보는 것(thinking, 도구 호출·결과, 텍스트)을 세지, 요청 페이로드에 있는 것을 세지 않아요. 에이전트 루프에서 클라이언트는 매 요청마다 전체 대화를 재전송하므로 페이로드는 계속 커지지만, 예산은 새 것, 즉 Claude가 생성한 토큰과 아직 보지 못한 콘텐츠만큼만 줄어들어요. 다음 예시는 세 개 요청으로 이루어진 에이전트 턴이에요: 첫 요청은 사용자 메시지를 담고, 다음 두 요청은 각각 도구 결과를 덧붙여 기록을 재전송해요.
task_budget: {type: "tokens", total: 100000}과 단일 bash 도구를 가진 루프를 생각해 봐요.
요청 1. 초기 요청을 보내요:
{
"messages": [
{ "role": "user", "content": "Audit this repo for security issues and report findings." }
]
}
Claude는 생각하다가 도구 호출을 내보내고 stop_reason: "tool_use"로 멈춰요:
{
"role": "assistant",
"content": [
{
"type": "thinking",
"thinking": "I'll start by listing dependencies to look for known-vulnerable packages..."
},
{
"type": "tool_use",
"id": "toolu_01",
"name": "bash",
"input": { "command": "cat package.json && npm audit --json" }
}
]
}
이 어시스턴트 메시지(thinking + 도구 호출)가 총 5,000 생성 토큰이라고 가정해 봐요. 생성 중 Claude가 본 카운트다운은 remaining ≈ 95,000 부근에서 끝났어요.
요청 2. 클라이언트가 도구를 실행하고, 전체 기록에 도구 결과를 덧붙여 재전송해요:
{
"messages": [
{ "role": "user", "content": "Audit this repo for security issues and report findings." },
{
"role": "assistant",
"content": [
{ "type": "thinking", "thinking": "I'll start by listing dependencies..." },
{
"type": "tool_use",
"id": "toolu_01",
"name": "bash",
"input": { "command": "cat package.json && npm audit --json" }
}
]
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01",
"content": "<2,800 tokens of npm audit output>"
}
]
}
]
}
요청 1에서 재전송된 메시지는 다시 세지 않지만, 2,800 토큰짜리 도구 결과는 새 콘텐츠이므로 예산에 반영돼요. Claude는 thinking과 두 번째 도구 호출(grep -rn "eval(" src/)로 4,000 토큰을 더 써요. 카운트다운은 remaining ≈ 88,200 부근에서 끝나요.
요청 3. 두 번째 도구 결과(1,200 토큰의 grep 출력)를 덧붙여 전체 기록을 다시 재전송해요. Claude는 6,000 토큰짜리 최종 조사 보고서를 쓰고 stop_reason: "end_turn"으로 멈춰요. remaining ≈ 81,000.
세 요청을 나란히 놓으면 페이로드 크기와 예산 지출의 차이가 명확해져요:
| Request | Request payload (approx. input tokens you sent) | Tokens counted against budget this request | Budget remaining after |
|---|---|---|---|
| 1 | ~20 | 5,000 (thinking + tool_use) |
~95,000 |
| 2 | ~7,800 (messages from request 1 + tool result) | 6,800 (2,800 tool result + 4,000 thinking and tool_use) |
~88,200 |
| 3 | ~13,000 (full history + second tool result) | 7,200 (1,200 tool result + 6,000 text) |
~81,000 |
| Total | ~20,820 sent across requests | 19,000 counted against budget | N/A |
클라이언트는 원래 사용자 메시지를 세 번, 첫 어시스턴트 메시지를 두 번 보냈지만 각각 한 번씩만 세졌어요. 예산은 100,000 중 19,000 토큰을 썼는데, 클라이언트가 전송한 누적 페이로드가 더 크고 요청 2·3의 프롬프트 캐시 입력이 더 컸음에도 그렇답니다.
compaction을 넘어 remaining으로 예산 이어가기
클라이언트 코드가 요청 사이에 메시지 기록을 compaction하거나 다시 쓰면(예: 이전 메시지 요약), 서버는 compaction 전에 얼마를 썼는지 기억하지 못해요. 다음 요청에 remaining을 전달해서, 카운트다운이 total로 재설정되는 대신 멈춘 지점부터 이어지게 하세요:
output_config = { "effort": "high", "task_budget": { "type": "tokens", "total": 128000, "remaining": 128000 - tokens_spent_so_far, }, }
```typescript TypeScript
// Tokens spent before compaction, tracked client-side
const tokensSpentSoFar = 45000;
const outputConfig = {
effort: "high",
task_budget: {
type: "tokens",
total: 128000,
remaining: 128000 - tokensSpentSoFar
}
};
// Tokens spent before compaction, tracked client-side
var tokensSpentSoFar = 45000;
var outputConfig = new BetaOutputConfig
{
Effort = Effort.High,
TaskBudget = new BetaTokenTaskBudget
{
Total = 128000,
Remaining = 128000 - tokensSpentSoFar,
},
};
// Tokens spent before compaction, tracked client-side
tokensSpentSoFar := int64(45000)
outputConfig := anthropic.BetaOutputConfigParam{
Effort: anthropic.BetaOutputConfigEffortHigh,
TaskBudget: anthropic.BetaTokenTaskBudgetParam{
Total: 128000,
Remaining: anthropic.Int(128000 - tokensSpentSoFar),
},
}
// Tokens spent before compaction, tracked client-side
long tokensSpentSoFar = 45000;
BetaOutputConfig outputConfig = BetaOutputConfig.builder()
.effort(BetaOutputConfig.Effort.HIGH)
.taskBudget(BetaTokenTaskBudget.builder()
.total(128000L)
.remaining(128000L - tokensSpentSoFar)
.build())
.build();
// Tokens spent before compaction, tracked client-side
$tokensSpentSoFar = 45000;
$outputConfig = [
'effort' => 'high',
'taskBudget' => [
'type' => 'tokens',
'total' => 128000,
'remaining' => 128000 - $tokensSpentSoFar,
],
];
# Tokens spent before compaction, tracked client-side
tokens_spent_so_far = 45_000
output_config = {
effort: :high,
task_budget: {
type: :tokens,
total: 128_000,
remaining: 128_000 - tokens_spent_so_far
}
}
이 예시에서 compaction 전에 쓴 토큰은 지금까지 기록에서 제거한 모든 메시지의 사용량이에요. 현재 사용량 측정하기에서처럼 측정해요. 보내는 메시지에 여전히 남아있는 것은(추가한 요약 포함) 빼요. 서버가 그 토큰들을 스스로 세거든요. 이 값을 이렇게 기록을 대체할 때만 갱신하고, 요청마다 줄이지 마세요. 결과 remaining을 compaction하는 그 요청뿐 아니라 모든 요청에 전달하세요.
compaction하지 않은 전체 기록을 매 요청마다 재전송하는 루프라면 remaining을 생략하고 서버가 카운트다운을 추적하게 하세요.
대화 중간에 예산 바꾸기
task_budget은 요청 수준 설정이에요. 과제 중간에 예산을 바꾸려면(예: 사용자가 요청을 넓혀서 예산을 늘리고 싶을 때) 다음 요청의 output_config에 새 task_budget을 설정하세요. 캐싱 영향은 명심하세요: 예산 값은 렌더링된 프롬프트에 참여하므로, 값이 바뀌면 기존 값으로 만든 캐시 항목과 일치하지 않아요(기능 지원 참고).
작업 예산은 조언일 뿐, 강제되지는 않아요
작업 예산은 **단단한 상한이 아니라 부드러운 힌트(sound hint)**예요. Claude는 방해하기보다 끝내는 게 더 낫다고 판단되는 행동 중이라면 이따금 예산을 초과할 수 있어요. 총 출력 토큰의 강제 상한은 여전히 max_tokens이고, 여기에 도달하면 stop_reason: "max_tokens"으로 응답을 잘라요.
비용이나 지연 시간을 확실히 제한하려면 작업 예산을 합리적인 max_tokens 값과 조합하세요:
task_budget으로 Claude가 페이스를 맞출 목표를 주세요.max_tokens을 폭주하는 생성을 막는 절대 상한으로 쓰세요.
task_budget은 전체 에이전트 루프(여러 요청일 수 있음)를 아우르는 반면 max_tokens은 개별 요청을 제한하므로, 두 값은 독립적이에요. 하나가 다른 하나 이하일 필요는 없어요.
예산 고르기
올바른 예산은 에이전트 루프가 현재 얼마나 많은 작업을 하는지에 달려 있어요. 추측하기보다, 먼저 기존 토큰 사용량을 측정하고 거기서 조정하세요.
현재 사용량 측정하기
task_budget 없이 대표적인 샘플 과제를 실행하고 과제당 Claude가 쓰는 총 토큰을 기록하세요. 에이전트 루프에서는 루프의 모든 요청에서 usage.output_tokens를 합산하고, 요청 사이에 덧붙이는 도구 결과의 토큰도 더해요:
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=4096,
messages=[
{"role": "user", "content": "Review the codebase and propose a refactor plan."}
],
)
# Sum output_tokens (text + thinking + tool calls) across every request in your loop.
print(response.usage.output_tokens)
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-opus-5-5",
max_tokens: 4096,
messages: [{ role: "user", content: "Review the codebase and propose a refactor plan." }]
});
// Sum output_tokens (text + thinking + tool calls) across every request in your loop.
console.log(response.usage.output_tokens);
var client = new AnthropicClient();
var response = await client.Messages.Create(new MessageCreateParams
{
Model = Model.ClaudeOpus5_5,
MaxTokens = 4096,
Messages = [new() { Role = Role.User, Content = "Review the codebase and propose a refactor plan." }],
});
// Sum OutputTokens (text + thinking + tool calls) across every request in your loop.
Console.WriteLine(response.Usage.OutputTokens);
client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5_5,
MaxTokens: 4096,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Review the codebase and propose a refactor plan.")),
},
})
if err != nil {
log.Fatal(err)
}
// Sum OutputTokens (text + thinking + tool calls) across every request in your loop.
fmt.Println(response.Usage.OutputTokens)
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5_5)
.maxTokens(4096L)
.addUserMessage("Review the codebase and propose a refactor plan.")
.build();
Message response = client.messages().create(params);
// Sum outputTokens (text + thinking + tool calls) across every request in your loop.
IO.println(response.usage().outputTokens());
$client = new Client();
$response = $client->messages->create(
model: 'claude-opus-5-5',
maxTokens: 4096,
messages: [
['role' => 'user', 'content' => 'Review the codebase and propose a refactor plan.'],
],
);
// Sum outputTokens (text + thinking + tool calls) across every request in your loop.
echo $response->usage->outputTokens . "\n";
client = Anthropic::Client.new
response = client.messages.create(
model: "claude-opus-5-5",
max_tokens: 4096,
messages: [
{ role: "user", content: "Review the codebase and propose a refactor plan." }
]
)
# Sum output_tokens (text + thinking + tool calls) across every request in your loop.
puts response.usage.output_tokens
대표적인 과제 세트에서 이 작업을 실행해 분포를 기록하세요. 과제당 토큰 지출의 p99에서 시작해 모델에 작업 예산을 주는 것이 행동을 어떻게 바꿀지 이해하고, 필요에 따라 위아래로 조정하며 테스트하세요.
task_budget.total의 최소 허용 값은 예산을 지원하는 모든 모델에서 20,000 토큰이에요(기능 지원). 더 작은 값은 400 오류를 반환해요.
다른 파라미터와의 상호작용
max_tokens: 작업 예산과 직교해요.max_tokens은 생성 토큰의 하드한 요청당 상한이고,task_budget은 전체 에이전트 루프(여러 요청일 수 있음)에 대한 조언적 상한이에요.xhigh나maxeffort에서는max_tokens을 최소 64k로 설정해 각 요청에서 Claude가 충분히 생각하고 행동할 공간을 주세요.- Effort: Effort는 Claude가 단계마다 얼마나 깊이 추론할지 제어해요. 작업 예산은 에이전트 루프에서 Claude가 하는 총 작업량을 제어해요. 둘은 상호 보완적이에요: effort는 깊이를, 작업 예산은 폭을 조정해요.
- 적응형 thinking: 작업 예산은 카운트에 thinking 토큰을 포함하므로, 예산이 줄어들면 적응형 thinking도 축소돼요.
- 프롬프트 캐싱: 예산 카운트다운 마커는 요청마다 서버 측에서 주입되므로 요청 간에 일치하지 않아요. 클라이언트가 후속 요청마다
task_budget.remaining을 줄이면, 바뀐 값이 그걸 포함한 캐시 접두사를 무효화해요. 캐싱을 보존하려면 초기 요청에 예산을 한 번 설정하고, 클라이언트에서 예산을 바꾸지 말고 모델이 서버 측 카운트다운에 맞춰 스스로 조절하게 하세요.
기능 지원
| Model | Support |
|---|---|
| Claude Fable 5.1 | Beta (set task-budgets-2026-03-13 header) |
| Claude Mythos 5.1 | Beta (set task-budgets-2026-03-13 header) |
| Claude Opus 5.5 | Beta (set task-budgets-2026-03-13 header) |
| Claude Opus 5 | Beta (set task-budgets-2026-03-13 header) |
| Claude Fable 5 | Beta (set task-budgets-2026-03-13 header) |
| Claude Mythos 5 | Beta (set task-budgets-2026-03-13 header) |
| Claude Sonnet 5 | Not supported |
| Claude Opus 4.8 | Beta (set task-budgets-2026-03-13 header) |
| Claude Opus 4.7 | Beta (set task-budgets-2026-03-13 header) |
| Claude Opus 4.6 | Not supported |
| Claude Sonnet 4.6 | Not supported |
| Claude Haiku 4.5 | Not supported |
작업 예산은 Claude Code나 Cowork 화면에서는 지원되지 않아요. 지원 모델에서 Messages API로 직접 작업 예산을 사용하세요.