feat(reflect): step-by-step context caching for the Gemini tool loop#2540
Open
nicoloboschi wants to merge 1 commit into
Open
feat(reflect): step-by-step context caching for the Gemini tool loop#2540nicoloboschi wants to merge 1 commit into
nicoloboschi wants to merge 1 commit into
Conversation
Reflect's cost is dominated by the tool-result context re-sent on every turn, not the static system prefix. Caching only the fixed system+tools prefix (Gemini CachedContent) recovered ~9% of input on a short reflect and 0% via implicit caching. Instead, roll a cache forward one step at a time: after each turn extend the CachedContent to cover that turn's full input, so the next `auto` turn reuses the entire prior conversation at the cached-input rate and sends only its own new tool results as the delta. Each retrieved payload is billed fresh exactly once, then cached thereafter. Measured on gemini-2.5-flash-lite: short loops ~29%, deep loops ~74-81% cached input (deepest turns ~99%). - GeminiCacheManager: cache `contents` (growing conversation), session- tracked create/delete for ephemeral per-reflect caches. - call_with_tools gains `cached_prefix_message_count` so only the un-cached tail is sent; forced (tool_config) turns stay uncached as Gemini requires. - The next-turn cache create is scheduled to run concurrently with tool execution, hiding its latency; the reflect wrapper deletes the session (all caches) on every exit path, with a short TTL as backstop. - New HINDSIGHT_API_REFLECT_PROMPT_CACHE_ENABLED flag (default true) to disable reflect caching independently of the global prompt cache. Tests: deterministic step-by-step contract test, cache-manager units, and a real-Gemini cached-ratio measurement test.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Reflect's LLM cost is dominated by the tool-result context re-sent on every turn, not the static system prefix. Caching only the fixed system+tools prefix (Gemini
CachedContent) recovered ~9% of input on a short reflect; Gemini implicit caching gave 0% on short loops (and is unreliable in general).This PR rolls a step-by-step context cache forward through the agent's tool loop: after each turn the
CachedContentis extended to cover that turn's full input, so the nextautoturn reuses the entire prior conversation at the cached-input rate and sends only its own new tool results as the delta. Each retrieved payload is billed at full price exactly once, then cached thereafter.Measured (gemini-2.5-flash-lite)
On deep loops the per-call ratio ramps to ~99% on the deepest turns (each turn reuses nearly the whole prior context). Unlike implicit caching, this is deterministic and works on short loops too.
How it works
GeminiCacheManager: cachescontents(the growing conversation), with session-tracked create/delete for ephemeral per-reflect caches.call_with_toolsgainscached_prefix_message_countso only the un-cached tail is sent. Forced (tool_config) retrieval turns stay uncached — Gemini rejectscached_content+tool_config— but the cache still advances underneath them, so the firstautoturn inherits a cache covering the whole forced phase.autocall.HINDSIGHT_API_REFLECT_PROMPT_CACHE_ENABLED(defaulttrue) disables reflect caching independently of the global prompt cache.Tests
autoturn reuses the previous turn's full input; caches torn down once) — no real LLM.GeminiCacheManagerunit tests (create/track/delete/soft-fallback/error-swallow), no network.HINDSIGHT_RUN_GEMINI_EVALS).Not an LLM-behaviour change (no prompt/classification changes), so no judge test needed.