Skip to content

fix(cache): scope prompt_cache_key by session to stop cross-session bucket sharing - #79161

Merged
kshitijk4poor merged 1 commit into
NousResearch:mainfrom
kshitijk4poor:review-78959
Aug 5, 2026
Merged

fix(cache): scope prompt_cache_key by session to stop cross-session bucket sharing#79161
kshitijk4poor merged 1 commit into
NousResearch:mainfrom
kshitijk4poor:review-78959

Conversation

@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Summary

Scopes prompt_cache_key by session_id so unrelated sessions stop sharing a cache bucket, while cron re-fires of the same job still share a stable key.

Root cause: prompt_cache_key was content-addressed only from the static request prefix (system instructions + tool schemas), with no session/tenant component. Any two unrelated sessions sharing the same system prompt and tool set collapsed onto the same cache routing bucket.

Changes

  • agent/transports/codex.py: New _cache_scope_from_session_id() normalizes cron per-fire timestamps; _content_cache_key() gains scope_id param; Codex headers now separate identity (session_id = raw) from routing (x-client-request-id = body key); xAI extra_body.prompt_cache_key honors top-level override
  • agent/transports/chat_completions.py: Threads session_id through _add_prompt_cache_key
  • agent/auxiliary_client.py: Auxiliary Codex adapter now scopes by session via set_runtime_main(session_id=...)
  • agent/turn_context.py: build_turn_context passes session_id to set_runtime_main
  • plugins/model-providers/nous/__init__.py + openrouter/__init__.py: Apply _cache_scope_from_session_id to sticky routing keys and x-grok-conv-id
  • Follow-up: hoisted _cache_scope to a local in build_kwargs (computed once instead of 4 times)

Closes #78941. Closes #79012. Closes #79013. Closes #79014. Closes #79015.

Salvaged from #78959 by @JoaoMarcos44 — cherry-picked with authorship preserved.

Validation

Before After
Cross-session isolation Same key for same content Different keys per session
Cron re-fire warmth Cold (timestamp in key) Warm (timestamp stripped)
Aux adapter scoping No session scope Scoped via set_runtime_main
xAI override Silently outrun Honored in extra_body
Tests 301 pass + 12 E2E assertions

…ucket sharing

Cherry-picked from PR NousResearch#78959 by @JoaoMarcos44 with authorship preserved.
Follow-up: hoist _cache_scope_from_session_id(session_id) to a local in
build_kwargs so it's computed once instead of 4 times per call.

Closes NousResearch#78941. Closes NousResearch#79012. Closes NousResearch#79013. Closes NousResearch#79014. Closes NousResearch#79015.

Co-authored-by: JoaoMarcos44 <joaomarcosdias444@gmail.com>
@kshitijk4poor
kshitijk4poor enabled auto-merge (rebase) August 5, 2026 07:09
@kshitijk4poor
kshitijk4poor merged commit 34c3f06 into NousResearch:main Aug 5, 2026
35 checks passed
kshitijk4poor added a commit that referenced this pull request Aug 15, 2026
…ation

Legacy compaction mode (compression.in_place: false) rotates the physical
session_id mid-conversation. The prompt-cache scope introduced in #79161 was
derived from that physical id, so every rotation moved the same conversation
into a fresh cache bucket - the prompt cache went cold at every rotation
boundary (#79017).

Fix: resolve a rotation-stable logical scope - the compression-lineage ROOT
of the current session (SessionDB.get_compression_lineage, fork-aware
post-#79193) - once per turn, memoized per transcript segment, and prefer it
over the physical session_id at every prompt_cache_key derivation site:

- agent/prompt_cache_scope.py (new): resolve_prompt_cache_scope(agent) -
  lineage-root walk with per-segment memo; falls back to the physical id
  when no DB is attached or the walk fails, degrading to pre-fix behavior.
- transports/codex.py: build_kwargs accepts cache_scope_id and prefers it
  for the body prompt_cache_key, the xAI x-grok-conv-id header, and the
  Codex x-client-request-id routing header. The Codex session_id header
  keeps the raw physical id (transcript identity, #57012 contract).
- transports/chat_completions.py: _add_prompt_cache_key accepts
  cache_scope_id with the same precedence.
- chat_completion_helpers.py: build_api_kwargs threads the resolved scope
  into all three build_kwargs call sites (codex, profile, legacy).
- auxiliary_client.py: set_runtime_main carries cache_scope; the aux
  Responses cache-key site prefers it over the physical session_id.
- turn_context.py: resolves the scope once per turn and threads it through
  set_runtime_main (no DB walk on the per-API-call hot path).

Scope semantics preserved from #79161: /new starts a fresh scope (new
lineage), /branch children, delegate subagents, and tool children stay
isolated (explicit-fork exclusion in get_compression_lineage), unrelated
sessions keep distinct buckets, and cron per-fire timestamps still
normalize via _cache_scope_from_session_id.

Default installs compact in place (session_id never rotates), so they hit
the memo and produce byte-identical keys to before.

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