Skip to content

fix(usage): keep cached and thinking tokens in reflect and retain totals - #5675

Merged
nicoloboschi merged 2 commits into
mainfrom
fix/reflect-usage-cached-thoughts
Oct 16, 2026
Merged

nicoloboschi merged 2 commits into
mainfrom
fix/reflect-usage-cached-thoughts

Conversation

@nicoloboschi

@nicoloboschi nicoloboschi commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #5487.

Problem

Reflect responses always reported cached_tokens: 0 and thoughts_tokens: 0. The agent sums them correctly, but reflect_async rebuilt TokenUsage with only input/output/total.

The same bug existed in more places:

  • Sync retain over HTTP summed usage across strategy groups field by field and dropped both fields.
  • Batch-API retain built usage from the raw provider block and never read prompt_tokens_details.cached_tokens or completion_tokens_details.reasoning_tokens.
  • Gemini and Anthropic batch translators never wrote those detail blocks in the first place, so even a fixed reader got zeros from them.

Fix

  • memory_engine.py: pass cached_tokens / thoughts_tokens through.
  • http.py: use TokenUsage.__add__, which already carries every field.
  • fact_extraction.py: read batch usage with visible_token_usage, the same reader live OpenAI-compatible calls use (so reasoning is split out of output, like live calls).
  • gemini_llm.py / anthropic_llm.py: batch translators emit cached (and Gemini thinking) counts in the OpenAI detail blocks.

Tests

  • test_reflect_response_keeps_cached_and_thoughts_tokens — stubbed agent returns 123/45, asserted on the reflect result.
  • test_batch_api_usage_keeps_cached_and_reasoning_tokens — batch body with details, asserts cached/thoughts and visible output.
  • Gemini/Anthropic batch translator tests now assert the detail blocks.

…als (#5487)

The reflect response rebuilt TokenUsage from input/output/total only, so the
cached and thinking tokens the agent had already summed always came back as 0.
The same field-by-field copy dropped them from the sync retain HTTP total, and
batch-API extraction never read them from the provider's usage block at all.

Reflect now passes both fields through, sync retain sums with TokenUsage's own
+, and batch results go through visible_token_usage like a live call does.
…pic batch results

Review follow-up: the batch consumer now reads OpenAI detail blocks, but the
Gemini and Anthropic batch translators never wrote them, so their cached and
thinking counts were still dropped before retain saw them. Gemini now maps
cachedContentTokenCount / thoughtsTokenCount, Anthropic maps
cache_read_input_tokens, matching what their live calls report.
@nicoloboschi
nicoloboschi merged commit 3d169f5 into main Oct 16, 2026
232 of 234 checks passed
@nicoloboschi
nicoloboschi deleted the fix/reflect-usage-cached-thoughts branch October 16, 2026 14:42
audichuang added a commit to audichuang/hindsight that referenced this pull request Oct 22, 2026
- FORK-COST-ACCOUNTING.md: H14-H18 and the dependency list now name the
  upstream vectorize-io#5693 code the hooks sit on (claude_code_llm's sdk_usage and
  _sdk_token_usage, codex_llm's _usage_from_completed_event and its
  token_usage / usage call-site variables); the marked raw_usage = None
  lines no longer exist.
- tests/test_fork_reflect_usage.py: v1.1.0's reflect_async reads
  reflect_use_decision_model / mental_model_refresh_use_decision_model from
  the resolved bank config (vectorize-io#5382), so the fake config carries both (off).
  The fork's vectorize-io#5487 backport it guards is otherwise identical to upstream
  vectorize-io#5675, and its code half came out empty in the rebase.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nicoloboschi nicoloboschi mentioned this pull request Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

reflect response drops cached_tokens / thoughts_tokens when converting agent usage (always 0)

1 participant