Threads AI

Usage and cost

Read what a thread and its subagents used and spent, straight from the log.

Every model response records its token counts in the thread's log, so what a thread used and spent is read back from the log, not from a separate meter. Open the thread and ask it.

Tokens used

// A price on the model is what lets cost() count money (see Models).
const bot = agent({
  model: anthropic("claude-sonnet-5", {
    price: { input: 2000, output: 10000 },
  }),
});
const store = sqlite(".threads");

const result = await bot.run("Summarize the report.", { store });
const thread = result.thread;

const usage = await thread.usage();
if (!usage.ok) throw new Error(usage.error.message);
const { input_tokens, output_tokens, unknown_responses } = usage.value;

unknown_responses counts responses whose provider didn't report usage (for example a stream that broke), and the rare response whose counts would take a total past 2^53-1. They are never added as zero, so a non-zero count tells you the totals are a lower bound.

A fork counts its parent's prefix too: its usage is everything on its branch.

Money spent

cost() prices the recorded usage with the model's price. An agent whose model has a price records its currency as USD, so the model factory's price is in nano-dollars per token (USD 2 per million tokens is 2000, see Models).

const cost = await thread.cost();
if (!cost.ok) throw new Error(cost.error.message);
if (cost.value !== null) {
  const { currency, known_nanos, complete } = cost.value;
  console.log(`${currency} ${known_nanos / 1e9}`, complete ? "" : "(at least)");
}
FieldMeaning
currencyThe pinned currency: USD for agent()
known_nanosWhat the recorded usage costs, in billionths of the currency unit
upper_bound_nanosknown_nanos plus the worst case of every attempt whose usage is unknown
completetrue when every attempt's cost is known exactly
boundedfalse when some unknown usage has no worst case, so upper_bound_nanos is not a bound

cost() is null when none of the agent's models (its model and fallback) has a price: threads doesn't guess money it can't count. If only some have one, requests to an unpriced model make the cost incomplete (complete: false) rather than free.

A whole team

Pass tree: true to add every subagent's spend, at any depth, each read from its own log:

const total = await thread.cost({ tree: true });

The total never hides spend it can't count. It is in the root's currency, or the first priced subagent's; if no thread in the tree has a price it is null, which means unknown, not free. complete and bounded turn false when a subagent called a model with no price, or was priced in another currency, or when a subagent has no log to prove it spent nothing (for example one a cancel stopped before it started). If a subagent's log can't be read, the whole call returns that error, naming the subagent, rather than a partial sum.

Prompt-cache breaks

A cache break is a turn whose prompt-cache reads dropped sharply, which usually means you paid full price for a prompt that was cached a moment before. cacheBreaks() / cache_breaks() lists them with the likely cause:

const breaks = await thread.cacheBreaks();
if (!breaks.ok) throw new Error(breaks.error.message);
for (const b of breaks.value) console.log(b.request_event_id, b.likely_cause);

likely_cause is settings_changed (model or settings switched), compacted, context_edited, tools_changed, ttl_expired (the gap since the last request outlived the cache) or unknown. The cache lifetime it judges by is context.cache_ttl_ms. By default it is the lifetime your models declare, so anthropic("claude-sonnet-5", { promptCache: "1h" }) judges by an hour; see Cache lifetimes.

Errors

All three return a value instead of throwing. A log that fails its integrity check comes back as log_corrupt; one written by a newer threads as unsupported_format or unsupported_critical_event. cost() also returns cost_overflow when a total passes 2^53-1 nanos, rather than a rounded number.

Edit on GitHub

On this page