openai / openai/openai-agents-python

Agent.as_tool() nested subagent runs never hit prompt cache, even with byte-identical instructions/tools/prompt_cache_key

Open
#5,085 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
29.6k
Forks
4.8k
Avg merge
1d 20h
Merged PRs (30d)
123

Description

Setup: A top-level Agent with several subagents attached via sub_agent.as_tool(tool_name=..., tool_description=..., session=read_only_session_view), where read_only_session_view is a custom SessionABC implementation wrapping the parent's own session (read-only — add_items/pop_item/clear_session are no-ops). Model: gpt-5.6-luna. We explicitly set a stable session_id on the wrapper so the SDK's own cache-key grouping (resolve_run_grouping) resolves to ("session", <stable-hash>) instead of falling back to a random per-run key — confirmed this works (see below).

Observed: Across 42 real subagent calls that clear the 1,024-token minimum, 0 ever returned any cached_tokens — a flat 0% hit rate, including repeat calls to the same subagent, seconds apart, with verified byte-identical prefixes.

Reproduction — two consecutive calls to the same subagent, ~12 seconds apart:

Captured the literal kwargs passed to AsyncResponses.create() (patched at that layer to rule out any SDK-side transformation):

Call 1: instructions="Collect \n1. User first name\n2. User last name\n3. User email\n\n...
         ## Known conversation context\n\nToday's date: Fri, 18 Sep 2026 (UTC)."
        prompt_cache_key="agents-sdk:session:<same-hash-both-calls>"
        input: [54 items]
        usage: input_tokens=695, cached_tokens=0

Call 2: instructions=<byte-identical to Call 1 — diff returns nothing>
        prompt_cache_key="agents-sdk:session:<same-hash-both-calls>"
        input: [58 items — first 53 byte-equal to Call 1's, remainder is
                the new turn appended, i.e. a normal growing conversation]
        usage: input_tokens=838, cached_tokens=0

Also confirmed identical between the two calls: tools (empty in this pair), model, and every other kwarg we captured (store, previous_response_id, conversation, truncation, parallel_tool_calls, text, reasoning, extra_body, extra_headers — all consistently omitted/default on both).

What we ruled out before filing:

  • Unstable prompt_cache_key (confirmed identical both calls, via a raw capture at the responses.create() call site)
  • Tool definition drift
  • Instructions drift (confirmed byte-identical via diff, not just visual inspection)
  • Input prefix drift (53/54 leading items are dict-equal)
  • Cache TTL expiry (still 0% at gaps as short as 8.5s)
  • Being under the 1,024-token minimum (42/42 tested calls are above it)

For comparison: the parent agent in the same run, calling the same model via a normal (non-nested) Runner.run() with its own stable session, gets 78-90% cache hit rates over the same kind of growing conversation. The only structural difference we can identify is that these are nested calls originating from Agent.as_tool(), using a custom read-only Session implementation rather than the SDK's own session type.

Question: is there a known limitation where nested/as_tool()-originated Runner.run() calls are excluded from prompt caching regardless of session/prompt_cache_key, or is there something about a custom SessionABC subclass that would prevent this (e.g. is session_id alone insufficient, and something else internal is expected)?

Related but distinct: #2784 (base64 file inputs breaking cache) — different root cause, but confirms prompt_cache_key having "zero effect" on unexpected 0% hit rates is a recurring pattern in this SDK.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at Agent.as_tool() and the nested Runner.run() path, then trace the kwargs captured at AsyncResponses.create(), especially prompt_cache_key and session grouping. Compare the nested custom SessionABC implementation with the parent agent's normal session path. Done means identifying why qualifying nested calls receive zero cached_tokens and adding a regression test for the demonstrated repeated-call case.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.