Thinking (reasoning) metrics
Iterion surfaces two per-node extended-thinking metrics for LLM nodes:
thinking_ms— wall-clock time spent in thinking blocks (milliseconds).thinking_tokens— count of thinking tokens, shown with a~because it may be an approximation depending on the backend (see below; on claw it is the provider's exact billed count whenever the API reports one).
They appear on four surfaces:
- Leveled logs — a
🧠block per step / per node. When the thinking content was captured, the full reasoning text is logged as a foldableLogBlock([node#iter/claw] thinking step N (~T tok, Dms):or[node#iter/claude-code] thinking ~T tok, Dms:followed by the│-indented body) — the studio's run-log view renders it collapsed with an expand/unfold toggle, like tool I/O and assistant text. When only the counts are known, the metrics-only line remains ([node#iter/claw] step N thinking: ~T tok, Dms). events.jsonl—thinking_ms/thinking_tokenskeys onllm_step_finishedevents, and_thinking_ms/_thinking_tokensstamped on the node output ofnode_finished. The thinking text is log-only by design: events.jsonl stays bounded to small payloads (big bodies live in sidecar blobs), and run.log is the surface that renders it.- Studio — the run-view
NodeDetailPanelheader (🧠 ~T tok · Ds). iterion report—Thinking Tokens/Thinking Timerows in the metrics table.
How tokens are counted
On the claw path, the preferred source is the provider's exact billed count: Anthropic's usage.output_tokens_details.thinking_tokens (raw internal reasoning, independent of thinking.display) and OpenAI's output_tokens_details.reasoning_tokens both flow through claw-code-go's UsageDelta into Usage.ReasoningTokens. This is authoritative — it counts the model's full internal reasoning, which with summarized display is systematically larger than the visible summary.
When the provider omits the breakdown (and always on the claude_code path, whose stream-json usage carries no details), iterion falls back to re-encoding the visible thinking text with a real BPE tokenizer (o200k_base, vendored and offline) in pkg/backend/thinktokens. o200k_base is OpenAI's encoding, not Anthropic's (whose tokenizer is not public), so that figure is a comparable-across-backends estimate — and with summarized display it counts the summary, not the underlying reasoning. It falls back to a chars/4 heuristic if the codec fails to load.
How time is measured
- claw (in-process): exact. The streaming aggregator (generation.go) measures each thinking block from its
content_block_starttocontent_block_stop. This relies on claw-code-go surfacingthinking_delta/signature_deltain its SSE parser. - claude_code (subprocess): best-effort. The Claude Code SDK delivers assembled
ThinkingBlocks (not deltas), so there is no intra-block timing. Iterion attributes the wall-clock gap since the previous stream item to a thinking-bearing assistant message — a proxy, not an exact measurement.
Model-dependent redaction — the thinking.display parameter
Whether the thinking content is visible is governed by the Anthropic API's thinking.display parameter, not by iterion (adaptive-thinking docs):
display: "summarized"returns a summary of the reasoning (produced by a separate summarizer model — the raw chain-of-thought is never returned on Claude 4 models; that policy is anti-distillation/misuse). This is the request-time default on Opus 4.6 / Sonnet 4.6 and earlier.display: "omitted"returns thinking blocks with an emptythinkingfield and only the encryptedsignature(full reasoning, decryptable only by the API for multi-turn continuity). This is the default on Opus 4.8 / 4.7 and Sonnet 5 — a silent change from Opus 4.6. Billing is identical either way (full thinking tokens); omitting only reduces time-to-first-text-token.
Consequences per backend (verified against claude CLI 2.1.195 by inspecting raw stream-json frames):
- claw — requests
display: "summarized"on every adaptive request (claw ≥ a27d632), so Anthropic models via API key show summarized thinking even on Opus 4.8. Override withCLAW_ANTHROPIC_THINKING_DISPLAY=omitted|off. OpenAI reasoning summaries flow via the Responses API (reasoning.summary=auto). - claude_code — in
--print(headless/SDK) mode the CLI defaults thinking display to omitted on Opus 4.8+ while its own interactive UI requests the summary (that's why the VS Code extension shows thinking live). The CLI has an undocumented--thinking-display summarized|omittedflag (present in 2.1.195; absent from--help), which iterion passes assummarizedby default — so opus thinking summaries fold in run.log like sonnet's. Override withITERION_CLAUDE_CODE_THINKING_DISPLAY=omitted(latency) oroff(don't pass the flag — required for claude CLIs that predate it and reject unknown options). When the flag can't be applied and blocks arrive signed-but-empty, iterion still logs🧠 thinking: Nms (content withheld by provider)with the timing metric (tokens stay 0 — nothing to re-encode; even the session transcript and--include-partial-messagesdeltas are empty then).
