Part of #174. nisse-review's turns on infra/identity#8 grew from 1 to 5 minutes until LiteLLM's 300s limit stopped one. Nothing recorded how long each model call took or how many tokens it read and wrote, so the cause had to be guessed from gaps between tool calls.
What changes
BudgetedClient.complete (every model call, both agents, every role) emits llm.call:
model, seconds;
input_tokens, output_tokens, cached_tokens;
answered_by when a fallback answered under another name.
A failed call emits llm.call with cause: failed, the error type and how long it ran, then re-raises.
last_usage gains cached: OpenAI's prompt_tokens_details.cached_tokens, Anthropic's cache_read_input_tokens. It shows whether the server reused the prompt between turns, which also matters for judging Strata / Qwen3.8-Flash-Next.
Tests
nisse-core/tests/test_llm.py: cached tokens parsed on both routes.
nisse-core/tests/test_context.py: an answered call logs time and tokens; a failed call logs its time and still raises. The "nothing is logged" test now checks only that no cut is logged.
Part of #174. nisse-review's turns on infra/identity#8 grew from 1 to 5 minutes until LiteLLM's 300s limit stopped one. Nothing recorded how long each model call took or how many tokens it read and wrote, so the cause had to be guessed from gaps between tool calls.
**What changes**
- `BudgetedClient.complete` (every model call, both agents, every role) emits `llm.call`:
- `model`, `seconds`;
- `input_tokens`, `output_tokens`, `cached_tokens`;
- `answered_by` when a fallback answered under another name.
- A failed call emits `llm.call` with `cause: failed`, the error type and how long it ran, then re-raises.
- `last_usage` gains `cached`: OpenAI's `prompt_tokens_details.cached_tokens`, Anthropic's `cache_read_input_tokens`. It shows whether the server reused the prompt between turns, which also matters for judging Strata / Qwen3.8-Flash-Next.
**Tests**
- `nisse-core/tests/test_llm.py`: cached tokens parsed on both routes.
- `nisse-core/tests/test_context.py`: an answered call logs time and tokens; a failed call logs its time and still raises. The "nothing is logged" test now checks only that no cut is logged.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
Each call through BudgetedClient emits llm.call: seconds, input,
output and cached tokens, and the answering model when a fallback
answered; a failed call logs how long it ran before it failed.
Usage now carries the server's cached-prompt count on both routes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Part of #174. nisse-review's turns on infra/identity#8 grew from 1 to 5 minutes until LiteLLM's 300s limit stopped one. Nothing recorded how long each model call took or how many tokens it read and wrote, so the cause had to be guessed from gaps between tool calls.
What changes
BudgetedClient.complete(every model call, both agents, every role) emitsllm.call:model,seconds;input_tokens,output_tokens,cached_tokens;answered_bywhen a fallback answered under another name.llm.callwithcause: failed, the error type and how long it ran, then re-raises.last_usagegainscached: OpenAI'sprompt_tokens_details.cached_tokens, Anthropic'scache_read_input_tokens. It shows whether the server reused the prompt between turns, which also matters for judging Strata / Qwen3.8-Flash-Next.Tests
nisse-core/tests/test_llm.py: cached tokens parsed on both routes.nisse-core/tests/test_context.py: an answered call logs time and tokens; a failed call logs its time and still raises. The "nothing is logged" test now checks only that no cut is logged.🤖 Generated with Claude Code
https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk