pub struct RunUsage {
pub input_tokens: u64,
pub output_tokens: u64,
pub cache_read_tokens: u64,
pub cache_creation_tokens: u64,
pub reasoning_tokens: u64,
pub thoughts_tokens: u64,
pub model_responses: u32,
}Expand description
What a run reported spending, summed over the rounds it took.
mentra emits one usage report per completed model response, carrying that round’s numbers rather than a running total — its own soft token budget is a sum of them, and so is this. A turn that took four rounds reports four times and lands here as one figure.
Four things to know before treating this as a bill.
It is reported, not measured. A provider that says nothing leaves this at zero. Basis records the reports through the run’s synchronous observer, so a lagging presentation stream cannot make the in-process tally undercount.
It counts every model call the run reported, summarizing passes included.
A compaction that happens inside a run — automatic, context-overflow
recovery, or the model’s own compact intrinsic — is a provider request
like any other, and since mentra 0.26 it reports like one: one
UsageReport per provider sample, emitted after the compaction’s own
events, folded in here through the same observer, and added to mentra’s
own token_budget counter — and so to any
BudgetPool — at the same point. That is real spend
truthfully accounted, not inflation: the summarizing request was always
billed, it just used to be invisible here (the pre-0.26 engine dropped
the response’s usage on the floor). The one summarizing pass this
figure never carries is the standalone
PreparedRun::compact, which is not a run
and returns no report — its samples reach the caller as
Event::Usage lines on the sink the pass borrows, and
the stream is the account for that verb.
It counts the rounds of the run’s own agent and of the work that agent
delegates. A delegating tool — basis’s spawn, or
mentra’s own task intrinsic — drives its subagent on the parent run’s
RunOptions::child and relays the
child’s usage reports onto the parent’s bus, so a delegated round arrives
here like any other. Indistinguishable from the parent’s own, which is
consistent with the accounting being aggregate: mentra’s usage report
carries no agent id for anyone to attribute by. Summing the stream and
reading this figure therefore give one answer, and it is the same answer
TurnOptions::token_budget and
BudgetPool are enforced against, because the child
shares the parent’s accounting handle rather than getting one of its own.
Both halves had to be built for that to hold. The shared counter came first, and until mentra made its event relay public the two disagreed for basis’s own delegations: the bound saw a delegated round and the tally did not, so a run could stop on a total it reported a fraction of.
One edge survives, and it fails loudly rather than leaking: a delegation
issued once the shared budget is already crossed inherits an allowance with
nothing left in it, does zero rounds, and comes back as a failed tool call
instead of an empty success. That is the round-boundary softness
TurnOptions::token_budget already describes, seen
from the delegating side, and mentra pins it
(delegating_with_the_budget_already_spent_fails_the_delegation).
And cache tokens are counted but never mixed in — see
total_tokens.
It is serializable because the same four numbers are what
Event::RunFinished puts on the wire and what basis’s CLI
records beside a finished task: one shape, so a host reading the stream and
a host holding the report cannot disagree about what a run cost.
Fields§
§input_tokens: u64§output_tokens: u64§cache_read_tokens: u64§cache_creation_tokens: u64§reasoning_tokens: u64Reasoning the model did inside output_tokens — the Responses wire
counts it there, so adding it to the total would count it twice.
thoughts_tokens: u64Thinking the model did outside output_tokens — Gemini counts its
thoughts beside the candidates, not in them. Two fields rather than one
sum because a sum would be wrong for one of the two wires, and a reader
who wants “how much did it think” adds them; one who wants “what was I
billed for output” must not. Absent from a record written before the
split, both read as zero.
model_responses: u32Completed model responses that reported usage. This distinguishes a provider-reported all-zero response from a provider that reported no usage at all.
Implementations§
Source§impl RunUsage
impl RunUsage
Sourcepub const fn total_tokens(&self) -> u64
pub const fn total_tokens(&self) -> u64
Input plus output — what RunSpec::with_token_budget counts.
Cache reads and cache writes are left out for the same reason mentra leaves them out of the budget it enforces: they are priced differently everywhere, and a total that mixed them would answer no question exactly.
Trait Implementations§
impl Copy for RunUsage
Source§impl<'de> Deserialize<'de> for RunUsage
impl<'de> Deserialize<'de> for RunUsage
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
impl Eq for RunUsage
impl StructuralPartialEq for RunUsage
Auto Trait Implementations§
impl Freeze for RunUsage
impl RefUnwindSafe for RunUsage
impl Send for RunUsage
impl Sync for RunUsage
impl Unpin for RunUsage
impl UnsafeUnpin for RunUsage
impl UnwindSafe for RunUsage
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.