diff --git a/gateway/slash_commands_session.py b/gateway/slash_commands_session.py index bfdb3cb730..17dbcda6b4 100644 --- a/gateway/slash_commands_session.py +++ b/gateway/slash_commands_session.py @@ -1039,6 +1039,14 @@ class GatewaySessionCommandsMixin: # ``_branched_from`` keeps the branch visible in /resume and /sessions after the parent is # reopened and re-ended. ALL routing columns go in at CREATE time: a crash before # switch_session() records the peer would otherwise leave the branch unroutable. + # The child sends the parent's exact system prompt: a row without one makes the branch's + # first turn rebuild (re-probing the workspace) and forfeits the warm cache the copied + # transcript buys. + parent_prompt = None + try: + parent_prompt = ((await self._session_db.get_session(parent_session_id)) or {}).get("system_prompt") + except Exception: + logger.debug("branch: parent system prompt read failed for %s", parent_session_id, exc_info=True) try: await self._session_db.create_session( session_id=new_session_id, @@ -1048,7 +1056,7 @@ class GatewaySessionCommandsMixin: parent_session_id=parent_session_id, user_id=dest_source.user_id, session_key=dest_key, chat_id=dest_source.chat_id, chat_type=dest_source.chat_type, thread_id=dest_source.thread_id, origin_json=_branch_origin_json, - display_name=current_entry.display_name) + display_name=current_entry.display_name, system_prompt=parent_prompt or None) except Exception as e: logger.error("Failed to create branch session: %s", e) return t("gateway.branch.create_failed", error=e) diff --git a/tests/gateway/test_branch_routing_columns.py b/tests/gateway/test_branch_routing_columns.py index d5754e74be..1e39fb6d16 100644 --- a/tests/gateway/test_branch_routing_columns.py +++ b/tests/gateway/test_branch_routing_columns.py @@ -153,3 +153,32 @@ class TestBranchRoutingColumns: _ = real_switch_session # silence unused + +class TestBranchSystemPrompt: + @pytest.mark.asyncio + async def test_branched_session_carries_parent_system_prompt(self, store): + """The branch copies the parent transcript byte-for-byte so its first + turn can hit the warm prefix cache, but create_session() at branch + time dropped the system prompt: the child row's system_prompt column + was None even though the parent row held one. Mirrors the CLI/TUI fix + in 2ec12952 for the third branch-creation site (the gateway).""" + source = _make_source() + parent_entry = store.get_or_create_session(source) + store._db.update_system_prompt(parent_entry.session_id, "PARENT PROMPT") + store._db.append_message(parent_entry.session_id, role="user", content="hello") + store._db.append_message(parent_entry.session_id, role="assistant", content="world") + + runner = _make_branch_runner(store) + await runner._handle_branch_command(_make_event("/branch")) + + new_entry = store.get_or_create_session(source) + assert new_entry.session_id != parent_entry.session_id + + row = store._db.get_session(new_entry.session_id) + assert row is not None + assert row["system_prompt"] == "PARENT PROMPT", ( + "branched session lost the parent's system prompt — its first " + "turn will re-probe the workspace and forfeit the warm cache " + "the copied transcript buys" + ) +