Skip to content

Forked subagents lose prompt-cache lineage for inherited parent context #24704

Open

Description

@john-lu-ptc

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?

  1. Start a normal Codex session using gpt-5.5.
  2. 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.
  3. Spawn a subagent with inherited/forked context for a small task.
  4. Inspect the usage events in the rollout/session JSONL, especially input_tokens and cached_input_tokens, for the forked child turn.
  5. 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:

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

  1. added
    bugSomething isn't working
    CLIIssues related to the Codex CLI
    subagentIssues involving subagents or multi-agent features
    on May 27, 2026
  2. Mike-Dax commented on May 27, 2026

    @Mike-Dax

    To add to this, /fork, /side should 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.

  3. Necmttn commented on Jun 21, 2026

    @Necmttn

    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.

  4. ignatremizov commented on Jul 10, 2026

    @ignatremizov

    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:

    • MultiAgentV1 fork_context=true, which carries full parent history;
    • MultiAgentV2 fork_turns="all", which has the same full-history exposure;
    • bounded V2 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.

    /side has another cache-breaking behavior beyond its fresh thread-derived key. It appends SIDE_DEVELOPER_INSTRUCTIONS to 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_key may 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 reads cached_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:

    1. a parent continuation;
    2. a V1 full-history fork;
    3. V2 fork_turns="all" and a bounded fork;
    4. a one-response /side question;
    5. a second request within the same child, demonstrating that child-local reuse still works.

    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

  5. denoflore commented on Jul 10, 2026

    @denoflore

    I confirmed the current spawn path still falls back to the newly created thread ID for prompt_cache_key unless 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: passed
    • scoped codex-core Clippy: passed
    • fresh-context cache-lineage integration test: passed
    • forked-context cache-lineage integration test: passed
    • broader codex-core run: 2,548 passed, 44 failed, 7 timed out in unrelated CLI, MCP, remote-process, and sandbox tests

    The 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:

    git fetch https://github.com/denoflore/codex.git denoflore/fix/preserve-subagent-cache-lineage
    git cherry-pick fee620ed4c551690801c9f22354ca5a1725f6814
  6. audexdev commented on Aug 28, 2026

    @audexdev

    I rechecked this on codex-cli 0.147.0 / rust-v0.147.0.

    The /side path still creates an ephemeral fork with a new thread/session ID, and ModelClient::prompt_cache_key() still falls back to that new session_id when 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 /side turn 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_tokens for:

    1. a high-hit parent continuation,
    2. an immediate short /side turn,
    3. the next parent continuation.

    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.

  7. VolkumDev commented on Aug 28, 2026

    @VolkumDev

    Incompetent OpenAI folks :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    CLIIssues related to the Codex CLIbugSomething isn't workingsubagentIssues involving subagents or multi-agent features

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions