[Bug]: Prompt cache doesn't work with subagents (when dynamic instructions are present, e.g. websearch)
What happened?
Human written
A subagent run with Anthropic prompt caching enabled doesn't actually cache the input properly, meaning that every tool result is re-billed at full price for the growing inputs. The code assumes there will be a user message which it can prepend with the dynamic parts, but the only user message for a subagent is the initial one the outer agent writes.
A run of 9 parallel subagents made 339 Sonnet calls cost me $40ish, which is a little higher than expected.
What follows is an analysis by Fable.
AI written for your AI
buildSystemRunnable in src/agents/AgentContext.ts, when both stable and dynamic instructions exist:
- Moves the dynamic text into a HumanMessage inserted at the index of the last human message (getPromptCacheDynamicTailIndex).
- Marks only the messages before that index (addStablePromptCacheMarkers).
- Skips addTailCacheControl because dynamicTail.length !== 0.
A subagent's only human message is the task at index 0. The stable prefix is therefore empty, no message is marked, and the tail marker is skipped. The same gap affects the primary agent's tool loop within a turn, since everything after the latest user message is unmarked.
Present in 3.7.19 and unchanged on main.
Proposed fix
After building body, if a dynamic tail was inserted and no message in body carries a cache marker, call addTailCacheControl(body, ttl). This keeps the request within Anthropic's four-breakpoint limit, because the prefix markers that would otherwise use those slots are absent in exactly this case.
A fuller variant: always place one tail marker and cap prefix markers at one, so tool loops after the latest user turn are cached in every run.
Version Information
f0dfeed2b0d26708029ce040d01ac2e649ecd6f0
Steps to Reproduce
1 .Anthropic provider, promptCache: true (the default). 2. Agent with a non-empty system prompt and web search enabled. Web search adds a timestamp line to additional_instructions, so dynamicInstructions is non-empty. Any other source of dynamic instructions triggers it too. 3. Create outer agent that spawns it as an isolated subagent and let it loop over tool calls.
Watch usage: cache_creation_input_tokens is 0 and cache_read_input_tokens is a small constant on every call.
What browsers are you seeing the problem on?
No response
Relevant log output
no relevant logsScreenshots
No response
Code of Conduct
- I agree to follow this project's Code of Conduct
Source: danny-avila/LibreChat