Skip to main content

Budget

Struct Budget 

Source
pub struct Budget {
    pub max_steps: Option<usize>,
    pub max_effects: Option<usize>,
    pub max_tokens: Option<u64>,
    pub max_minor_units: Option<u64>,
    pub max_replans: Option<u32>,
    pub max_wallclock_secs: Option<u64>,
    pub max_parallel_steps: Option<usize>,
    pub max_denials: Option<u32>,
    pub max_egress_bytes: Option<u64>,
    pub max_call_tokens: Option<u64>,
    pub max_call_minor_units: Option<u64>,
}
Expand description

What a run is allowed to consume.

Every limit is optional; Budget::unlimited() is a legitimate choice for a run that only touches free, local effects. It is not the default, because “nobody set a limit” and “somebody decided no limit was needed” should not look the same in a config file.

§A ceiling gates every effect, not only the metered ones

Worth stating once, because the field names invite the opposite reading: admit_effect evaluates all of these limits before every effect, whatever kind it is. A run holding a token ceiling passes it on the way to a read-only tool call, and a run that has reached any one ceiling performs no further operation of any kind. So max_tokens is not “what the model may spend” — it is a gate the whole run walks through.

The corollary is that zero is not a useful value for any ceiling here except max_replans and max_denials: at zero the limit is reached before the run starts, and the agent can never do anything at all. Refused on both paths that can carry one — the manifest at parse, and RuntimeBuilder::budget at build — from the single rule in bricked_ceiling.

Fields§

§max_steps: Option<usize>

Plan nodes this run may execute, checked before each one starts.

Zero permits no step at all, so a run carrying it cannot begin.

§max_effects: Option<usize>

Externally visible operations, of every kind.

The blunt instrument that stops a loop nobody predicted, independent of what each operation happens to cost — a free local read costs one here exactly as a model completion does. Zero permits no effect at all.

§max_tokens: Option<u64>

Metered units the run may consume in total.

Compared before every effect, including the effects that consume no tokens, so this bounds when the run stops rather than only what the model costs. Zero refuses the first effect of any kind.

§max_minor_units: Option<u64>

Money in minor units the run may spend in total.

Gates every effect, exactly like max_tokens: a free effect still has to pass it. Zero refuses the first one.

§max_replans: Option<u32>

How many times a run may change its plan.

A run that replans without bound is a run that has stopped making progress and started thrashing, and the ceiling is what turns that from an unbounded spend into a reported fault.

Zero is meaningful here, unlike the ceilings above: it says the plan the run started with is the plan it finishes with, and a run that never replans is unaffected by it.

§max_wallclock_secs: Option<u64>

Wall-clock ceiling.

Costs one journaled clock read per step boundary when set — see the module docs. Checked against Consumed::elapsed_secs, which counts second boundaries rather than measuring a duration and starts at zero, so a zero ceiling refuses the first step.

§max_parallel_steps: Option<usize>

How many of a plan’s ready steps may run at once.

A ready set is every node whose predecessors are done, so nothing in it depends on anything else in it and running them concurrently is the point of writing a graph rather than a list. What that width costs is paid outside this process — connections, provider rate limits, and the metered ceilings above, which are checked before an operation and billed after it, so each step in flight may be holding one operation’s worth of unbilled spend.

Absent means the plan’s own width is the bound, which is the right default for a graph the embedder wrote and the wrong one for a graph anything else may widen. Zero permits no step at all.

§max_denials: Option<u32>

How many times this run may be refused before it is stopped.

A run that keeps being refused is probing. Refusals carry a uniform message precisely so a model cannot tell one from another (REFUSED), but the refused/allowed bit itself still leaks one bit per attempt, and nothing short of fabricating success removes that. What bounds the channel is bounding the attempts.

Both refusal paths count, and they behave differently. A sink refusal — an untrusted value in a protected field, a sensitivity over a ceiling — comes back to a tool-calling model as REFUSED and the loop continues, so those accumulate and this is what stops them. An engine denial is not model-facing at all: in that same loop it ends the run outright, which is a stricter bound than any ceiling, and it accumulates only where a run’s own code catches StepError::Denied and carries on. Counting only the second would leave the ceiling naming a channel it did not reach.

It is an operational ceiling as much as a security one: a run stuck in a denial loop has stopped making progress, exactly like one that replans without bound.

Zero is meaningful here too, and for a different reason than max_replans: this one is counted after the refusal and compared with >, so zero says the first refusal ends the run. A run nothing refuses never notices it.

§max_egress_bytes: Option<u64>

Bytes this run may send into sinks, in total.

Volume is the axis a label does not have. Ten thousand records marked Internal pass every gate one record passes, and every ceiling above is a cost control that bounds work: an extraction sized just under an effect count is invisible to all of them. This bounds the quantity that left.

Measured as the canonical size of each effect’s outbound value — a tool call’s arguments, a model call’s prompt, a peer call’s payload — which is the figure EffectStarted.outbound_bytes records. An effect binding no outbound value costs nothing here, so a run of pure reads is unaffected.

Exact, unlike every metered ceiling above. A token cost is unknown until the call returns, so those refuse once consumption has reached the limit and the run overshoots by at most one operation. This size is known before dispatch, so the effect that would cross the ceiling is the effect refused: nothing over the limit is ever sent.

Zero is meaningful, like max_denials and unlike the metered ceilings: it says this run may read and may not send, which is a coherent thing to ask of an agent. So it is not a bricked ceiling.

What it is not is an anomaly detector. Forty times the median for this capability is a threshold a deployment sets against its own traffic, and the journaled figure is what makes that an ordinary query. A ceiling bounds the worst case; the figure catches the case that stayed under it.

§max_call_tokens: Option<u64>

The most tokens one operation of this run can report.

Not a ceiling the ledger checks — a metered cost is unknown until the call returns — but the size of the overshoot the ceilings above permit: a run stops once max_tokens is reached, so it can end one operation past it per step in flight. A tenant’s spend quota reserves the ceiling plus that overshoot at admission, and this is the figure it reserves per step, so under a token quota a run that declares none is refused.

A manifest derives it from its model roles (max_input_tokens plus max_tokens per call), and a model call reporting more input than its role declares fails. An embedder’s own effects cannot be inspected, so for those it is a declaration the reservation is only as exact as — the same footing as a price.

§max_call_minor_units: Option<u64>

The most money, in minor units, one operation of this run can report.

max_call_tokens for max_minor_units: the per-step overshoot a tenant’s money quota reserves beside the ceiling. A manifest derives it from each role’s per-call token maxima at the role’s declared price.

Implementations§

Source§

impl Budget

Source

pub const fn bricked_ceiling(&self) -> Option<&'static str>

The first ceiling set to zero, if any — the field name, for a message.

Zero is not a small limit here; it is a limit already reached. Every ceiling this reports is compared before the work and against every effect of every kind, so at zero the run is refused its first operation: a read-only tool call, a local lookup, an agent that declares no models at all. Such a plane does not run once and stop — it fails identically on every run it will ever make, which makes it a wiring mistake rather than a budget.

Three are excluded, because zero is a coherent instruction for each: max_replans says do not replan, max_denials says the first refusal ends the run, and max_egress_bytes says this run may read and may not send. A run that never replans, is never refused, or sends nothing is unaffected by its own — which is what separates them from a ceiling that refuses the first operation of any kind.

Source

pub const fn unlimited() -> Self

No limits. An explicit choice, not an absence of one.

Source

pub const fn steps(self, n: usize) -> Self

Source

pub const fn effects(self, n: usize) -> Self

Source

pub const fn tokens(self, n: u64) -> Self

Source

pub const fn minor_units(self, n: u64) -> Self

Source

pub const fn replans(self, n: u32) -> Self

Source

pub const fn denials(self, n: u32) -> Self

How many refusals this run may accrue — sink and engine alike.

Source

pub const fn egress_bytes(self, n: u64) -> Self

How many bytes this run may send into sinks.

Source

pub const fn call_tokens(self, n: u64) -> Self

The most tokens one operation may report. See max_call_tokens.

Source

pub const fn call_minor_units(self, n: u64) -> Self

The most money one operation may report. See max_call_minor_units.

Source

pub const fn wallclock_secs(self, n: u64) -> Self

Source

pub const fn parallel_steps(self, n: usize) -> Self

Bound the width of a ready set. See max_parallel_steps.

Source

pub const fn parallelism(&self) -> usize

How many ready steps may be dispatched at once — the plan’s own width when nothing narrows it.

Source

pub const fn tracks_wallclock(&self) -> bool

Trait Implementations§

Source§

impl Clone for Budget

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for Budget

Source§

impl Debug for Budget

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Budget

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for Budget

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Eq for Budget

Source§

impl PartialEq for Budget

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for Budget

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for Budget

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> AsOut<T> for T
where T: Copy,

Source§

fn as_out(&mut self) -> Out<'_, T>

Returns an out reference to self.
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<Unshared, Shared> IntoShared<Shared> for Unshared
where Shared: FromUnshared<Unshared>,

Source§

fn into_shared(self) -> Shared

Creates a shared type from an unshared type.
Source§

impl<T> MaybeSend for T
where T: Send,

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more