AI spend that jumps overnight
Watch unit economics for AI features, not the total bill.
Narrower than signals-scout-ai-observability, which rotates through cost, latency, errors,
volume and eval lenses and deliberately does not run every lens each tick. Run this one when
cost is the thing you want watched every day rather than whenever the rotation reaches it. If
you already run the canonical scout and cost regressions are surfacing in time, you don't need
both.
Discriminator
Emit a report when cost per conversation rises above its trailing 14-day norm while
conversation volume stays within its own normal range. Write nothing when total cost rose but
cost per conversation is flat – that is usage growth.
The bar
A starting threshold, to be tuned against what this team's data actually does:
- ≥1.5× the trailing 14-day median cost per conversation, sustained across two consecutive
complete days, or
- ≥2× for a single complete day, where volume is within ±25% of its own 14-day median.
Use complete days only – a partial day reads as a spike every time. If the team's baseline is
genuinely noisy, widen the bar and write what you chose to the scratchpad rather than lowering
it and filing marginal reports.
Explore
- Compute daily cost per conversation over the last 14 days: total generation cost divided by
distinct conversations –
$ai_trace_id, or $ai_session_id where a conversation spans
traces – split per feature.
- Where a day clears the bar, decompose it. Check in this order, roughly the order of
likelihood:
- Model changed – compare the model distribution before and after.
- Cache hit rate dropped – compare
$ai_cache_read_input_tokens as a share of input
tokens. A system-prompt edit invalidates the cache prefix and multiplies input cost with no
change to model, token count, or generation count, so every other check here comes back
clean while the bill climbs. Check it early.
- Prompt grew – compare median input tokens per generation.
- Retries – compare generations per conversation, and check
$ai_is_error on the same
generation stream.
- Output grew – compare median output tokens.
- If retries look like the cause, confirm the failures actually cost money. A 429 rejection
bills nothing; only failures after the provider started generating do – timeouts mid-stream,
max_tokens truncation, malformed tool output, guardrail rejections. Check $ai_http_status
and $ai_stop_reason, and confirm the failed generations carry non-zero cost before calling
retries the cause.
- Name the most likely cause in the report. A cost report without a cause is just an invoice.
- Where a deploy sits in the window, include the commit range.
Disqualifiers
- Conversation volume moved as much as cost did – that's growth.
- The change is a deliberate, announced model upgrade.
- The change follows a deliberate caching-strategy change – a shortened prefix or a switched
cache TTL raises unit cost on purpose.
- The provider changed its prices. This scout does not watch price lists, so confirm the
per-token rate is unchanged before attributing a rise to your own code.
- The window covers a backfill, replay, or eval run rather than real traffic.
- The feature is new enough that there's no stable norm to compare against.
1---2name: signals-scout-ai-cost3description: Watches cost per conversation for AI features and reports when it breaks from its recent norm while volume holds steady, attributing the change to a model, prompt, cache, or retry cause.4---56# AI spend that jumps overnight78Watch unit economics for AI features, not the total bill.910Narrower than `signals-scout-ai-observability`, which rotates through cost, latency, errors,11volume and eval lenses and deliberately does not run every lens each tick. Run this one when12cost is the thing you want watched every day rather than whenever the rotation reaches it. If13you already run the canonical scout and cost regressions are surfacing in time, you don't need14both.1516## Discriminator1718Emit a report when cost per conversation rises above its trailing 14-day norm **while19conversation volume stays within its own normal range**. Write nothing when total cost rose but20cost per conversation is flat – that is usage growth.2122### The bar2324A starting threshold, to be tuned against what this team's data actually does:2526- **≥1.5× the trailing 14-day median cost per conversation, sustained across two consecutive27 complete days**, or28- **≥2× for a single complete day**, where volume is within ±25% of its own 14-day median.2930Use complete days only – a partial day reads as a spike every time. If the team's baseline is31genuinely noisy, widen the bar and write what you chose to the scratchpad rather than lowering32it and filing marginal reports.3334## Explore35361. Compute daily cost per conversation over the last 14 days: total generation cost divided by37 distinct conversations – `$ai_trace_id`, or `$ai_session_id` where a conversation spans38 traces – split per feature.392. Where a day clears the bar, decompose it. Check in this order, roughly the order of40 likelihood:41 - **Model changed** – compare the model distribution before and after.42 - **Cache hit rate dropped** – compare `$ai_cache_read_input_tokens` as a share of input43 tokens. A system-prompt edit invalidates the cache prefix and multiplies input cost with no44 change to model, token count, or generation count, so every other check here comes back45 clean while the bill climbs. Check it early.46 - **Prompt grew** – compare median input tokens per generation.47 - **Retries** – compare generations per conversation, and check `$ai_is_error` on the same48 generation stream.49 - **Output grew** – compare median output tokens.503. If retries look like the cause, confirm the failures actually cost money. A 429 rejection51 bills nothing; only failures after the provider started generating do – timeouts mid-stream,52 `max_tokens` truncation, malformed tool output, guardrail rejections. Check `$ai_http_status`53 and `$ai_stop_reason`, and confirm the failed generations carry non-zero cost before calling54 retries the cause.554. Name the most likely cause in the report. A cost report without a cause is just an invoice.565. Where a deploy sits in the window, include the commit range.5758## Disqualifiers5960- Conversation volume moved as much as cost did – that's growth.61- The change is a deliberate, announced model upgrade.62- The change follows a deliberate caching-strategy change – a shortened prefix or a switched63 cache TTL raises unit cost on purpose.64- The provider changed its prices. This scout does not watch price lists, so confirm the65 per-token rate is unchanged before attributing a rise to your own code.66- The window covers a backfill, replay, or eval run rather than real traffic.67- The feature is new enough that there's no stable norm to compare against.