diff mrjunejune/inference/copilot_sidecar_test.py @ 265:056790c4fb0d

add role-aware Epi assistant prompts Add verified June knowledge, guest/member/admin Copilot profiles, profile-isolated session recovery, animated Epi greetings, and a single authoritative runtime config workflow for inference. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <me@mrjunejune.com>
date Fri, 07 Aug 2026 10:50:30 -0700
parents 1f9877b637e9
children
line wrap: on
line diff
--- a/mrjunejune/inference/copilot_sidecar_test.py	Fri Aug 07 07:34:12 2026 -0700
+++ b/mrjunejune/inference/copilot_sidecar_test.py	Fri Aug 07 10:50:30 2026 -0700
@@ -1,9 +1,20 @@
 import asyncio
+import hashlib
 import types
 import unittest
+import uuid
 from dataclasses import replace
 
-from mrjunejune.inference.copilot_sidecar import Sidecar, SidecarConfig
+from mrjunejune.inference.copilot_sidecar import (
+    CompiledProfile,
+    Sidecar,
+    SidecarConfig,
+    _derive_sdk_session_id,
+    _SDK_SESSION_NAMESPACE,
+    _validate_history,
+    _HISTORY_MAX_ENTRIES,
+    _HISTORY_MAX_BYTES,
+)
 
 
 def event(event_type, **data):
@@ -13,6 +24,43 @@
     )
 
 
+# ---------------------------------------------------------------------------
+# Fake compiled-profile infrastructure
+# ---------------------------------------------------------------------------
+
+def _make_content(profile: str) -> str:
+    return f"You are the {profile} assistant. Common rules apply."
+
+
+_FAKE_PROFILES: dict = {
+    profile: {
+        "content": _make_content(profile),
+        "version": 1,
+        "hash": hashlib.sha256(_make_content(profile).encode()).hexdigest(),
+    }
+    for profile in ("public_visitor", "invited_friend", "june_admin")
+}
+
+
+def _fake_compile_fn(profile: str) -> dict:
+    if profile not in _FAKE_PROFILES:
+        raise ValueError(f"Unknown profile: {profile!r}")
+    return _FAKE_PROFILES[profile]
+
+
+def _fake_sdk_id(conversation_id: str, profile: str) -> str:
+    """Compute the deterministic SDK session ID for use in test assertions."""
+    p = _FAKE_PROFILES[profile]
+    compiled = CompiledProfile(
+        profile=profile,
+        content=p["content"],
+        prompt_version=1,
+        knowledge_version=p["version"],
+        hash=p["hash"],
+    )
+    return _derive_sdk_session_id(conversation_id, compiled)
+
+
 class FakeSession:
     def __init__(self, session_id, behavior="complete"):
         self.session_id = session_id
@@ -142,9 +190,9 @@
             base_directory="/not-used-by-fake",
         )
 
-    async def make_sidecar(self, behavior="complete"):
+    async def make_sidecar(self, behavior="complete", compile_fn=_fake_compile_fn):
         client = FakeClient(behavior)
-        sidecar = Sidecar(client, self.config, self.capture)
+        sidecar = Sidecar(client, self.config, self.capture, compile_fn=compile_fn)
         await sidecar.start()
         return sidecar, client
 
@@ -171,13 +219,17 @@
                     "request_id": request_id,
                     "conversation_id": "conversation-a",
                     "prompt": prompt,
+                    "prompt_profile": "public_visitor",
+                    "prompt_version": 1,
+                    "knowledge_version": 1,
                 }
             )
             await sidecar.drain_events()
 
         self.assertEqual(len(client.create_calls), 1)
         self.assertEqual(len(client.resume_calls), 1)
-        session = client.sessions["conversation-a"]
+        sdk_id = _fake_sdk_id("conversation-a", "public_visitor")
+        session = client.sessions[sdk_id]
         self.assertEqual(session.prompts, ["first", "second"])
         options = client.create_calls[0][1]
         self.assertEqual(options["provider"]["type"], "openai")
@@ -202,6 +254,9 @@
                     "request_id": "left-request",
                     "conversation_id": "left",
                     "prompt": "left",
+                    "prompt_profile": "public_visitor",
+                    "prompt_version": 1,
+                    "knowledge_version": 1,
                 }
             ),
             sidecar.dispatch(
@@ -210,6 +265,9 @@
                     "request_id": "right-request",
                     "conversation_id": "right",
                     "prompt": "right",
+                    "prompt_profile": "public_visitor",
+                    "prompt_version": 1,
+                    "knowledge_version": 1,
                 }
             ),
         )
@@ -233,6 +291,9 @@
                 "request_id": "turn-request",
                 "conversation_id": "abort-me",
                 "prompt": "wait",
+                "prompt_profile": "public_visitor",
+                "prompt_version": 1,
+                "knowledge_version": 1,
             }
         )
         await sidecar.dispatch(
@@ -244,7 +305,7 @@
         )
         await sidecar.drain_events()
 
-        self.assertEqual(client.sessions["abort-me"].abort_calls, 1)
+        self.assertEqual(client.sessions[_fake_sdk_id("abort-me", "public_visitor")].abort_calls, 1)
         done = [
             item
             for item in self.output
@@ -268,6 +329,9 @@
                 "request_id": "error-request",
                 "conversation_id": "error-conversation",
                 "prompt": "fail",
+                "prompt_profile": "public_visitor",
+                "prompt_version": 1,
+                "knowledge_version": 1,
             }
         )
         await sidecar.drain_events()
@@ -287,6 +351,9 @@
                 "request_id": "active-request",
                 "conversation_id": "abort-error",
                 "prompt": "wait",
+                "prompt_profile": "public_visitor",
+                "prompt_version": 1,
+                "knowledge_version": 1,
             }
         )
         await sidecar.dispatch(
@@ -314,9 +381,13 @@
                 "request_id": "active",
                 "conversation_id": "delete-me",
                 "prompt": "wait",
+                "prompt_profile": "public_visitor",
+                "prompt_version": 1,
+                "knowledge_version": 1,
             }
         )
-        session = client.sessions["delete-me"]
+        _delete_sdk_id = _fake_sdk_id("delete-me", "public_visitor")
+        session = client.sessions[_delete_sdk_id]
         await sidecar.dispatch(
             {
                 "command": "conversation.delete",
@@ -333,7 +404,15 @@
         )
 
         self.assertEqual(session.disconnect_calls, 1)
-        self.assertEqual(client.delete_calls, ["delete-me"])
+        self.assertEqual(client.delete_calls[0], _delete_sdk_id)
+        self.assertEqual(
+            set(client.delete_calls),
+            {
+                _fake_sdk_id("delete-me", "public_visitor"),
+                _fake_sdk_id("delete-me", "invited_friend"),
+                _fake_sdk_id("delete-me", "june_admin"),
+            },
+        )
         self.assertTrue(client.stopped)
         self.assertTrue(sidecar.shutting_down)
         shutdown = [
@@ -357,12 +436,15 @@
                     "request_id": f"request-{conversation_id}",
                     "conversation_id": conversation_id,
                     "prompt": conversation_id,
+                    "prompt_profile": "public_visitor",
+                    "prompt_version": 1,
+                    "knowledge_version": 1,
                 }
             )
             await sidecar.drain_events()
             await asyncio.sleep(0.01)
 
-        old_session = client.sessions["old"]
+        old_session = client.sessions[_fake_sdk_id("old", "public_visitor")]
         old_session.disconnect_error = True
         await sidecar.evict_idle_sessions()
         self.assertEqual(old_session.disconnect_calls, 1)
@@ -371,7 +453,7 @@
 
         sidecar._conversations["new"].last_used -= 4000
         await sidecar.evict_idle_sessions()
-        self.assertEqual(client.sessions["new"].disconnect_calls, 1)
+        self.assertEqual(client.sessions[_fake_sdk_id("new", "public_visitor")].disconnect_calls, 1)
         self.assertEqual(sidecar._conversations, {})
         await sidecar.dispatch(
             {
@@ -385,7 +467,7 @@
         client = FakeClient("pending")
         client.resume_started = asyncio.Event()
         client.resume_release = asyncio.Event()
-        sidecar = Sidecar(client, self.config, self.capture)
+        sidecar = Sidecar(client, self.config, self.capture, compile_fn=_fake_compile_fn)
         await sidecar.start()
 
         start_task = asyncio.create_task(
@@ -395,6 +477,9 @@
                     "request_id": "starting",
                     "conversation_id": "race",
                     "prompt": "wait",
+                    "prompt_profile": "public_visitor",
+                    "prompt_version": 1,
+                    "knowledge_version": 1,
                 }
             )
         )
@@ -414,13 +499,16 @@
         await asyncio.gather(start_task, shutdown_task)
 
         self.assertTrue(client.stopped)
-        self.assertEqual(client.sessions["race"].disconnect_calls, 1)
+        self.assertEqual(client.sessions[_fake_sdk_id("race", "public_visitor")].disconnect_calls, 1)
         await sidecar.dispatch(
             {
                 "command": "turn.start",
                 "request_id": "too-late",
                 "conversation_id": "late",
                 "prompt": "no",
+                "prompt_profile": "public_visitor",
+                "prompt_version": 1,
+                "knowledge_version": 1,
             }
         )
         late_error = [
@@ -442,6 +530,823 @@
             )
         self.assertEqual(sidecar._conversation_gates, {})
 
+    # ------------------------------------------------------------------
+    # New: profile-aware tests
+    # ------------------------------------------------------------------
+
+    async def test_three_distinct_profiles_produce_distinct_append_prompts(self):
+        sidecar, client = await self.make_sidecar()
+        for profile in ("public_visitor", "invited_friend", "june_admin"):
+            await sidecar.dispatch(
+                {
+                    "command": "turn.start",
+                    "request_id": f"req-{profile}",
+                    "conversation_id": f"conv-{profile}",
+                    "prompt": "hello",
+                    "prompt_profile": profile,
+                    "prompt_version": 1,
+                    "knowledge_version": 1,
+                }
+            )
+            await sidecar.drain_events()
+
+        contents = [opts["system_message"]["content"] for _, opts in client.create_calls]
+        self.assertEqual(len(contents), 3)
+        self.assertEqual(len(set(contents)), 3, "all three profiles must produce distinct content")
+        for _, opts in client.create_calls:
+            self.assertEqual(opts["system_message"]["mode"], "append")
+
+    async def test_session_options_memory_disabled_and_no_tools(self):
+        sidecar, client = await self.make_sidecar()
+        await sidecar.dispatch(
+            {
+                "command": "turn.start",
+                "request_id": "r-opts",
+                "conversation_id": "conv-opts",
+                "prompt": "test",
+                "prompt_profile": "public_visitor",
+                "prompt_version": 1,
+                "knowledge_version": 1,
+            }
+        )
+        await sidecar.drain_events()
+        _, opts = client.create_calls[0]
+        self.assertEqual(opts["memory"], {"enabled": False})
+        self.assertEqual(opts["tools"], [])
+        self.assertEqual(opts["available_tools"], [])
+        self.assertEqual(opts["mcp_servers"], {})
+        self.assertTrue(opts["enable_session_store"])
+
+    async def test_missing_profile_rejected_before_client_calls(self):
+        sidecar, client = await self.make_sidecar()
+        await sidecar.dispatch(
+            {
+                "command": "turn.start",
+                "request_id": "r-missing",
+                "conversation_id": "conv-missing",
+                "prompt": "hello",
+            }
+        )
+        errors = [e for e in self.output if e["type"] == "turn.error"]
+        self.assertEqual(errors[0]["error"]["code"], "invalid_prompt_profile")
+        done = [e for e in self.output if e["type"] == "turn.done"]
+        self.assertTrue(done[0]["failed"])
+        self.assertEqual(len(client.create_calls), 0)
+        self.assertEqual(len(client.resume_calls), 0)
+
+    async def test_unknown_profile_rejected_before_client_calls(self):
+        sidecar, client = await self.make_sidecar()
+        await sidecar.dispatch(
+            {
+                "command": "turn.start",
+                "request_id": "r-unknown",
+                "conversation_id": "conv-unknown",
+                "prompt": "hello",
+                "prompt_profile": "hacker",
+                "prompt_version": 1,
+                "knowledge_version": 1,
+            }
+        )
+        errors = [e for e in self.output if e["type"] == "turn.error"]
+        self.assertEqual(errors[0]["error"]["code"], "invalid_prompt_profile")
+        self.assertEqual(len(client.create_calls), 0)
+
+    async def test_stale_prompt_version_rejected_before_client_calls(self):
+        sidecar, client = await self.make_sidecar()
+        await sidecar.dispatch(
+            {
+                "command": "turn.start",
+                "request_id": "r-stale-pv",
+                "conversation_id": "conv-stale-pv",
+                "prompt": "hello",
+                "prompt_profile": "public_visitor",
+                "prompt_version": 999,
+                "knowledge_version": 1,
+            }
+        )
+        errors = [e for e in self.output if e["type"] == "turn.error"]
+        self.assertEqual(errors[0]["error"]["code"], "invalid_prompt_profile")
+        self.assertEqual(len(client.create_calls), 0)
+
+    async def test_stale_knowledge_version_rejected_before_client_calls(self):
+        sidecar, client = await self.make_sidecar()
+        await sidecar.dispatch(
+            {
+                "command": "turn.start",
+                "request_id": "r-stale-kv",
+                "conversation_id": "conv-stale-kv",
+                "prompt": "hello",
+                "prompt_profile": "public_visitor",
+                "prompt_version": 1,
+                "knowledge_version": 999,
+            }
+        )
+        errors = [e for e in self.output if e["type"] == "turn.error"]
+        self.assertEqual(errors[0]["error"]["code"], "invalid_prompt_profile")
+        self.assertEqual(len(client.create_calls), 0)
+
+    async def test_bool_prompt_version_rejected_before_client_calls(self):
+        sidecar, client = await self.make_sidecar()
+        await sidecar.dispatch(
+            {
+                "command": "turn.start",
+                "request_id": "r-bool-pv",
+                "conversation_id": "conv-bool-pv",
+                "prompt": "hello",
+                "prompt_profile": "public_visitor",
+                "prompt_version": True,
+                "knowledge_version": 1,
+            }
+        )
+        errors = [e for e in self.output if e["type"] == "turn.error"]
+        self.assertEqual(errors[0]["error"]["code"], "invalid_prompt_profile")
+        self.assertEqual(len(client.create_calls), 0)
+
+    async def test_bool_knowledge_version_rejected_before_client_calls(self):
+        sidecar, client = await self.make_sidecar()
+        await sidecar.dispatch(
+            {
+                "command": "turn.start",
+                "request_id": "r-bool-kv",
+                "conversation_id": "conv-bool-kv",
+                "prompt": "hello",
+                "prompt_profile": "public_visitor",
+                "prompt_version": 1,
+                "knowledge_version": True,
+            }
+        )
+        errors = [e for e in self.output if e["type"] == "turn.error"]
+        self.assertEqual(errors[0]["error"]["code"], "invalid_prompt_profile")
+        self.assertEqual(len(client.create_calls), 0)
+
+    async def test_same_profile_reuses_existing_session(self):
+        sidecar, client = await self.make_sidecar()
+        for i in range(3):
+            await sidecar.dispatch(
+                {
+                    "command": "turn.start",
+                    "request_id": f"r-reuse-{i}",
+                    "conversation_id": "conv-reuse",
+                    "prompt": f"message {i}",
+                    "prompt_profile": "public_visitor",
+                    "prompt_version": 1,
+                    "knowledge_version": 1,
+                }
+            )
+            await sidecar.drain_events()
+
+        self.assertEqual(len(client.create_calls), 1)
+        self.assertEqual(len(client.resume_calls), 1)
+        session = client.sessions[_fake_sdk_id("conv-reuse", "public_visitor")]
+        self.assertEqual(session.prompts, ["message 0", "message 1", "message 2"])
+
+    async def test_profile_switch_disconnects_and_resumes_with_new_prompt(self):
+        sidecar, client = await self.make_sidecar()
+
+        await sidecar.dispatch(
+            {
+                "command": "turn.start",
+                "request_id": "r-switch-1",
+                "conversation_id": "conv-switch",
+                "prompt": "first",
+                "prompt_profile": "public_visitor",
+                "prompt_version": 1,
+                "knowledge_version": 1,
+            }
+        )
+        await sidecar.drain_events()
+        visitor_sdk_id = _fake_sdk_id("conv-switch", "public_visitor")
+        friend_sdk_id = _fake_sdk_id("conv-switch", "invited_friend")
+        first_session = client.sessions[visitor_sdk_id]
+        self.assertEqual(len(client.create_calls), 1)
+
+        await sidecar.dispatch(
+            {
+                "command": "turn.start",
+                "request_id": "r-switch-2",
+                "conversation_id": "conv-switch",
+                "prompt": "second",
+                "prompt_profile": "invited_friend",
+                "prompt_version": 1,
+                "knowledge_version": 1,
+            }
+        )
+        await sidecar.drain_events()
+
+        # Old session is disconnected and permanently deleted before new one opens.
+        self.assertEqual(first_session.disconnect_calls, 1)
+        self.assertIn(visitor_sdk_id, client.delete_calls)
+
+        # The new session uses the invited_friend derived ID, not the visitor one.
+        resume_id, resume_opts = client.resume_calls[-1]
+        self.assertEqual(resume_id, friend_sdk_id)
+        self.assertNotEqual(friend_sdk_id, visitor_sdk_id)
+        self.assertEqual(resume_opts["system_message"]["mode"], "append")
+        expected_content = _FAKE_PROFILES["invited_friend"]["content"]
+        self.assertEqual(resume_opts["system_message"]["content"], expected_content)
+
+        # The newly opened session is a distinct object.
+        second_session = client.sessions[friend_sdk_id]
+        self.assertIsNot(second_session, first_session)
+
+        done = [e for e in self.output if e["type"] == "turn.done" and e["request_id"] == "r-switch-2"]
+        self.assertFalse(done[0].get("failed", False))
+
+    async def test_concurrent_conversations_profile_isolation(self):
+        sidecar, client = await self.make_sidecar()
+        await asyncio.gather(
+            sidecar.dispatch(
+                {
+                    "command": "turn.start",
+                    "request_id": "req-visitor",
+                    "conversation_id": "conv-visitor",
+                    "prompt": "hello visitor",
+                    "prompt_profile": "public_visitor",
+                    "prompt_version": 1,
+                    "knowledge_version": 1,
+                }
+            ),
+            sidecar.dispatch(
+                {
+                    "command": "turn.start",
+                    "request_id": "req-admin",
+                    "conversation_id": "conv-admin",
+                    "prompt": "hello admin",
+                    "prompt_profile": "june_admin",
+                    "prompt_version": 1,
+                    "knowledge_version": 1,
+                }
+            ),
+        )
+        await sidecar.drain_events()
+
+        visitor_sdk_id = _fake_sdk_id("conv-visitor", "public_visitor")
+        admin_sdk_id = _fake_sdk_id("conv-admin", "june_admin")
+        options_by_sdk_id = {sdk_id: opts for sdk_id, opts in client.create_calls}
+        visitor_content = options_by_sdk_id[visitor_sdk_id]["system_message"]["content"]
+        admin_content = options_by_sdk_id[admin_sdk_id]["system_message"]["content"]
+        self.assertNotEqual(visitor_content, admin_content)
+        self.assertEqual(visitor_content, _FAKE_PROFILES["public_visitor"]["content"])
+        self.assertEqual(admin_content, _FAKE_PROFILES["june_admin"]["content"])
+
+    async def test_profile_switch_while_active_is_rejected(self):
+        sidecar, client = await self.make_sidecar("pending")
+
+        await sidecar.dispatch(
+            {
+                "command": "turn.start",
+                "request_id": "active-req",
+                "conversation_id": "conv-active-switch",
+                "prompt": "wait",
+                "prompt_profile": "public_visitor",
+                "prompt_version": 1,
+                "knowledge_version": 1,
+            }
+        )
+
+        await sidecar.dispatch(
+            {
+                "command": "turn.start",
+                "request_id": "switch-req",
+                "conversation_id": "conv-active-switch",
+                "prompt": "switch",
+                "prompt_profile": "invited_friend",
+                "prompt_version": 1,
+                "knowledge_version": 1,
+            }
+        )
+
+        errors = [e for e in self.output if e["type"] == "turn.error" and e["request_id"] == "switch-req"]
+        self.assertTrue(len(errors) > 0)
+        done = [e for e in self.output if e["type"] == "turn.done" and e["request_id"] == "switch-req"]
+        self.assertTrue(done[0]["failed"])
+        # Active-turn switch must not trigger any cleanup at all.
+        sdk_id = _fake_sdk_id("conv-active-switch", "public_visitor")
+        self.assertEqual(client.sessions[sdk_id].disconnect_calls, 0)
+        self.assertEqual(client.delete_calls, [])
+
+    async def test_startup_compilation_failure_prevents_readiness(self):
+        call_count = {"n": 0}
+
+        def failing_compile_fn(profile: str) -> dict:
+            call_count["n"] += 1
+            raise ValueError(f"corrupted assets for {profile!r}")
+
+        client = FakeClient()
+        sidecar = Sidecar(client, self.config, self.capture, compile_fn=failing_compile_fn)
+        with self.assertRaises(ValueError) as ctx:
+            await sidecar.start()
+        self.assertIn("corrupted", str(ctx.exception))
+        self.assertFalse(client.started)
+        self.assertGreater(call_count["n"], 0)
+
+    # ------------------------------------------------------------------
+    # Derived session ID security tests
+    # ------------------------------------------------------------------
+
+    async def test_distinct_profiles_produce_distinct_sdk_session_ids(self):
+        """Different profiles on the same conversation must never share an SDK ID."""
+        ids = {
+            profile: _fake_sdk_id("conv-same", profile)
+            for profile in ("public_visitor", "invited_friend", "june_admin")
+        }
+        self.assertEqual(len(set(ids.values())), 3, "each profile needs a unique SDK ID")
+        for sid in ids.values():
+            uuid.UUID(sid)  # every value must be a valid UUID
+
+    async def test_same_profile_produces_same_sdk_id_across_restarts(self):
+        """The derived ID is deterministic: a sidecar restart resumes the same session."""
+        client = FakeClient()
+        sidecar1 = Sidecar(client, self.config, self.capture, compile_fn=_fake_compile_fn)
+        await sidecar1.start()
+        await sidecar1.dispatch({
+            "command": "turn.start",
+            "request_id": "restart-1",
+            "conversation_id": "conv-restart",
+            "prompt": "hello",
+            "prompt_profile": "public_visitor",
+            "prompt_version": 1,
+            "knowledge_version": 1,
+        })
+        await sidecar1.drain_events()
+
+        # Simulate sidecar restart: new Sidecar instance, same FakeClient (SDK store).
+        sidecar2 = Sidecar(client, self.config, self.capture, compile_fn=_fake_compile_fn)
+        await sidecar2.start()
+        await sidecar2.dispatch({
+            "command": "turn.start",
+            "request_id": "restart-2",
+            "conversation_id": "conv-restart",
+            "prompt": "still here",
+            "prompt_profile": "public_visitor",
+            "prompt_version": 1,
+            "knowledge_version": 1,
+        })
+        await sidecar2.drain_events()
+
+        # Sidecar1 tries resume (fails - no session yet) then creates.
+        # Sidecar2 tries resume (succeeds - session persists in SDK store).
+        # Therefore exactly one create, two resume attempts, both on the same derived ID.
+        self.assertEqual(len(client.create_calls), 1)
+        self.assertEqual(len(client.resume_calls), 2)
+        derived = _fake_sdk_id("conv-restart", "public_visitor")
+        self.assertEqual(client.create_calls[0][0], derived)
+        self.assertTrue(all(r == derived for r, _ in client.resume_calls))
+
+    async def test_profile_switch_permanently_deletes_old_sdk_session(self):
+        """Switching profile must delete the old derived SDK session before creating new."""
+        sidecar, client = await self.make_sidecar()
+        await sidecar.dispatch({
+            "command": "turn.start",
+            "request_id": "admin-turn",
+            "conversation_id": "conv-priv",
+            "prompt": "admin question",
+            "prompt_profile": "june_admin",
+            "prompt_version": 1,
+            "knowledge_version": 1,
+        })
+        await sidecar.drain_events()
+
+        admin_sdk_id = _fake_sdk_id("conv-priv", "june_admin")
+        visitor_sdk_id = _fake_sdk_id("conv-priv", "public_visitor")
+        self.assertNotEqual(admin_sdk_id, visitor_sdk_id)
+
+        await sidecar.dispatch({
+            "command": "turn.start",
+            "request_id": "visitor-turn",
+            "conversation_id": "conv-priv",
+            "prompt": "public question",
+            "prompt_profile": "public_visitor",
+            "prompt_version": 1,
+            "knowledge_version": 1,
+        })
+        await sidecar.drain_events()
+
+        # Admin session must be permanently deleted before visitor session opens.
+        self.assertIn(admin_sdk_id, client.delete_calls)
+        # Admin session must not be in the live sessions map.
+        self.assertNotIn(admin_sdk_id, client.sessions)
+        # Visitor session is a distinct object.
+        self.assertIn(visitor_sdk_id, client.sessions)
+
+    async def test_delete_conversation_uses_derived_sdk_id_not_conversation_id(self):
+        """conversation.delete must call delete_session with the derived UUID."""
+        sidecar, client = await self.make_sidecar()
+        await sidecar.dispatch({
+            "command": "turn.start",
+            "request_id": "setup-turn",
+            "conversation_id": "conv-del-check",
+            "prompt": "hi",
+            "prompt_profile": "june_admin",
+            "prompt_version": 1,
+            "knowledge_version": 1,
+        })
+        await sidecar.drain_events()
+
+        admin_sdk_id = _fake_sdk_id("conv-del-check", "june_admin")
+
+        await sidecar.dispatch({
+            "command": "conversation.delete",
+            "request_id": "del-req",
+            "conversation_id": "conv-del-check",
+        })
+
+        self.assertEqual(client.delete_calls[0], admin_sdk_id)
+        self.assertEqual(
+            set(client.delete_calls),
+            {
+                _fake_sdk_id("conv-del-check", "public_visitor"),
+                _fake_sdk_id("conv-del-check", "invited_friend"),
+                _fake_sdk_id("conv-del-check", "june_admin"),
+            },
+        )
+        self.assertNotIn("conv-del-check", client.delete_calls)
+
+    async def test_delete_nonexistent_conversation_cleans_known_profile_ids(self):
+        """An uncached delete purges every currently known derived session ID."""
+        sidecar, client = await self.make_sidecar()
+        await sidecar.dispatch({
+            "command": "conversation.delete",
+            "request_id": "del-ghost",
+            "conversation_id": "ghost-conv",
+        })
+
+        self.assertEqual(
+            set(client.delete_calls),
+            {
+                _fake_sdk_id("ghost-conv", "public_visitor"),
+                _fake_sdk_id("ghost-conv", "invited_friend"),
+                _fake_sdk_id("ghost-conv", "june_admin"),
+            },
+        )
+        self.assertNotIn("ghost-conv", client.delete_calls)
+        done = [e for e in self.output if e["type"] == "turn.done" and e["request_id"] == "del-ghost"]
+        self.assertEqual(done[0]["action"], "conversation.delete")
+
+    async def test_admin_session_not_reachable_via_visitor_derived_id(self):
+        """A visitor SDK ID must differ from the admin one for the same conversation."""
+        admin_id = _fake_sdk_id("shared-conv", "june_admin")
+        visitor_id = _fake_sdk_id("shared-conv", "public_visitor")
+        self.assertNotEqual(admin_id, visitor_id)
+
+        sidecar, client = await self.make_sidecar()
+        # Establish an admin session.
+        await sidecar.dispatch({
+            "command": "turn.start",
+            "request_id": "admin-req",
+            "conversation_id": "shared-conv",
+            "prompt": "secret",
+            "prompt_profile": "june_admin",
+            "prompt_version": 1,
+            "knowledge_version": 1,
+        })
+        await sidecar.drain_events()
+
+        # The admin session object must NOT be accessible under the visitor-derived ID.
+        admin_session = client.sessions.get(admin_id)
+        self.assertIsNotNone(admin_session)
+        self.assertIsNone(client.sessions.get(visitor_id),
+                          "visitor SDK ID must not map to any session object at this point")
+
+
+# ---------------------------------------------------------------------------
+# _validate_history unit tests
+# ---------------------------------------------------------------------------
+
+class ValidateHistoryTest(unittest.TestCase):
+    def test_none_returns_empty_list(self):
+        self.assertEqual(_validate_history(None), [])
+
+    def test_empty_list_accepted(self):
+        self.assertEqual(_validate_history([]), [])
+
+    def test_valid_two_entries(self):
+        hist = [
+            {"role": "user", "content": "hello"},
+            {"role": "assistant", "content": "hi"},
+        ]
+        result = _validate_history(hist)
+        self.assertEqual(result, hist)
+
+    def test_not_a_list_raises(self):
+        for bad in (42, "string", True, False, {}, object()):
+            with self.assertRaises(ValueError, msg=f"should reject {bad!r}"):
+                _validate_history(bad)
+
+    def test_too_many_entries_raises(self):
+        entries = [{"role": "user", "content": "x"}] * (_HISTORY_MAX_ENTRIES + 1)
+        with self.assertRaises(ValueError):
+            _validate_history(entries)
+
+    def test_exactly_max_entries_accepted(self):
+        entries = [
+            {"role": "user" if i % 2 == 0 else "assistant", "content": "x"}
+            for i in range(_HISTORY_MAX_ENTRIES)
+        ]
+        result = _validate_history(entries)
+        self.assertEqual(len(result), _HISTORY_MAX_ENTRIES)
+
+    def test_non_object_entry_raises(self):
+        for bad_entry in (42, "string", True, None, []):
+            with self.assertRaises(ValueError):
+                _validate_history([bad_entry])
+
+    def test_invalid_role_raises(self):
+        for bad_role in ("system", "SYSTEM", "User", "ASSISTANT", "", " user"):
+            with self.assertRaises(ValueError, msg=f"role {bad_role!r} must be rejected"):
+                _validate_history([{"role": bad_role, "content": "x"}])
+
+    def test_bool_role_raises(self):
+        with self.assertRaises(ValueError):
+            _validate_history([{"role": True, "content": "x"}])
+
+    def test_none_content_raises(self):
+        with self.assertRaises(ValueError):
+            _validate_history([{"role": "user", "content": None}])
+
+    def test_bool_content_raises(self):
+        with self.assertRaises(ValueError):
+            _validate_history([{"role": "user", "content": True}])
+
+    def test_int_content_raises(self):
+        with self.assertRaises(ValueError):
+            _validate_history([{"role": "user", "content": 42}])
+
+    def test_extra_key_raises(self):
+        with self.assertRaises(ValueError):
+            _validate_history([{"role": "user", "content": "hi", "injected": "bad"}])
+
+    def test_oversized_total_raises(self):
+        # One entry with content just over the byte limit.
+        big = "x" * (_HISTORY_MAX_BYTES + 1)
+        with self.assertRaises(ValueError):
+            _validate_history([{"role": "user", "content": big}])
+
+    def test_total_at_limit_accepted(self):
+        # Two entries whose combined bytes sit at or under the limit.
+        chunk_size = _HISTORY_MAX_BYTES // 2 - 10  # under limit
+        entries = [
+            {"role": "user", "content": "a" * chunk_size},
+            {"role": "assistant", "content": "b" * chunk_size},
+        ]
+        result = _validate_history(entries)
+        self.assertEqual(len(result), 2)
+
+    def test_special_characters_accepted(self):
+        entry = {"role": "user", "content": "hello \"world\" \\ \n"}
+        result = _validate_history([entry])
+        self.assertEqual(result[0]["content"], entry["content"])
+
+    def test_empty_content_string_accepted(self):
+        result = _validate_history([{"role": "user", "content": ""}])
+        self.assertEqual(result[0]["content"], "")
+
+
+# ---------------------------------------------------------------------------
+# Sidecar history integration tests
+# ---------------------------------------------------------------------------
+
+class SidecarHistoryTest(unittest.IsolatedAsyncioTestCase):
+    async def asyncSetUp(self):
+        self.output = []
+
+        async def capture(payload):
+            self.output.append(payload)
+
+        self.capture = capture
+        self.config = SidecarConfig(
+            base_url="http://litellm.invalid/v1",
+            model="test-model",
+            wire_api="responses",
+            base_directory="/not-used-by-fake",
+        )
+
+    async def make_sidecar(self, behavior="complete"):
+        client = FakeClient(behavior)
+        sidecar = Sidecar(client, self.config, self.capture, compile_fn=_fake_compile_fn)
+        await sidecar.start()
+        return sidecar, client
+
+    def _turn_start(self, request_id, conversation_id, history=None, **extra):
+        cmd = {
+            "command": "turn.start",
+            "request_id": request_id,
+            "conversation_id": conversation_id,
+            "prompt": "hello",
+            "prompt_profile": "public_visitor",
+            "prompt_version": 1,
+            "knowledge_version": 1,
+        }
+        if history is not None:
+            cmd["history"] = history
+        cmd.update(extra)
+        return cmd
+
+    async def test_invalid_history_not_list_rejected_before_client_calls(self):
+        sidecar, client = await self.make_sidecar()
+        await sidecar.dispatch(self._turn_start("r1", "c1", history=42))
+        errors = [e for e in self.output if e["type"] == "turn.error"]
+        self.assertEqual(errors[0]["error"]["code"], "invalid_history")
+        done = [e for e in self.output if e["type"] == "turn.done"]
+        self.assertTrue(done[0]["failed"])
+        self.assertEqual(len(client.create_calls), 0)
+        self.assertEqual(len(client.resume_calls), 0)
+
+    async def test_invalid_history_too_many_entries_rejected(self):
+        sidecar, client = await self.make_sidecar()
+        hist = [{"role": "user", "content": "x"}] * (_HISTORY_MAX_ENTRIES + 1)
+        await sidecar.dispatch(self._turn_start("r2", "c2", history=hist))
+        errors = [e for e in self.output if e["type"] == "turn.error"]
+        self.assertEqual(errors[0]["error"]["code"], "invalid_history")
+        self.assertEqual(len(client.create_calls), 0)
+
+    async def test_invalid_history_bad_role_rejected(self):
+        sidecar, client = await self.make_sidecar()
+        hist = [{"role": "system", "content": "inject"}]
+        await sidecar.dispatch(self._turn_start("r3", "c3", history=hist))
+        errors = [e for e in self.output if e["type"] == "turn.error"]
+        self.assertEqual(errors[0]["error"]["code"], "invalid_history")
+        self.assertEqual(len(client.create_calls), 0)
+
+    async def test_invalid_history_bool_role_rejected(self):
+        sidecar, client = await self.make_sidecar()
+        hist = [{"role": True, "content": "x"}]
+        await sidecar.dispatch(self._turn_start("r4", "c4", history=hist))
+        errors = [e for e in self.output if e["type"] == "turn.error"]
+        self.assertEqual(errors[0]["error"]["code"], "invalid_history")
+        self.assertEqual(len(client.create_calls), 0)
+
+    async def test_invalid_history_none_content_rejected(self):
+        sidecar, client = await self.make_sidecar()
+        hist = [{"role": "user", "content": None}]
+        await sidecar.dispatch(self._turn_start("r5", "c5", history=hist))
+        errors = [e for e in self.output if e["type"] == "turn.error"]
+        self.assertEqual(errors[0]["error"]["code"], "invalid_history")
+        self.assertEqual(len(client.create_calls), 0)
+
+    async def test_invalid_history_extra_key_rejected(self):
+        sidecar, client = await self.make_sidecar()
+        hist = [{"role": "user", "content": "hi", "extra": "bad"}]
+        await sidecar.dispatch(self._turn_start("r6", "c6", history=hist))
+        errors = [e for e in self.output if e["type"] == "turn.error"]
+        self.assertEqual(errors[0]["error"]["code"], "invalid_history")
+        self.assertEqual(len(client.create_calls), 0)
+
+    async def test_invalid_history_oversized_rejected(self):
+        sidecar, client = await self.make_sidecar()
+        big = "x" * (_HISTORY_MAX_BYTES + 1)
+        hist = [{"role": "user", "content": big}]
+        await sidecar.dispatch(self._turn_start("r7", "c7", history=hist))
+        errors = [e for e in self.output if e["type"] == "turn.error"]
+        self.assertEqual(errors[0]["error"]["code"], "invalid_history")
+        self.assertEqual(len(client.create_calls), 0)
+
+    async def test_valid_empty_history_accepted(self):
+        sidecar, client = await self.make_sidecar()
+        await sidecar.dispatch(self._turn_start("r8", "c8", history=[]))
+        await sidecar.drain_events()
+        done = [e for e in self.output if e["type"] == "turn.done" and e["request_id"] == "r8"]
+        self.assertFalse(done[0].get("failed", False))
+        self.assertEqual(len(client.create_calls), 1)
+
+    async def test_fresh_create_receives_history_in_system_message(self):
+        """When resume_session fails (no persisted session), create_session must
+        include the PRIOR OWNED CONVERSATION TRANSCRIPT in system_message."""
+        sidecar, client = await self.make_sidecar()
+        hist = [
+            {"role": "user", "content": "prior question"},
+            {"role": "assistant", "content": "prior answer"},
+        ]
+        await sidecar.dispatch(self._turn_start("r-create", "c-create", history=hist))
+        await sidecar.drain_events()
+
+        self.assertEqual(len(client.create_calls), 1)
+        _, create_opts = client.create_calls[0]
+        sys_content = create_opts["system_message"]["content"]
+        self.assertIn("PRIOR OWNED CONVERSATION TRANSCRIPT", sys_content)
+        self.assertIn("prior question", sys_content)
+        self.assertIn("prior answer", sys_content)
+        self.assertIn("untrusted", sys_content.lower())
+        self.assertEqual(create_opts["system_message"]["mode"], "append")
+
+    async def test_resume_does_not_inject_history(self):
+        """An existing persisted session must not receive history in system_message."""
+        sidecar, client = await self.make_sidecar()
+        hist = [{"role": "user", "content": "prior"}]
+
+        # First turn: creates the session.
+        await sidecar.dispatch(self._turn_start("r-resume-1", "c-resume", history=hist))
+        await sidecar.drain_events()
+        self.assertEqual(len(client.create_calls), 1)
+        first_content = client.create_calls[0][1]["system_message"]["content"]
+
+        # Second turn on same conversation: must resume (SDK session persists).
+        await sidecar.dispatch(self._turn_start("r-resume-2", "c-resume", history=hist))
+        await sidecar.drain_events()
+        self.assertEqual(len(client.resume_calls), 1)
+        # resume_session does not receive options from create_session call.
+        resume_opts = client.resume_calls[0][1]
+        resume_sys = resume_opts["system_message"]["content"]
+        # The resume options (base options) must not contain the transcript block.
+        self.assertNotIn("PRIOR OWNED CONVERSATION TRANSCRIPT", resume_sys)
+        # The create call had the transcript; verify only one create happened.
+        self.assertEqual(len(client.create_calls), 1)
+
+    async def test_history_with_no_field_uses_base_system_message_on_create(self):
+        """Absent history field: create_session uses base system_message without transcript."""
+        sidecar, client = await self.make_sidecar()
+        await sidecar.dispatch(self._turn_start("r-nofield", "c-nofield"))
+        await sidecar.drain_events()
+        self.assertEqual(len(client.create_calls), 1)
+        _, opts = client.create_calls[0]
+        self.assertNotIn("PRIOR OWNED CONVERSATION TRANSCRIPT", opts["system_message"]["content"])
+
+    async def test_profile_switch_create_fallback_gets_history(self):
+        """After a profile switch the new derived session is a fresh create;
+        the history must be injected into the new session's system_message only."""
+        sidecar, client = await self.make_sidecar()
+
+        # First turn: public_visitor session created; no prior history yet.
+        await sidecar.dispatch(self._turn_start("r-sw-1", "c-switch"))
+        await sidecar.drain_events()
+        visitor_sdk_id = _fake_sdk_id("c-switch", "public_visitor")
+        friend_sdk_id = _fake_sdk_id("c-switch", "invited_friend")
+        self.assertNotEqual(visitor_sdk_id, friend_sdk_id)
+        first_create_content = client.create_calls[0][1]["system_message"]["content"]
+        # No transcript on initial create (no history provided).
+        self.assertNotIn("PRIOR OWNED CONVERSATION TRANSCRIPT", first_create_content)
+
+        # Second turn: invited_friend profile — old session deleted, new fresh create.
+        # Now we send history representing the prior visitor exchange.
+        friend_hist = [
+            {"role": "user", "content": "visitor msg"},
+            {"role": "assistant", "content": "answer"},
+        ]
+        await sidecar.dispatch({
+            "command": "turn.start",
+            "request_id": "r-sw-2",
+            "conversation_id": "c-switch",
+            "prompt": "switch question",
+            "prompt_profile": "invited_friend",
+            "prompt_version": 1,
+            "knowledge_version": 1,
+            "history": friend_hist,
+        })
+        await sidecar.drain_events()
+
+        # Old visitor session must be deleted.
+        self.assertIn(visitor_sdk_id, client.delete_calls)
+        # New session created under invited_friend derived ID.
+        create_ids = [sid for sid, _ in client.create_calls]
+        self.assertIn(friend_sdk_id, create_ids)
+
+        # Second create must include the history transcript.
+        second_create_opts = dict(client.create_calls)[friend_sdk_id]
+        second_content = second_create_opts["system_message"]["content"]
+        self.assertIn("PRIOR OWNED CONVERSATION TRANSCRIPT", second_content)
+        self.assertIn("visitor msg", second_content)
+        self.assertIn("answer", second_content)
+        self.assertIn("untrusted", second_content.lower())
+
+        done = [e for e in self.output if e["type"] == "turn.done" and e["request_id"] == "r-sw-2"]
+        self.assertFalse(done[0].get("failed", False))
+
+    async def test_history_not_exposed_in_events(self):
+        """History must not appear in any turn.accepted, assistant.delta, or
+        turn.done events emitted to the client."""
+        sidecar, client = await self.make_sidecar()
+        sensitive = "SENSITIVE_TRANSCRIPT_DATA_XYZ"
+        hist = [{"role": "user", "content": sensitive}]
+        await sidecar.dispatch(self._turn_start("r-safe", "c-safe", history=hist))
+        await sidecar.drain_events()
+
+        for ev in self.output:
+            for field in ("delta", "content", "prompt"):
+                val = ev.get(field, "")
+                if isinstance(val, str):
+                    self.assertNotIn(sensitive, val,
+                                     f"history leaked into event[{field}]: {ev}")
+
+    async def test_existing_conversation_history_compatibility(self):
+        """Conversations without history (pre-change) work normally: no
+        transcript block is injected when history is absent/empty."""
+        sidecar, client = await self.make_sidecar()
+        # Simulate an old-style command with no history key.
+        await sidecar.dispatch({
+            "command": "turn.start",
+            "request_id": "r-compat",
+            "conversation_id": "c-compat",
+            "prompt": "legacy",
+            "prompt_profile": "june_admin",
+            "prompt_version": 1,
+            "knowledge_version": 1,
+        })
+        await sidecar.drain_events()
+        done = [e for e in self.output if e["type"] == "turn.done" and e["request_id"] == "r-compat"]
+        self.assertFalse(done[0].get("failed", False))
+        _, opts = client.create_calls[0]
+        self.assertNotIn("PRIOR OWNED CONVERSATION TRANSCRIPT", opts["system_message"]["content"])
+
 
 if __name__ == "__main__":
     unittest.main()