세션 예산

세션 예산 (Session budgets)

세션 예산은 세션을 만들 때 설정하는 선택적 하드 지출 상한이에요. 플랫폼이 세션이 소비하는 모든 것을 공개 목록 가격(public list rate)으로 계속 가격을 매기고(이를 세션의 list cost라 해요), 비용이 예산에 도달하면 새 모델 요청 발행을 멈춰요. 상한을 넘는 순간 진행 중이던 요청은 끝까지 완료되므로, 최종 list cost는 예산을 조금 넘을 수 있어요. 예산에 도달한 세션은 종료되는 대신 일시 중지되어 유휴(idle) 상태가 돼요. 예산을 바꾸거나 제거하면 작업이 자동으로 재개된답니다.

출처: 문서

본문

세션 예산은 세션을 만들 때 설정하는 선택적 하드 지출 상한이에요. 플랫폼은 세션이 소비하는 모든 것을 공개 목록 가격으로 계속 가격을 매기고(세션의 list cost), 그 비용이 예산에 도달하면 새 모델 요청 발행을 중단해요. 상한을 넘는 순간 진행 중이던 요청은 여전히 끝까지 완료되므로 최종 list cost는 예산을 조금 넘을 수 있어요. 예산에 도달한 세션은 종료되는 대신 일시 중지되어 유휴 상태가 돼요. 예산을 바꾸거나 제거하면 작업이 자동으로 재개돼요. 디플로이먼트도 동일한 예산을 받아들이고, 시작하는 각 세션에 적용해요. 자세한 내용은 Budgets on deployments를 참고하세요.

Set a budget at session creation

세션을 만들 때 선택적 budget 필드를 전달하세요:

```bash cURL curl -sS --fail-with-body https://api.anthropic.com/v1/sessions \ -H "x-api-key: $ANTHR...KEY" \ -H "anthropic-version: 2023-06-01" \ -H "anthropic-beta: managed-agents-2026-04-01" \ -H "content-type: application/json" \ -d @- <# Keep the amount quoted so it is sent as a string, not a number. ant beta:sessions create \ --agent "$AGENT_ID" \ --environment-id "$ENVIRONMENT_ID" \ --budget '{type: limit, max_list_cost: {amount: "125", currency: USD}}'
session = client.beta.sessions.create(
    agent=agent.id,
    environment_id=environment.id,
    budget={
        "type": "limit",
        "max_list_cost": {"amount": "125", "currency": "USD"},
    },
)
print(session.id, session.budget.max_list_cost.amount)  # sesn_01... 125
const session = await client.beta.sessions.create({
  agent: agent.id,
  environment_id: environment.id,
  budget: {
    type: "limit",
    max_list_cost: { amount: "125", currency: "USD" }
  }
});
console.log(session.id, session.budget?.max_list_cost.amount); // sesn_01... 125
var session = await client.Beta.Sessions.Create(new()
{
    Agent = agent.ID,
    EnvironmentID = environment.ID,
    Budget = new()
    {
        Type = BetaManagedAgentsBudgetLimitType.Limit,
        MaxListCost = new() { Amount = "125", Currency = BetaCurrency.Usd },
    },
});
Console.WriteLine($"{session.ID} {session.Budget?.MaxListCost.Amount}");  // sesn_01... 125
session, err := client.Beta.Sessions.New(ctx, anthropic.BetaSessionNewParams{
	Agent: anthropic.BetaSessionNewParamsAgentUnion{
		OfString: anthropic.String(agent.ID),
	},
	EnvironmentID: environment.ID,
	Budget: anthropic.BetaManagedAgentsBudgetLimitParam{
		Type: anthropic.BetaManagedAgentsBudgetLimitTypeLimit,
		MaxListCost: anthropic.BetaMonetaryAmountParam{
			Amount:   "125",
			Currency: anthropic.BetaCurrencyUsd,
		},
	},
})
if err != nil {
	panic(err)
}
fmt.Println(session.ID, session.Budget.MaxListCost.Amount) // sesn_01... 125
var session = client.beta().sessions().create(SessionCreateParams.builder()
    .agent(agent.id())
    .environmentId(environment.id())
    .budget(BetaManagedAgentsBudgetLimit.builder()
        .type(BetaManagedAgentsBudgetLimit.Type.LIMIT)
        .maxListCost(BetaMonetaryAmount.builder()
            .amount("125")
            .currency(BetaCurrency.USD)
            .build())
        .build())
    .build());
IO.println(session.id() + " " + session.budget().orElseThrow().maxListCost().amount());  // sesn_01... 125
$session = $client->beta->sessions->create(
    agent: $agent->id,
    environmentID: $environment->id,
    budget: [
        'type' => 'limit',
        'max_list_cost' => ['amount' => '125', 'currency' => 'USD'],
    ],
);
echo "{$session->id} {$session->budget->maxListCost->amount}\n"; // sesn_01... 125
session = client.beta.sessions.create(
  agent: agent.id,
  environment_id: environment.id,
  budget: {
    type: "limit",
    max_list_cost: {amount: "125", currency: "USD"}
  }
)
puts "#{session.id} #{session.budget.max_list_cost.amount}" # sesn_01... 125

budget 객체에는 두 개의 필드가 있어요:

  • type is always "limit".
  • max_list_cost is the cap itself: amount is a whole number of US cents written as a string with no leading zeros ("125" is $1.25 and "50" is 50 cents) and must be greater than zero. Decimal forms such as "25.00" are rejected. The amount is a string rather than a number so no float rounding is ever applied to it. currency is an uppercase ISO-4217 currency code; USD is the only supported currency.

예산은 세션을 만들 때만 붙일 수 있어요. 예산이 없는 기존 세션에 예산을 추가하면 400 에러로 거부돼요. 예산이 있는 세션의 상한은 언제든 변경하거나 제거할 수 있어요.

How list cost is measured

플랫폼은 세션이 소비하는 것을 공개 목록 가격으로 계속 가격을 매겨요:

  • Model tokens, at each served model's list price
  • Web searches, at $10 per 1,000 searches
  • Session running time, at $0.08 per hour

이 누적 달러 합계가 세션의 list cost이며, 예산이 비교하는 값이에요. List cost는 계약 가격이 아니에요: 조직이 할인을 협상했다면 세션은 목록 가격 합계가 상한에 도달할 때 일시 중지되고, 실제 청구액은 상한보다 낮을 수 있어요.

강제는 정확한(반올림하지 않은) list cost를 사용해요. 세션과 그 이벤트에 보고되는 list_cost 수치는 가장 가까운 센트로 반올림된 정수 센트이므로, 보고된 수치가 강제에 사용된 정확한 금액과 양쪽으로 최대 반 센트 차이가 날 수 있어요.

When a session reaches its budget

상한은 요청 중간이 아니라 모델 요청 사이에 강제돼요. 각 모델 요청 전에 플랫폼은 소비된 list cost를 확인하고, 합계가 상한에 도달하면 모든 스레드가 다음 요청 전에 일시 중지돼요. 합계를 상한을 넘게 만든 그 요청은 세션이 아직 상한 아래 있을 때 허용된 것이므로 끝까지 실행돼요. 그래서 일시 중지된 세션의 기록된 list_costmax_list_cost와 같거나 그보다 조금 더 읽힐 수 있어요: "50"(50센트)으로 상한이 설정된 세션은 "53"list_cost로 일시 중지될 수 있죠. 이는 예상된 현상이며 청구 오류가 아니에요. 초과분은 스레드당 모델 요청 하나로 제한돼요. 예산을 정확한 중지 지점이 아니라 새 작업의 경계로 취급하고, 그 한 요청 여유를 염두에 두고 상한을 잡으세요.

예산에 도달한 세션은 stop_reasonbudget_reached인 유휴 상태가 돼요. 종료되는 것이 아니며, 기록과 샌드박스는 다른 유휴 세션처럼 보존돼요. 이벤트 스트림에는 순서대로 다음이 보여요:

  1. A session.thread_status_idle event with a stop_reason of budget_reached as each thread pauses.
  2. A session.usage event with the session's cumulative usage and list cost.
  3. A session.status_idle event with a stop_reason of budget_reached. The usage event always immediately precedes this idle event.

마지막 요청이 상한을 넘으면서도 턴을 완료하는 스레드는 자체 session.thread_status_idle 이벤트에 end_turn을 보고하는 반면, 세션은 여전히 budget_reached를 보고해요. 세션 레벨 stop_reason을 세션이 예산에서 일시 중지됐다는 신호로 취급하세요.

Events accepted at the cap

세션이 예산 이상이거나 예산에 있을 때, 진행 중인 작업을 정리하는 이벤트만 받아들여요:

  • user.tool_confirmation
  • user.tool_result
  • user.custom_tool_result
  • user.interrupt

새 작업을 시작할 이벤트(예: user.message)는 이 목록을 명시한 400 에러로 거부돼요. 정리된 결과는 새 모델 요청을 트리거하지 않고 기록되며, 세션은 예산에서 일시 중지된 상태를 유지해요.

세션이 예산에서 일시 중지된 상태(모든 스레드가 상한에서 일시 중지됨)에서 보낸 user.interrupt는 받아들여지고 무시돼요: 이벤트 목록에 나타나지 않고 아무것도 바꾸지 않아요. 계속하려면 예산을 변경하거나 제거하세요.

Resume a session at its budget

세션 업데이트로 예산을 변경하거나 제거하세요. 승인된 업데이트는 일시 중지된 작업을 자동으로 재개해요. 추가 클라이언트 조치는 필요 없어요.

Change the budget

세션을 새 max_list_cost로 업데이트하세요. 새 값은 현재 상한보다 높거나 낮을 수 있지만, 세션이 소비한 list cost보다 반드시 엄격하게 커야 해요. 그렇지 않으면 budget.max_list_cost must be greater than the session's consumed list cost라는 400 에러로 업데이트가 거부돼요. 세션이 일시 중지될 때 소비된 비용은 보통 옛 상한을 조금 넘는 위치에 있으므로, 새 값은 옛 max_list_cost가 아니라 보고된 usage.list_cost에 기반하세요. 그 수치보다 1센트 이상 높게 설정하세요: 보고된 값은 반올림되어 있고, 검사가 사용하는 정확한 소비 비용보다 조금 낮을 수 있어요.

```bash cURL curl -sS --fail-with-body "https://api.anthropic.com/v1/sessions/$SESSION_ID" \ -H "x-api-key: $ANTHR...KEY" \ -H "anthropic-version: 2023-06-01" \ -H "anthropic-beta: managed-agents-2026-04-01" \ -H "content-type: application/json" \ -d '{ "budget": { "type": "limit", "max_list_cost": {"amount": "500", "currency": "USD"} } }' ```
ant beta:sessions update \
  --session-id "$SESSION_ID" \
  --budget '{type: limit, max_list_cost: {amount: "500", currency: USD}}'
updated_session = client.beta.sessions.update(
    session.id,
    budget={
        "type": "limit",
        "max_list_cost": {"amount": "500", "currency": "USD"},
    },
)
print(updated_session.budget.max_list_cost.amount)  # 500
const updatedSession = await client.beta.sessions.update(session.id, {
  budget: {
    type: "limit",
    max_list_cost: { amount: "500", currency: "USD" }
  }
});
console.log(updatedSession.budget?.max_list_cost.amount); // 500
var updatedSession = await client.Beta.Sessions.Update(session.ID, new()
{
    Budget = new()
    {
        Type = BetaManagedAgentsBudgetLimitType.Limit,
        MaxListCost = new() { Amount = "500", Currency = BetaCurrency.Usd },
    },
});
Console.WriteLine(updatedSession.Budget?.MaxListCost.Amount);  // 500
updatedSession, err := client.Beta.Sessions.Update(ctx, session.ID, anthropic.BetaSessionUpdateParams{
	Budget: anthropic.BetaManagedAgentsBudgetLimitParam{
		Type: anthropic.BetaManagedAgentsBudgetLimitTypeLimit,
		MaxListCost: anthropic.BetaMonetaryAmountParam{
			Amount:   "500",
			Currency: anthropic.BetaCurrencyUsd,
		},
	},
})
if err != nil {
	panic(err)
}
fmt.Println(updatedSession.Budget.MaxListCost.Amount) // 500
var updatedSession = client.beta().sessions().update(session.id(), SessionUpdateParams.builder()
    .budget(BetaManagedAgentsBudgetLimit.builder()
        .type(BetaManagedAgentsBudgetLimit.Type.LIMIT)
        .maxListCost(BetaMonetaryAmount.builder()
            .amount("500")
            .currency(BetaCurrency.USD)
            .build())
        .build())
    .build());
IO.println(updatedSession.budget().orElseThrow().maxListCost().amount());  // 500
$updatedSession = $client->beta->sessions->update(
    $session->id,
    budget: [
        'type' => 'limit',
        'max_list_cost' => ['amount' => '500', 'currency' => 'USD'],
    ],
);
echo "{$updatedSession->budget->maxListCost->amount}\n"; // 500
updated_session = client.beta.sessions.update(
  session.id,
  budget: {
    type: "limit",
    max_list_cost: {amount: "500", currency: "USD"}
  }
)
puts updated_session.budget.max_list_cost.amount # 500

Remove the budget

상한을 완전히 제거하려면 budgetnull로 설정하세요. 세션의 일시 중지된 작업은 재개되고, 결과로 나오는 session.updated 이벤트는 budgetnull로 설정된 채 전달돼요.

```bash cURL curl -sS --fail-with-body "https://api.anthropic.com/v1/sessions/$SESSION_ID" \ -H "x-api-key: $ANTHR...KEY" \ -H "anthropic-version: 2023-06-01" \ -H "anthropic-beta: managed-agents-2026-04-01" \ -H "content-type: application/json" \ -d '{"budget": null}' ```
ant beta:sessions update --session-id "$SESSION_ID" --budget null
unbudgeted_session = client.beta.sessions.update(session.id, budget=None)
print(unbudgeted_session.budget)  # None
const unbudgetedSession = await client.beta.sessions.update(session.id, { budget: null });
console.log(unbudgetedSession.budget); // null
// Assigning null sends an explicit null; leaving Budget unset would omit the field.
var unbudgetedSession = await client.Beta.Sessions.Update(session.ID, new() { Budget = null });
Console.WriteLine(unbudgetedSession.Budget is null);  // True: the session no longer has a budget
// A zero-value Budget is omitted from the request; param.NullStruct (from
// github.com/anthropics/anthropic-sdk-go/packages/param) sends an explicit null.
unbudgetedSession, err := client.Beta.Sessions.Update(ctx, session.ID, anthropic.BetaSessionUpdateParams{
	Budget: param.NullStruct[anthropic.BetaManagedAgentsBudgetLimitParam](),
})
if err != nil {
	panic(err)
}
fmt.Println(unbudgetedSession.JSON.Budget.Valid()) // false: the session no longer has a budget
// An empty Optional sends an explicit null; leaving budget unset would omit the field.
var unbudgetedSession = client.beta().sessions().update(session.id(), SessionUpdateParams.builder()
    .budget(Optional.empty())
    .build());
IO.println(unbudgetedSession.budget().isPresent());  // false: the session no longer has a budget
// update(budget: null) omits the field, so send the explicit null through the raw client.
$unbudgetedSession = $client->beta->sessions->raw
    ->update($session->id, ['budget' => null])
    ->parse();
echo json_encode($unbudgetedSession->budget), "\n"; // null
unbudgeted_session = client.beta.sessions.update(session.id, budget: nil)
p unbudgeted_session.budget # nil
Removing a session's budget is one-way: a session whose budget has been removed cannot be given a new one. To keep a cap on the session, change the budget instead.

Monitor spend

세션 객체는 budget과 추적된 지출이 담긴 usage 객체를 담고 있어요: usage.list_cost는 세션의 소비된 list cost이고, usage.active_seconds는 런타임 비용이 책정되는 실행 시간이에요. budget_reached로 일시 중지된 세션에서는 usage.list_costmax_list_cost와 같거나 조금 더 읽히는 것을 기대하세요: 상한을 넘긴 요청이 일시 중지 전에 끝났기 때문이에요. 세션 레벨 active_seconds는 동시 스레드의 겹치는 활동을 한 번만 셉니다. 스레드 조회 응답은 스레드 자체의 usage에 같은 두 필드를 담고, 스레드별로 가격이 매겨져요. 스레드별 수치는 독립적으로 반올림되고 세션의 실행 시간 비용을 제외하므로 정확히 세션의 list_cost에 합산되지 않아요. 예산이 강제되는 기준은 세션 수치예요.

session.usage 이벤트는 세션의 누적 사용량과 추적된 list cost의 스냅샷이에요. 세션의 토큰 합계, list_cost, active_seconds, server_tool_use 요청 수(web_search_requests는 요청당 list cost에 포함되고, web_fetch_requests는 요청당 요금이 없어 계량되지 않으므로 0으로 읽힘), 그리고 세션의 budget의 반영값(없으면 null)을 담아요. 이벤트 목록과 세션 스트림에 나타나요. 세션이 어떤 중지 사유로든 유휴 상태가 되기 직전에 하나를 발행하므로, 예산에 도달한 세션은 항상 예산 도달 유휴 이벤트 직전에 하나를 발행해요.

스트림과 세션 객체에서 사용량을 읽는 방법은 Tracking usage를 참고하세요.

Budgets in multiagent sessions

multiagent 세션은 모든 스레드가 공유하는 단일 예산을 가지며, 스레드별 상한은 없어요. 각 스레드의 소비는 자체 서빙 모델 가격으로 책정되고, 공유 상한에 도달하면 스레드가 독립적으로 일시 중지돼요. Advisor 상담은 같은 예산에 포함되며, 어드바이저 모델 요율로 책정돼요. 한 스레드가 budget_reached로 일시 중지되는 동안 다른 스레드가 진행 중인 요청을 끝낼 수 있어요.

대기 중인 요청(ask)은 상한보다 우선해요: 한 스레드가 requires_action으로 대기하고 다른 스레드가 budget_reached로 일시 중지된 세션은 세션 레벨에서 requires_action을 보고해요. 대기 중인 요청은 여전히 답이 필요하고, 답하는 것은 예산이 막지 않는 settle event예요.

Budgets on deployments

deployment는 만들거나 업데이트할 때 같은 budget 객체를 받아들여요:

{
  "budget": {
    "type": "limit",
    "max_list_cost": { "amount": "2000", "currency": "USD" }
  }
}

상한은 디플로이먼트가 시작하는 각 세션에 복사되므로, 디플로이먼트의 누적 지출이 아니라 각 실행을 개별적으로 제한해요. 디플로이먼트의 예산을 변경하면 이후에 시작하는 세션에 적용되며 이미 실행 중인 세션에는 적용되지 않아요. 세션과 달리 디플로이먼트의 예산은 null로 지웠다가 나중에 다시 설정할 수 있어요. 자세한 내용은 Set a budget on each run를 참고하세요.

Models without a list price

예산은 플랫폼이 가격을 매길 수 있는 소비만 추적할 수 있어요. 에이전트나 multiagent 로스터의 에이전트·어드바이저 중 하나라도 공개 목록 가격이 없는 모델을 사용하는 예산 세션을 만들면 모델에 대한 목록 가격이 없다는 400 에러로 거부돼요.

예산이 있는 세션의 사용량에 목록 가격이 없는 모델이 포함되게 되면 예산은 더 이상 세션 지출을 측정할 수 없어요: 세션은 budget_reachedstop_reason으로 일시 중지될 수 있고, 예산 변경은 거부돼요. 세션을 재개하려면 예산을 제거하세요.

Error reference

예산 관련 요청은 다음 경우에 거부돼요:

Condition Status
A work-starting event (for example, user.message) is sent while the session is at or over its budget; the error names the accepted settle events 400
The budget is set to a value at or below the session's consumed list cost 400
A budget is added to a session created without one, or re-added after removal 400
amount is not a whole number of cents (for example, "25.00"), is zero or negative, or currency is not USD 400
A budgeted create references a model with no public list price 400
Session budgets are hard caps in US dollars (written in cents) on a single session, enforced by the platform. They are distinct from the Messages API's [task budgets](https://platform.claude.com/docs/en/build-with-claude/task-budgets), which are advisory, token-denominated budgets the model uses to self-regulate within one agentic loop.

더 알아보기 (Learn more)