Expand description
The resource budget of the sandboxed autonomous mode (spec §11.1).
OrchestrationMode::SandboxedAutonomous
is the one mode where the model drives writes nobody reviewed, and the
ResourceBudget it carries is what keeps that from being open-ended. This
module is the enforcement: it measures what a turn has spent and answers one
question — may the turn keep going? — with the name of the bound that ran
out when the answer is no.
§Three bounds, one rule
| Bound | Measured as | Exhausted when |
|---|---|---|
max_model_calls | one ProviderAttempt per call the turn made, retries and fallbacks included | spent >= max |
max_prompt_tokens | the prompt tokens the provider reported for every successful attempt | spent >= max |
max_wall_clock | the runtime clock, from the moment the turn was accepted | elapsed >= max |
The comparison is >=, not >, and that is the fail-closed reading: a
budget of eight calls means a turn may make eight, so a turn that has made
eight has nothing left to spend and stops before asking for a ninth. An
attempt that failed still counts — it cost the same call.
§Where it is checked, and what happens
Orchestrator::handle_turn
checks the budget after interpretation and again before anything executes.
Both are before any effect, so an exhausted budget fails the turn
outright: BudgetLimit::into_error names the bound in a typed
PolicyError::BudgetExhausted, nothing was journaled and nothing can have
happened.
After the commit the answer is different, and deliberately so. The effects
are real by then, and §23.1 says a turn that committed keeps its effects and
regenerates its wording; failing it to save a token budget would throw away
the only truthful account of what just happened. So a budget that runs out
post-commit stops the model calls — narration and answers — and the turn
is delivered with its receipts, its notices and an explicit
budget_exhausted notice.
use std::time::Duration;
use turnframe_runtime::budget::{BudgetLimit, BudgetSpend, TurnBudget};
use turnframe_runtime::config::ResourceBudget;
let budget = ResourceBudget::conservative().with_max_model_calls(2);
let started = chrono::DateTime::UNIX_EPOCH;
let turn = TurnBudget::new(budget, started);
let spent = BudgetSpend::none().with_model_calls(2);
assert_eq!(turn.exhausted(spent, started), Some(BudgetLimit::ModelCalls));
assert_eq!(turn.exhausted(BudgetSpend::none(), started), None);Structs§
- Budget
Spend - What a turn has spent so far.
- Turn
Budget - One turn’s budget, measured against the clock it started on.
Enums§
- Budget
Limit - Which bound of a
ResourceBudgetran out.