From a8cfbbf5ccb3e777bf7b673ca945134e4be29873 Mon Sep 17 00:00:00 2001 From: Justin Gao Date: Tue, 2 Jun 2026 13:52:58 +0800 Subject: [PATCH 1/2] =?UTF-8?q?refs(#2264):=20Phase=203=20=E2=80=94=20emit?= =?UTF-8?q?=20PrefixCacheChange=20events=20from=20FrozenPrefix=20layer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upgrades the three-zone diagnostic layer from silent tracing::debug! to user-visible Event::PrefixCacheChange events. - First turn: emits 'frozen: ' event (stability 100%, no change) - Drift detected: emits PrefixDrift description event (stability 0%, changed) - Stable turns: no event (existing PrefixStabilityManager handles this) Phase 2→3: only turn_loop.rs, +18 lines. --- crates/tui/src/core/engine/turn_loop.rs | 27 +++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/crates/tui/src/core/engine/turn_loop.rs b/crates/tui/src/core/engine/turn_loop.rs index 6b7ffd1ff..eb06f4290 100644 --- a/crates/tui/src/core/engine/turn_loop.rs +++ b/crates/tui/src/core/engine/turn_loop.rs @@ -314,7 +314,7 @@ impl Engine { // Three-zone prefix contract (#2264): freeze baseline on first // turn, verify against it on subsequent turns. Operates alongside // PrefixStabilityManager as an independent diagnostic layer. - // Phase 2: warn-only, auto-re-freeze on drift. + // Phase 3: emit PrefixCacheChange events for user visibility. let system_text = crate::prefix_cache::system_prompt_text(self.session.system_prompt.as_ref()); let current_tools: &[crate::models::Tool] = active_tools.as_deref().unwrap_or_default(); @@ -326,6 +326,17 @@ impl Engine { target: "prefix_cache", "three-zone drift: {drift}" ); + let _ = self + .tx_event + .send(Event::PrefixCacheChange { + description: drift.to_string(), + system_prompt_changed: drift.system_changed, + tools_changed: drift.tools_changed, + stability_pct: 0, + changed: true, + pinned_combined_hash: frozen.hash().to_string(), + }) + .await; let pinned = PinnedPrefix::new( self.session.system_prompt.as_ref(), current_tools.to_vec(), @@ -338,7 +349,19 @@ impl Engine { self.session.system_prompt.as_ref(), current_tools.to_vec(), ); - self.session.frozen_prefix = Some(pinned.freeze()); + let frozen = pinned.freeze(); + let _ = self + .tx_event + .send(Event::PrefixCacheChange { + description: format!("frozen: {}", frozen.short_id()), + system_prompt_changed: false, + tools_changed: false, + stability_pct: 100, + changed: false, + pinned_combined_hash: frozen.hash().to_string(), + }) + .await; + self.session.frozen_prefix = Some(frozen); } } From 21721a45898d102faf0097b8b0252f004289d7e3 Mon Sep 17 00:00:00 2001 From: Justin Gao Date: Tue, 2 Jun 2026 13:59:45 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20don't=20double-count=20drift=20?= =?UTF-8?q?=E2=80=94=20only=20emit=20PrefixCacheChange=20on=20first=20free?= =?UTF-8?q?ze?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The existing PrefixStabilityManager::check_and_update() block already emits PrefixCacheChange on every check (stable + drift). The three-zone layer was re-using the same event type, doubling prefix_change_count and prefix_checks_total. Fix: emit only the one-shot 'frozen: ' event on first turn. Drift is still verified and logged (tracing::debug!) but not re-emitted — check_and_update already surfaces the change. --- crates/tui/src/core/engine/turn_loop.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/crates/tui/src/core/engine/turn_loop.rs b/crates/tui/src/core/engine/turn_loop.rs index eb06f4290..472bc9bc7 100644 --- a/crates/tui/src/core/engine/turn_loop.rs +++ b/crates/tui/src/core/engine/turn_loop.rs @@ -314,7 +314,9 @@ impl Engine { // Three-zone prefix contract (#2264): freeze baseline on first // turn, verify against it on subsequent turns. Operates alongside // PrefixStabilityManager as an independent diagnostic layer. - // Phase 3: emit PrefixCacheChange events for user visibility. + // Phase 3: emit a one-shot 'frozen' event on first turn. + // Drift is logged (tracing::debug!) but not re-emitted — + // PrefixStabilityManager already reports the change above. let system_text = crate::prefix_cache::system_prompt_text(self.session.system_prompt.as_ref()); let current_tools: &[crate::models::Tool] = active_tools.as_deref().unwrap_or_default(); @@ -326,17 +328,6 @@ impl Engine { target: "prefix_cache", "three-zone drift: {drift}" ); - let _ = self - .tx_event - .send(Event::PrefixCacheChange { - description: drift.to_string(), - system_prompt_changed: drift.system_changed, - tools_changed: drift.tools_changed, - stability_pct: 0, - changed: true, - pinned_combined_hash: frozen.hash().to_string(), - }) - .await; let pinned = PinnedPrefix::new( self.session.system_prompt.as_ref(), current_tools.to_vec(),