Repository navigation
Forked subagents lose prompt-cache lineage for inherited parent context #24704
Description
What version of Codex CLI is running?
codex-cli 0.133.0
Which model were you using?
gpt-5.5
What platform is your computer?
macOS
What issue are you seeing?
Forked subagents appear to lose the parent thread's prompt-cache lineage even though they inherit a large prefix of the parent conversation.
When a subagent is spawned with inherited context, the child thread usually starts from a long parent prefix that should be highly cacheable. However, the child currently gets its own thread-derived prompt_cache_key, so the backend appears to treat the fork as an unrelated request instead of a continuation/fork of the parent cache lineage.
The practical effect is that fork_context=true subagents can pay mostly uncached input-token cost for a prefix that was already present and cached in the parent thread.
This is especially expensive for workflows that intentionally load substantial project context in the main thread and then delegate small follow-up tasks to forked subagents.
What steps can reproduce the bug?
- Start a normal Codex session using
gpt-5.5. - Add enough stable context to the parent thread, for example by discussing or loading a large repo/file/document so the parent has a substantial cached prefix.
- Spawn a subagent with inherited/forked context for a small task.
- Inspect the usage events in the rollout/session JSONL, especially
input_tokensandcached_input_tokens, for the forked child turn. - Compare that with either:
- a normal continuation in the parent thread, or
- a patched build where the forked child preserves the parent prompt-cache lineage.
Expected behavior
A forked subagent that inherits parent context should preserve the relevant parent prompt-cache lineage for the inherited prefix.
The child should still have its own thread/session identity, but the prompt-cache key used for the reusable inherited prefix should not be replaced solely because the fork has a new child thread id.
In other words, forked subagents should be isolated as conversation threads, but not forced into unrelated prompt-cache namespaces for the parent prefix they inherit.
Actual behavior
The forked child receives a new thread-derived prompt_cache_key.
Because the cache key changes at fork startup, the inherited parent prefix is not reused as effectively as expected. The observed cached_input_tokens for forked subagents is much lower than it should be for a mostly identical inherited prefix.
Why this matters
fork_context=true is most useful when the parent has already accumulated important project/task context and a subagent needs to perform a bounded task using that context.
Without cache-lineage preservation, that pattern becomes unexpectedly expensive:
- each fork can rebill a large inherited prefix as uncached input;
- multi-subagent workflows multiply the cost;
- users may see rapid quota/cost drain even when the actual per-subagent task prompt is small.
Related issues / PRs
This is related to, but more specific than:
- App-server sessions need stable prompt-cache key support for identical startup context #21796: stable prompt-cache key support for identical startup context across app-server sessions.
- Low cache hit rate when Codex integrates with GPT-5.5 #20301: broader low cache hit rate reports with GPT-5.5.
- Feature request: batch-spawn forked subagents from a shared preloaded context #18683: feature request for batch-spawning forked subagents from shared preloaded context.
- preserve fork prompt cache state #20909: a closed PR that attempted to preserve fork prompt-cache state.
Unlike #20301, this report is not about a general model-level cache anomaly. It is specifically about forked subagents losing cache lineage because the fork gets a new thread-derived prompt-cache key.
Suggested fix
When starting a forked subagent, preserve the parent's prompt-cache lineage for the inherited context instead of deriving a fresh key only from the child thread id.
A narrow fix is to carry an inherited prompt-cache key through fork startup and use it when constructing the child session/client, while keeping the child thread id and conversation state separate.
Reference implementation: john-lu-ptc/codex#1
Activity
Mike-Dax commentedon May 27, 2026
To add to this,
/fork,/sideshould not have to pay the cost of redoing prefill for the entire shared prefix. I understand that from the first prefill step onward, the conversation is prefix-cached, but decoupling the prompt_cache_key from the thread_id seems like a pretty cheap way to reduce TTFT.Necmttn commentedon Jun 21, 2026
A forked child needs cache lineage metadata separate from thread identity.
At spawn time, record parent session id, parent prefix hash/cache key, inherited item range, child delta start, and whether cache lineage was accepted by the backend. Usage should then split inherited-prefix input from child-new input.
That makes the behavior auditable even before the cache policy changes. If a child pays uncached cost for an inherited prefix, the usage ledger can show "fork cache lineage lost" instead of making it look like ordinary new context.
Generated with ax.
ignatremizov commentedon Jul 10, 2026
GPT-5.6 makes this materially more expensive and exposes two additional implementation details that a cache-key-only fix would miss.
OpenAI now bills GPT-5.6 cache writes at 1.25x uncached input while reads cost 0.1x. A fork that writes a large inherited prefix and completes in a single Responses request can therefore terminate before receiving any read that amortizes the write. One successful reuse is enough to recover the premium, so this is specifically acute for short-lived, one-response children rather than every subagent task.
The affected fork modes include:
fork_context=true, which carries full parent history;fork_turns="all", which has the same full-history exposure;fork_turns="N", which carries a smaller recent suffix and therefore cannot generally reuse the parent's complete prompt prefix, but can still pay for a child-specific write;/side, which creates an ephemeral full-history fork intended for a quick question and is therefore especially likely to be discarded after one response./sidehas another cache-breaking behavior beyond its fresh thread-derived key. It appendsSIDE_DEVELOPER_INSTRUCTIONSto the fork configuration's request-level developer instructions before the child request:https://github.com/openai/codex/blob/69c48bfc5b83bc8f5a5b9b4120a3114d84e41c9f/codex-rs/tui/src/app/side.rs#L469-L481
That changes the beginning of the rendered prompt. Merely inheriting the parent's
prompt_cache_keymay therefore still fail exact-prefix matching. The implementation then injects a substantially overlapping side-conversation boundary at the end of the inherited history:https://github.com/openai/codex/blob/69c48bfc5b83bc8f5a5b9b4120a3114d84e41c9f/codex-rs/tui/src/app/side.rs#L24-L52
https://github.com/openai/codex/blob/69c48bfc5b83bc8f5a5b9b4120a3114d84e41c9f/codex-rs/tui/src/app/side.rs#L594-L596
For cache reuse, the side-specific control block should instead be injected as a developer item after the inherited parent history and before the new side question, leaving the inherited prefix and request-level instructions byte-identical. An explicit breakpoint at the inherited-prefix boundary would make that behavior more deterministic. The child can retain an independent thread/conversation identity while inheriting a cache-lineage key scoped to the parent/fork family.
There is also currently an observability gap. GPT-5.6 Responses report writes in
input_tokens_details.cache_write_tokens, but Codex's SSE usage parser only readscached_tokens:https://github.com/openai/codex/blob/69c48bfc5b83bc8f5a5b9b4120a3114d84e41c9f/codex-rs/codex-api/src/sse/responses.rs#L123-L152
A regression test should record both cache-write and cache-read tokens for:
fork_turns="all"and a bounded fork;/sidequestion;The invariant should be that inherited, unchanged parent context is not billed as an unrelated cache write solely because the fork has a new thread ID, while genuinely new child instructions and task content remain isolated.
GPT-5.6 prompt-caching documentation: https://developers.openai.com/api/docs/guides/prompt-caching
denoflore commentedon Jul 10, 2026
I confirmed the current spawn path still falls back to the newly created thread ID for
prompt_cache_keyunless an override is supplied, so forked and sibling subagents can lose the parent's cache-routing lineage.I prepared a narrow patch on current
main:The patch carries the parent's effective cache key through thread spawn and session configuration. Child thread IDs, conversation state, permissions, and exact-prefix matching remain independent. It covers full-history forks, fresh-context subagents, nested workers, and agent-job workers while retaining the Guardian-specific cache namespace as the fallback.
Validation on Windows:
cargo build -p codex-cli: passedcodex-coreClippy: passedcodex-corerun: 2,548 passed, 44 failed, 7 timed out in unrelated CLI, MCP, remote-process, and sandbox testsThe upstream repository currently has
pull_request_creation_policy: collaborators_only, so GitHub rejected PR creation from this fork. A maintainer can fetch the patch directly:audexdev commentedon Aug 28, 2026
I rechecked this on
codex-cli 0.147.0/rust-v0.147.0.The
/sidepath still creates an ephemeral fork with a new thread/session ID, andModelClient::prompt_cache_key()still falls back to that newsession_idwhen no override is supplied. I could not find a/side-specific cache-key override. Guardian review sessions still explicitly use a parent-scoped override.I also audited my local Codex usage read-only. Across 10 long parent threads, normal continuation cache-hit rates had a median of about 98%. Several parent turns with 180k–300k input tokens were about 99% cached.
I could not directly measure the first
/sideturn because side conversations are ephemeral and their post-fork token-usage data is not persisted in the rollout/state data I inspected. So I cannot confirm the actual backend cache-hit rate for the first side turn from existing local history alone.This makes an in-process regression test especially useful: compare
cached_input_tokensfor:/sideturn,I also use Side Chat heavily through Codex Desktop. If Desktop uses the same or equivalent fork/cache-lineage behavior, it would be useful to verify that surface as well.
VolkumDev commentedon Aug 28, 2026
Incompetent OpenAI folks :)
feat(web): continue a side chat past its first turn (#169)