# Context must be ordered stable-to-volatile — the stable prefix (layers 1-7) gets cached, the volatile suffix (layers 8-12) never does ## 1. The 12-layer model (the stable-to-volatile ordering) | # | Layer | Stable across turns? | Source | SSDL | |---|---|---|---|---| | 1 | Role instructions (model + provider) | yes | `_get_combined_system_prompt` | `[I]` | | 2 | Function-calling schema | yes | per provider | `[I]` | | 3 | Discovered tool descriptions | yes | `mcp_client.get_tool_schemas()` | `[I]` | | 4 | System prompt preset | yes | `app_state.ai_settings.system_prompt` | `[I]` | | 5 | Persona profile | yes | `app_state.active_persona` | `[I]` | | 6 | Project context (per `manual_slop.toml`) | yes | NEW (Candidate 14) | `[I]` | | 7 | Knowledge digest (per `knowledge/digest.md`) | yes (within a gc cycle) | NEW (Candidate 8) | `[I]` | | 8 | Discussion metadata (name, role count) | no (per turn) | `disc_entries[:1]` or `disc_meta` | `───` (data) | | 9 | Active preset (FileItem set) | no (per turn) | `self.context_files` | `───` (data) | | 10 | Per-file details (history, slices, notes) | no (per file) | per `FileItem` | `───` (data) | | 11 | Tool-call results from prior turns | no (per turn) | per `_reread_file_items` | `───` (data) | | 12 | The user message | no (per turn) | the input | `───` (data) | **The cache boundary is at layer 7/8.** Layers 1-7 are byte-identical across turns of the same discussion (and across discussions of the same mode). Layers 8-12 change per turn. --- ## 2. The byte-comparison test (the design contract) The design rule "stable prefix is byte-identical" must be testable. The test: ```python # In tests/test_aggregate_caching.py (NEW) def test_aggregate_stable_to_volatile_ordering(): """The first N characters of the context should be identical across turns of the same conversation, when no stable-layer inputs change.""" ctrl = mock_app_controller() ctrl.ai_settings.system_prompt = "Test system prompt" ctrl.active_persona = mock_persona() # Turn 1 turn1 = aggregate.build_initial_context(ctrl, user_message="first prompt") # Turn 2 (same stable inputs, different user message) turn2 = aggregate.build_initial_context(ctrl, user_message="second prompt") # The first N characters should be identical (N = where the volatile layers start) N = aggregate.stable_prefix_length(ctrl) assert turn1[:N] == turn2[:N], f"Stable prefix mismatch: {turn1[:N]!r} != {turn2[:N]!r}" ``` **The test is the contract.** If a new layer is added in the middle of the stack, this test fails; the agent must either move the layer to the stable position or update the test (with written justification).