diff --git a/chat.el b/chat.el index 7adae5e..12e079c 100644 --- a/chat.el +++ b/chat.el @@ -2519,6 +2519,24 @@ fn handle_chat_plan(body: String) -> String { return "{\"plan\":" + plan_json + ",\"model\":\"" + json_safe(model) + "\"}" } +// ── agentic_safety_screen — the agentic path's L1 input gate ────────────────── +// +// Extracted 2026-08-07 (issue #129) so the agentic path's safety INPUT is +// reachable by a test. It owns exactly two decisions: which history window the +// screen sees, and the screen call itself. +// +// Why it is a function and not two inline lines: those two lines sat in the +// middle of a 300-line handler, and a key rename (ff421d3) moved the producer +// without moving this consumer. Nothing failed, nothing logged — the +// history-amplification half of the crisis score simply received "" on every +// real session for a day. Inline safety inputs are untestable safety inputs. +// See tests/test_history_amplification.el, which fails if this window and +// conv_history_record ever stop agreeing. +fn agentic_safety_screen(session_id: String, message: String) -> String { + let history: String = state_get(conv_hist_key(session_id)) + return safety_screen(message, history) +} + fn handle_chat_agentic(body: String) -> String { let message: String = json_get(body, "message") if str_eq(message, "") { @@ -2554,10 +2572,10 @@ fn handle_chat_agentic(body: String) -> String { // L1 safety screen — agentic path must pass the same gate as layered_cycle. // Hard bell: return the crisis response immediately, do not enter the agentic loop. - // Fix(issue #9): "conversation_history" key was never written; history lives under "conv_history". - // Old key caused history-amplification in safety_screen to always receive "" on agentic path. - let history: String = state_get("conv_history") - let screen_result: String = safety_screen(message, history) + // The history window this screen sees is owned by agentic_safety_screen (issue #129); + // it must be the same window conv_history_record writes, or the escalation half of the + // crisis score is silently starved. Do not inline this read back into the handler. + let screen_result: String = agentic_safety_screen(sess_for_root, message) let screen_action: String = json_get(screen_result, "action") if str_eq(screen_action, "hard_bell") { safety_log_bell("hard", json_get(screen_result, "reason"), str_slice(message, 0, 80))