Estimate and persist cumulative model cost for each thread.
The graph owns the durable total. CostTrackingMiddleware is the only writer of
_session_cost_usd, so each cost update rides the model checkpoint and works for
local, headless, and remote graph execution without a client-side state update.
The client is a reader: it renders the streamed total and never maintains its own
lifetime figure.
Coverage is not limited to the agent's own model node. Offload/summarization and
the Auto mode classifier invoke a model directly, outside after_model, and
subagents run their own graph. _SessionCostRecorder — a callback handler
installed process-wide for every model request (see _install_recorder) —
collects one record per completed request, keyed by thread, and
CostTrackingMiddleware drains and prices those records on the main agent's
checkpoint path. New side invokes are covered with no extra wiring.
The recorder only collects; the middleware alone prices and writes. The agent's own response is still priced from state, but only when the recorder did not already charge that message ID, so a request is never counted twice. That fallback keeps main-agent cost correct even for a model that never fires callbacks.
Nested agents first checkpoint their own spend on the same private channel. That makes a completed model call durable before a later tool approval can interrupt the subgraph. When the subagent finishes, its middleware transfers the accumulated delta through an owner-scoped state entry. The subagent tool checkpoints that entry on the parent graph even when a sibling interrupts, while the private total itself remains isolated between graphs.
Every caller uses estimate_cost, the only function that imports or calls
genai-prices. The import is lazy so the package and its bundled pricing data
stay off the CLI startup path. On that first successful import a daemon-thread
updater starts refreshing the catalog from upstream hourly (see
_start_price_updater); DEEPAGENTS_CODE_PRICES_AUTO_UPDATE=0 or
[update].prices_auto_update = false in config.toml opts out, and
DEEPAGENTS_CODE_OFFLINE suppresses it along with every other network fetch.
When the active genai-prices catalog -- the bundled data, or the auto-updated
snapshot once one is installed -- has no rates for a model, a local override
catalog is consulted as a fallback-on-miss (see _override_price): the user's
own ~/.deepagents/prices.json first, then a maintainer-curated file shipped
as package data. PRICING.md documents the former for users and
bundled_prices.README.md the latter for maintainers. Unsupported models and
malformed usage return None; pricing must never interrupt a model turn.