Skip to content

Telemetry & cost

Every model call bb makes is billed by its provider, and bb tells you exactly what that cost — never an estimate. reply.Usage() reports what one ask cost:

go
reply, _ := chat.Ask()
u := reply.Usage() // Usage{Input, Output, CacheRead, CacheWrite, Reasoning}

For a live (streaming) reply this blocks until the stream completes — providers report usage last, exactly as reply.ToolCalls() already does. A provider that reports nothing (some self-hosted OpenAI-compatible endpoints never do) yields the zero Usage; bb never fills the gap with a guess.

bb.Spent(ctx) reports the running total for the WHOLE request so far — every flow, every agent, every tool round, summed:

go
func(ctx context.Context, turn *bb.Turn, chat *bb.ModelChat) error {
    if bb.Spent(ctx).Total() > budget {
        return turn.Reply("that's enough for now")
    }
    ...
}

It's a snapshot (calls still in flight aren't counted yet) and reads zero outside a served request.

The usage block is honest, not flattering

The usage block bb sends its own clients is the SUM of every upstream call the run made — a router's cheap pass, a capability agent's real one, every tool round — not a mirror of the client's own prompt size. A brain that made five model calls really did spend all five calls' worth of tokens, and reporting anything less would make bb the one "model" in the world whose own bill is bigger than what it told you. prompt_tokens (OpenAI) / input_tokens (Anthropic) will therefore usually exceed the size of the prompt the client actually sent — that's honest, not a bug.

Two edge cases worth knowing:

  • A resumed durable run reports only what THAT run spent. A flow whose result was replayed from a checkpoint (flow.cached) made no model call, so it contributes zero tokens — the truthful answer to "what did this attempt cost", not "what has this conversation cost across every crash and retry".
  • bb.FixedModel and a bound Bound(mock) report zero usage. No provider was called, so nothing was billed.

What this is not

  • Not a price table. A model's price changes per provider, per region, per cache-hit class, faster than a library can track it — turn model.tokens into cost with a Prometheus/Grafana rule against your own deployment, not code shipped here.
  • Not a benchmark harness. Every number above is dominated by a third party's network latency, so go test -bench would only measure the provider's mood — point a real load generator, like k6 or vegeta, at a running brain instead.

Where the numbers land, automatically

With no author action, the same numbers also land as OTel instruments (model.tokens, model.ttft.seconds, model.call.seconds, request.seconds, …) — inert until BIG_BRAIN_TELEMETRY=stdout or =otlp (with BIG_BRAIN_OTLP_ENDPOINT) is set, and as a Usage on each flow's flow.end trace event at /v1/diagnostics/trace, always on, for "which flow spent the tokens" without any config at all.

Per-flow attribution sums legally over a sequential span (tokens sum across time); it is deliberately not attempted for Group/One members, whose overlapping intervals would make an individual share a lie.

Debugging is a byproduct of running the tree, not a separate subsystem to wire up — see Serving for the diagnostics endpoint and trace backends.

A tree of flows and agents, disguised as a model.