{"repo":"NousResearch/hermes-agent","public_id":"3341","display_label":"Stabilize LLM prompt prefix for cross-session cache hits","summary":"These issues and PRs address a common theme: making system prompts byte-stable so that prefix caching works reliably across sessions and turns. Work includes separating volatile runtime metadata from stable prompt blocks, fixing timestamp and session-context placement, and adding cache breakpoints in the right locations.","subsystem":"agent","counts":{"members":19,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":5,"merged_prs":2,"attempt_prs":12},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":27675},"resolution":{"state":"partially_resolved","by":"pr-70990"},"freshness":{"state":"verified","last_verified_at":"2026-07-24T23:01:38+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.0902923976608187},"fix_edges":[{"issue_number":8687,"pr_number":27675,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":68191,"pr_number":70990,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":3341,"title":"[superseded] perf(ttft): move runtime metadata out of the cached system prompt","url":null,"state":"closed"},{"kind":"issue","number":3353,"title":"perf(ttft): move runtime metadata out of the cached system prompt","url":null,"state":"closed"},{"kind":"issue","number":8687,"title":"perf: system prompt timestamp changes after compression, invalidating prefix cache on local LLM backends","url":null,"state":"closed"},{"kind":"issue","number":15866,"title":"Question: does the minute-precision timestamp in `_build_system_prompt` invalidate prompt caching for upstream inference servers?","url":null,"state":"closed"},{"kind":"issue","number":68191,"title":"Cross-session prompt cache misses: per-session bytes sit ahead of the only system breakpoint","url":null,"state":"closed"},{"kind":"pr","number":3345,"title":"perf(ttft): reduce pre-request startup overhead","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#3361","number":3361},{"kind":"pr","value":"#3348","number":3348},{"kind":"pr","value":"#3362","number":3362},{"kind":"pr","value":"#3349","number":3349},{"kind":"pr","value":"#3363","number":3363},{"kind":"pr","value":"#3350","number":3350},{"kind":"pr","value":"#3364","number":3364},{"kind":"pr","value":"#3351","number":3351},{"kind":"pr","value":"#3365","number":3365},{"kind":"pr","value":"#3353","number":3353},{"kind":"pr","value":"#3366","number":3366},{"kind":"pr","value":"#3352","number":3352},{"kind":"pr","value":"#3354","number":3354},{"kind":"pr","value":"#3367","number":3367},{"kind":"pr","value":"#3355","number":3355}],"category":"superseded_by_pr","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":3365,"title":"perf(ttft): move runtime metadata out of cached prompt","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#3395","number":3395},{"kind":"pr","value":"#3421","number":3421}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":8689,"title":"perf: stabilize system prompt timestamp across compression cycles","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#27675","number":27675}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":27675,"title":"perf(prompt-cache): date-only timestamp + loud gateway-DB roundtrip logging","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/conversation_loop.py","agent/system_prompt.py","tests/agent/test_system_prompt_restore.py","tests/run_agent/test_run_agent.py"],"fix_hunks":[{"file":"agent/conversation_loop.py","hunks":["@@ -82,6 +82,108 @@ def _ra():\n     return run_agent\n \n \n+def _restore_or_build_system_prompt(agent, system_message, conversation_history):\n+    \"\"\"Restore the cached system prompt from the session DB or build it fresh.\n+\n+    Mutates ``agent._cached_system_prompt`` and persists a freshly-built\n+    prompt back to the session DB on first build.  Extracted from\n+    ``run_conversation`` so the prefix-cache restore path can be tested in\n+    isolation.\n+\n+    Three-way state distinction for the stored row, surfaced via logs so\n+    silent prefix-cache misses are visible in ``agent.log``:\n+\n+      * ``missing`` — no session row yet (legitimate first turn).\n+      * ``null``   — row exists, ``system_prompt`` column is NULL.\n+        Legacy session predating system-prompt persistence, or a migration\n+        leftover.  Warns when ``conversation_history`` is non-empty.\n+      * ``empty``  — row exists, ``system_prompt`` column is the empty\n+        string.  Indicates a previous-turn write that ran but stored\n+        nothing (silent persistence bug).  Always warns.\n+      * ``present`` — row exists with a usable prompt → reused verbatim.\n+\n+    Read or write failures against the session DB log at WARNING (not\n+    DEBUG) so persistent issues (disk full, schema drift, lock contention)\n+    surface without needing verbose mode.  This used to be a debug-level\n+    log that silently broke prefix-cache reuse on the gateway path\n+    (which constructs a fresh ``AIAgent`` per turn and depends on this\n+    DB roundtrip).\n+    \"\"\"\n+    stored_prompt = None\n+    stored_state = \"missing\"\n+    if conversation_history and agent._session_db:\n+        try:\n+            session_row = agent._session_db.get_session(agent.session_id)\n+            if session_row is not None:\n+                raw_prompt = session_row.get(\"system_prompt\")\n+                if raw_prompt is None:\n+                    stored_state = \"null\"\n+                elif raw_prompt == \"\":\n+                    stored_state = \"empty\"\n+                else:\n+                    stored_prompt = raw_prompt\n+                    stored_state = \"present\"\n+        except Exception as exc:\n+            logger.warning(\n+                \"Session DB get_session failed for system-prompt restore \"\n+                \"(session=%s): %s. Falling back to fresh build — prefix \"\n+                \"cache will miss for this turn.\",\n+                agent.session_id, exc,\n+            )\n+\n+    if stored_prompt:\n+        # Continuing session — reuse the exact system prompt from the\n+        # previous turn so the Anthropic cache prefix matches.\n+        agent._cached_system_prompt = stored_prompt\n+        return\n+\n+    if conversation_history and stored_state in (\"null\", \"empty\"):\n+        # Continuing session whose stored prompt is unusable.  The\n+        # previous turn's write either never happened or wrote an empty\n+        # string — either way every turn now rebuilds and the prefix\n+        # cache misses every time.\n+        logger.warning(\n+            \"Stored system prompt for session %s is %s; rebuilding \"\n+            \"from scratch this turn. Prefix cache will miss until \"\n+            \"the rebuild persists. Investigate the previous turn's \"\n+            \"update_system_prompt write path.\",\n+            agent.session_id, stored_state,\n+        )\n+\n+    # First turn of a new session (or recovering from a broken stored\n+    # prompt) — build from scratch.\n+    agent._cached_system_prompt = agent._build_system_prompt(system_message)\n+\n+    # Plugin hook: on_session_start — fired once when a brand-new\n+    # session is created (not on continuation).  Plugins can use this\n+    # to initialise session-scoped state (e.g. warm a memory cache).\n+    try:\n+        from hermes_cli.plugins import invoke_hook as _invoke_hook\n+        _invoke_hook(\n+            \"on_session_start\",\n+            session_id=agent.session_id,\n+            model=agent.model,\n+            platform=getattr(agent, \"platform\", None) or \"\",\n+        )\n+    except Exception as exc:\n+        logger.warning(\"on_session_start hook failed: %s\", exc)\n+\n+    # Persist the system prompt snapshot in SQLite.  Failure here used\n+    # to log at DEBUG, which silently broke prefix-cache reuse on the\n+    # gateway path (fresh AIAgent per turn → reads from this row every\n+    # subsequent turn).\n+    if agent._session_db:\n+        try:\n+            agent._session_db.update_system_prompt(agent.session_id, agent._cached_system_prompt)\n+        except Exception as exc:\n+            logger.warning(\n+                \"Session DB update_system_prompt failed for session %s: \"\n+                \"%s. Subsequent turns will rebuild the system prompt and \"\n+                \"miss the prefix cache.\",\n+                agent.session_id, exc,\n+            )\n+\n+\n def run_conversation(\n     agent,\n     user_message: str,","@@ -313,43 +415,7 @@ def run_conversation(\n     # producing a different system prompt and breaking the Anthropic\n     # prefix cache.\n     if agent._cached_system_prompt is None:\n-        stored_prompt = None\n-        if conversation_history and agent._session_db:\n-            try:\n-                session_row = agent._session_db.get_session(agent.session_id)\n-                if session_row:\n-                    stored_prompt = session_row.get(\"system_prompt\") or None\n-            except Exception:\n-                pass  # Fall through to build fresh\n-\n-        if stored_prompt:\n-            # Continuing session — reuse the exact system prompt from\n-            # the previous turn so the Anthropic cache prefix matches.\n-            agent._cached_system_prompt = stored_prompt\n-        else:\n-            # First turn of a new session — build from scratch.\n-            agent._cached_system_prompt = agent._build_system_prompt(system_message)\n-            # Plugin hook: on_session_start\n-            # Fired once when a brand-new session is created (not on\n-            # continuation).  Plugins can use this to initialise\n-            # session-scoped state (e.g. warm a memory cache).\n-            try:\n-                from hermes_cli.plugins import invoke_hook as _invoke_hook\n-                _invoke_hook(\n-                    \"on_session_start\",\n-                    session_id=agent.session_id,\n-                    model=agent.model,\n-                    platform=getattr(agent, \"platform\", None) or \"\",\n-                )\n-            except Exception as exc:\n-                logger.warning(\"on_session_start hook failed: %s\", exc)\n-\n-            # Store the system prompt snapshot in SQLite\n-            if agent._session_db:\n-                try:\n-                    agent._session_db.update_system_prompt(agent.session_id, agent._cached_system_prompt)\n-                except Exception as e:\n-                    logger.debug(\"Session DB update_system_prompt failed: %s\", e)\n+        _restore_or_build_system_prompt(agent, system_message, conversation_history)\n \n     active_system_prompt = agent._cached_system_prompt\n "]},{"file":"agent/system_prompt.py","hunks":["@@ -255,7 +255,13 @@ def build_system_prompt_parts(agent: Any, system_message: Optional[str] = None)\n \n     from hermes_time import now as _hermes_now\n     now = _hermes_now()\n-    timestamp_line = f\"Conversation started: {now.strftime('%A, %B %d, %Y %I:%M %p')}\"\n+    # Date-only (not minute-precision) so the system prompt is byte-stable\n+    # for the full day.  Minute-precision changes invalidate prefix-cache KV\n+    # on every rebuild path (compression boundary, fresh-agent gateway turns,\n+    # session resume without a stored prompt).  The model can still query the\n+    # exact wall-clock time via tools when it actually needs it.\n+    # Credit: @iamfoz (PR #20451).\n+    timestamp_line = f\"Conversation started: {now.strftime('%A, %B %d, %Y')}\"\n     if agent.pass_session_id and agent.session_id:\n         timestamp_line += f\"\\nSession ID: {agent.session_id}\"\n     if agent.model:"]},{"file":"tests/agent/test_system_prompt_restore.py","hunks":["@@ -0,0 +1,223 @@\n+\"\"\"Tests for ``agent.conversation_loop._restore_or_build_system_prompt``.\n+\n+Validates the gateway DB-roundtrip path that keeps the system prompt\n+byte-stable across turns (fresh AIAgent → must restore from session DB\n+instead of rebuilding).  Covers:\n+\n+  * Successful restore from a stored prompt (present row).\n+  * Legitimate first-turn build (no history).\n+  * Silent-failure recovery paths:\n+      - DB read raises → WARNING + fresh build\n+      - Row has system_prompt=NULL → WARNING + fresh build\n+      - Row has system_prompt=\"\" → WARNING + fresh build\n+      - DB write fails → WARNING (subsequent turns will miss cache)\n+\"\"\"\n+\n+from __future__ import annotations\n+\n+import logging\n+from unittest.mock import MagicMock\n+\n+import pytest\n+\n+from agent.conversation_loop import _restore_or_build_system_prompt\n+\n+\n+def _make_agent(session_db=None, prebuilt_prompt: str = \"BUILT_PROMPT\"):\n+    \"\"\"Construct the minimal agent fake the helper needs.\"\"\"\n+    agent = MagicMock()\n+    agent._cached_system_prompt = None\n+    agent.session_id = \"test-session-id\"\n+    agent.model = \"test-model\"\n+    agent.platform = \"cli\"\n+    agent._session_db = session_db\n+    agent._build_system_prompt = MagicMock(return_value=prebuilt_prompt)\n+    return agent\n+\n+\n+# ---------------------------------------------------------------------------\n+# Happy paths\n+# ---------------------------------------------------------------------------\n+\n+\n+class TestStoredPromptReuse:\n+    def test_present_row_is_reused_verbatim(self, caplog):\n+        \"\"\"Continuing session with a stored prompt → reuse byte-for-byte.\"\"\"\n+        stored = \"Stored prompt from turn 1 — byte-identical reuse\"\n+        db = MagicMock()\n+        db.get_session.return_value = {\"system_prompt\": stored}\n+        agent = _make_agent(session_db=db)\n+\n+        with caplog.at_level(logging.WARNING, logger=\"agent.conversation_loop\"):\n+            _restore_or_build_system_prompt(agent, None, [{\"role\": \"user\", \"content\": \"hi\"}])\n+\n+        assert agent._cached_system_prompt == stored\n+        agent._build_system_prompt.assert_not_called()\n+        db.update_system_prompt.assert_not_called()\n+        # No warnings on the happy path\n+        assert not [r for r in caplog.records if r.levelno >= logging.WARNING]\n+\n+    def test_present_row_with_unicode_preserved(self):\n+        \"\"\"Non-ASCII bytes in the stored prompt are not mangled.\"\"\"\n+        stored = \"Stored prompt with unicode: ☤ ⚗ ◆ — and emoji 🦊\"\n+        db = MagicMock()\n+        db.get_session.return_value = {\"system_prompt\": stored}\n+        agent = _make_agent(session_db=db)\n+\n+        _restore_or_build_system_prompt(agent, None, [{\"role\": \"user\", \"content\": \"hi\"}])\n+        assert agent._cached_system_prompt == stored\n+\n+\n+# ---------------------------------------------------------------------------\n+# Legitimate fresh-build paths (no history, no DB)\n+# ---------------------------------------------------------------------------\n+\n+\n+class TestLegitimateFreshBuild:\n+    def test_no_history_skips_db_and_builds_fresh(self, caplog):\n+        \"\"\"First turn with empty history → build fresh, don't touch the DB.\"\"\"\n+        db = MagicMock()\n+        agent = _make_agent(session_db=db)\n+\n+        with caplog.at_level(logging.WARNING, logger=\"agent.conversation_loop\"):\n+            _restore_or_build_system_prompt(agent, None, [])\n+\n+        # No history → DB read skipped entirely\n+        db.get_session.assert_not_called()\n+        agent._build_system_prompt.assert_called_once_with(None)\n+        assert agent._cached_system_prompt == \"BUILT_PROMPT\"\n+        # Persisted to DB\n+        db.update_system_prompt.assert_called_once_with(agent.session_id, \"BUILT_PROMPT\")\n+        assert not [r for r in caplog.records if r.levelno >= logging.WARNING]\n+\n+    def test_no_db_skips_persistence(self):\n+        \"\"\"When session DB is None, build and skip persistence silently.\"\"\"\n+        agent = _make_agent(session_db=None)\n+        _restore_or_build_system_prompt(agent, None, [])\n+        agent._build_system_prompt.assert_called_once()\n+        assert agent._cached_system_prompt == \"BUILT_PROMPT\"\n+\n+\n+# ---------------------------------------------------------------------------\n+# Silent-failure recovery — these are the new A/B logging paths\n+# ---------------------------------------------------------------------------\n+\n+\n+class TestSilentFailureWarnings:\n+    def test_db_read_exception_warns_and_rebuilds(self, caplog):\n+        \"\"\"DB read raising → WARNING + fall through to fresh build.\"\"\"\n+        db = MagicMock()\n+        db.get_session.side_effect = RuntimeError(\"disk full\")\n+        agent = _make_agent(session_db=db)\n+\n+        with caplog.at_level(logging.WARNING, logger=\"agent.conversation_loop\"):\n+            _restore_or_build_system_prompt(agent, None, [{\"role\": \"user\", \"content\": \"hi\"}])\n+\n+        # Built fresh\n+        agent._build_system_prompt.assert_called_once()\n+        assert agent._cached_system_prompt == \"BUILT_PROMPT\"\n+        # Loud warning about the read failure\n+        warnings = [r for r in caplog.records if r.levelno >= logging.WARNING]\n+        assert any(\"get_session failed\" in r.getMessage() for r in warnings), \\\n+            f\"Expected a get_session warning, got: {[r.getMessage() for r in warnings]}\"\n+        assert any(\"disk full\" in r.getMessage() for r in warnings)\n+\n+    def test_null_system_prompt_warns_about_unusable_stored_state(self, caplog):\n+        \"\"\"Row exists but system_prompt is NULL → WARNING + fresh build.\"\"\"\n+        db = MagicMock()\n+        db.get_session.return_value = {\"system_prompt\": None}\n+        agent = _make_agent(session_db=db)\n+\n+        with caplog.at_level(logging.WARNING, logger=\"agent.conversation_loop\"):\n+            _restore_or_build_system_prompt(agent, None, [{\"role\": \"user\", \"content\": \"hi\"}])\n+\n+        agent._build_system_prompt.assert_called_once()\n+        warnings = [r.getMessage() for r in caplog.records if r.levelno >= logging.WARNING]\n+        assert any(\"is null\" in m and \"rebuilding\" in m for m in warnings), \\\n+            f\"Expected null-stored-prompt warning, got: {warnings}\"\n+\n+    def test_empty_system_prompt_warns_about_silent_persistence_bug(self, caplog):\n+        \"\"\"Row exists but system_prompt is '' → WARNING about silent write bug.\"\"\"\n+        db = MagicMock()\n+        db.get_session.return_value = {\"system_prompt\": \"\"}\n+        agent = _make_agent(session_db=db)\n+\n+        with caplog.at_level(logging.WARNING, logger=\"agent.conversation_loop\"):\n+            _restore_or_build_system_prompt(agent, None, [{\"role\": \"user\", \"content\": \"hi\"}])\n+\n+        agent._build_system_prompt.assert_called_once()\n+        warnings = [r.getMessage() for r in caplog.records if r.levelno >= logging.WARNING]\n+        assert any(\"is empty\" in m and \"rebuilding\" in m for m in warnings), \\\n+            f\"Expected empty-stored-prompt warning, got: {warnings}\"\n+\n+    def test_db_write_failure_warns_loudly(self, caplog):\n+        \"\"\"update_system_prompt raising → WARNING (was DEBUG before).\"\"\"\n+        db = MagicMock()\n+        # No prior row (first turn)\n+        db.get_session.return_value = None\n+        db.update_system_prompt.side_effect = RuntimeError(\"database is locked\")\n+        agent = _make_agent(session_db=db)\n+\n+        with caplog.at_level(logging.WARNING, logger=\"agent.conversation_loop\"):\n+            _restore_or_build_system_prompt(agent, None, [])\n+\n+        # Built and assigned the cache anyway\n+        agent._build_system_prompt.assert_called_once()\n+        assert agent._cached_system_prompt == \"BUILT_PROMPT\"\n+        # Warning surfaced\n+        warnings = [r.getMessage() for r in caplog.records if r.levelno >= logging.WARNING]\n+        assert any(\n+            \"update_system_prompt failed\" in m and \"database is locked\" in m\n+            for m in warnings\n+        ), f\"Expected write-failure warning, got: {warnings}\"\n+\n+    def test_no_history_with_null_row_does_not_warn(self, caplog):\n+        \"\"\"First turn (no history) hitting a null row is not surprising — no warn.\"\"\"\n+        db = MagicMock()\n+        db.get_session.return_value = {\"system_prompt\": None}\n+        agent = _make_agent(session_db=db)\n+\n+        with caplog.at_level(logging.WARNING, logger=\"agent.conversation_loop\"):\n+            # Empty history → DB read is skipped entirely\n+            _restore_or_build_system_prompt(agent, None, [])\n+\n+        db.get_session.assert_not_called()\n+        # No \"rebuilding from scratch\" warning because history is empty\n+        warnings = [r.getMessage() for r in caplog.records if r.levelno >= logging.WARNING]\n+        assert not any(\"rebuilding\" in m for m in warnings)\n+\n+\n+# ---------------------------------------------------------------------------\n+# Byte-stability invariant\n+# ---------------------------------------------------------------------------\n+\n+\n+class TestPromptStabilityInvariant:\n+    def test_restored_prompt_is_byte_identical_to_stored(self):\n+        \"\"\"The restored prompt must equal the stored bytes exactly — no\n+        normalization, trimming, or concat that could shift the prefix.\n+\n+        This is the core invariant: any byte-level change at this point\n+        invalidates KV cache on every prefix-cache backend.\n+        \"\"\"\n+        stored = (\n+            \"You are Hermes Agent.\\n\"\n+            \"\\n\"\n+            \"Conversation started: Sunday, May 17, 2026\\n\"\n+            \"Session ID: 20260517_153500_abc123\\n\"\n+        )\n+        db = MagicMock()\n+        db.get_session.return_value = {\"system_prompt\": stored}\n+        agent = _make_agent(session_db=db)\n+\n+        _restore_or_build_system_prompt(agent, None, [{\"role\": \"user\", \"content\": \"hi\"}])\n+\n+        # Identity check — must be the same object reference for maximum\n+        # confidence we're not slicing/copying/normalizing.\n+        assert agent._cached_system_prompt == stored\n+        # Byte-level check\n+        assert agent._cached_system_prompt.encode(\"utf-8\") == stored.encode(\"utf-8\")\n+\n+\n+if __name__ == \"__main__\":\n+    pytest.main([__file__, \"-v\"])"]},{"file":"tests/run_agent/test_run_agent.py","hunks":["@@ -989,6 +989,28 @@ def test_includes_datetime(self, agent):\n         # Should contain current date info like \"Conversation started:\"\n         assert \"Conversation started:\" in prompt\n \n+    def test_datetime_is_date_only_not_minute_precision(self, agent):\n+        \"\"\"Timestamp must be date-only (no HH:MM) so the system prompt\n+        stays byte-stable for the full day. Minute precision invalidates\n+        prefix-cache KV on every rebuild path (compression, fresh-agent\n+        gateway turns, session resume without a stored prompt).\"\"\"\n+        prompt = agent._build_system_prompt()\n+        # Find the line and strip it for inspection\n+        for line in prompt.splitlines():\n+            if line.startswith(\"Conversation started:\"):\n+                # Must NOT contain AM/PM indicator (minute precision had %I:%M %p)\n+                assert \" AM\" not in line and \" PM\" not in line, (\n+                    f\"Timestamp line has time-of-day, breaks daily cache stability: {line!r}\"\n+                )\n+                # Must NOT contain a colon followed by two digits (HH:MM pattern)\n+                import re as _re\n+                assert not _re.search(r\":\\d{2}\", line), (\n+                    f\"Timestamp line has HH:MM, breaks daily cache stability: {line!r}\"\n+                )\n+                break\n+        else:\n+            assert False, \"Expected a 'Conversation started:' line in the system prompt\"\n+\n     def test_includes_nous_subscription_prompt(self, agent, monkeypatch):\n         monkeypatch.setattr(run_agent, \"build_nous_subscription_prompt\", lambda tool_names: \"NOUS SUBSCRIPTION BLOCK\")\n         prompt = agent._build_system_prompt()\n"]}]},{"kind":"pr","number":40329,"title":"perf(agent): move runtime metadata out of cached prompt (#3353)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#3353","number":3353}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":68085,"title":"fix(prompt-caching): inject cache breakpoints after message normalization","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#70990","number":70990},{"kind":"pr","value":"#68191","number":68191}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":68258,"title":"fix(agent): cache static system prompt prefixes","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#70990","number":70990},{"kind":"pr","value":"#68191","number":68191}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":69341,"title":"fix(caching): split system prompt into stable/volatile blocks for cross-session cache hits (#68191)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#70990","number":70990},{"kind":"pr","value":"#68258","number":68258}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":69633,"title":"fix(agent): support stable cross-session prefix caching on Anthropic","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#69704","number":69704}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":69701,"title":"fix(agent): support stable cross-session prefix caching on Anthropic","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#69633","number":69633},{"kind":"pr","value":"#69633","number":69633},{"kind":"pr","value":"#69633","number":69633}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":69704,"title":"fix(agent): support stable cross-session prefix caching on Anthropic","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#70990","number":70990},{"kind":"pr","value":"#68258","number":68258},{"kind":"pr","value":"#68085","number":68085}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":70541,"title":"fix: split system prompt into two cache blocks for cross-session warm hits (#68191)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#70990","number":70990},{"kind":"pr","value":"#68258","number":68258},{"kind":"pr","value":"#68085","number":68085}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":70896,"title":"fix(cache): split system prompt into static/session blocks for cross-session cache hits (#68191)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#70990","number":70990}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":70990,"title":"fix(cache): split system prompt into static/session blocks for cross-session cache hits","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/agent_init.py","agent/coding_context.py","agent/conversation_compression.py","agent/conversation_loop.py","agent/moa_loop.py","agent/prompt_caching.py","agent/system_prompt.py","tests/agent/test_anthropic_adapter.py","tests/agent/test_prompt_caching.py","tests/agent/test_system_prompt.py","tests/agent/test_system_prompt_restore.py","tests/run_agent/test_run_agent.py"]}],"trust":{"default_tier":"verified","includes_suggested":false}}