diff --git a/agent/background_review.py b/agent/background_review.py index ed6df7e3ed4b2..92dbf8003a026 100644 --- a/agent/background_review.py +++ b/agent/background_review.py @@ -745,6 +745,13 @@ def _run_review_in_thread( # _cached_system_prompt below. if not _routed: _fork_kwargs["reasoning_config"] = getattr(agent, "reasoning_config", None) + # Gateway session context is appended to the parent's cached + # system prompt at API-call time through this field. Preserve + # it on same-model forks so the complete effective system + # prompt remains byte-identical and can reuse the warm prefix. + _fork_kwargs["ephemeral_system_prompt"] = getattr( + agent, "ephemeral_system_prompt", None + ) review_agent = AIAgent( model=_rt.get("model") or agent.model, max_iterations=16, diff --git a/tests/run_agent/test_background_review_cache_parity.py b/tests/run_agent/test_background_review_cache_parity.py index 951a644a2d6f7..d050a148ba717 100644 --- a/tests/run_agent/test_background_review_cache_parity.py +++ b/tests/run_agent/test_background_review_cache_parity.py @@ -33,6 +33,9 @@ def _make_agent_stub(agent_cls): "PARENT-SYSTEM-PROMPT-BYTES — must be inherited verbatim " "for prefix-cache parity" ) + agent.ephemeral_system_prompt = ( + "WebUI session context:\n- Pinned per-request gateway context" + ) import datetime as _dt agent.session_start = _dt.datetime(2026, 1, 1, 12, 0, 0) agent._MEMORY_REVIEW_PROMPT = "review memory" @@ -88,6 +91,7 @@ def _make_recorder_class(captured=None, record_on_run=()): self.suppress_status_output = None self.session_start = None self.session_id = None + self.ephemeral_system_prompt = kwargs.get("ephemeral_system_prompt") def run_conversation(self, *args, **kwargs): if captured is not None: @@ -152,6 +156,44 @@ def test_review_fork_inherits_parent_cached_system_prompt(): ) +def test_review_fork_inherits_parent_ephemeral_system_prompt(): + """The fork must send the parent's complete effective system prompt. + + Gateway session context is appended through ``ephemeral_system_prompt`` at + API-call time, outside ``_cached_system_prompt``. Copying only the cached + base therefore makes every background review diverge at the gateway block + and miss the parent's warm prefix cache. + """ + import run_agent + + agent = _make_agent_stub(run_agent.AIAgent) + captured = {} + _Recorder = _make_recorder_class( + captured, + record_on_run=("_cached_system_prompt", "ephemeral_system_prompt"), + ) + + with patch.object(run_agent, "AIAgent", _Recorder), \ + patch("threading.Thread", _SyncThread): + agent._spawn_background_review( + messages_snapshot=[], + review_memory=True, + review_skills=False, + ) + + parent_effective = ( + getattr(agent, "_cached_system_prompt") + + "\n\n" + + getattr(agent, "ephemeral_system_prompt") + ).strip() + fork_effective = ( + captured["_cached_system_prompt"] + + "\n\n" + + captured["ephemeral_system_prompt"] + ).strip() + assert fork_effective == parent_effective + + def test_review_fork_pins_session_start_and_session_id(): """Defensive complement to cached-system-prompt inheritance.