fix(engine): restore multi-turn crisis escalation on the agentic path (P0, closes #129) #130
Reference in New Issue
Block a user
Delete Branch "fix/129-history-amplification"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
P0 SAFETY. Closes #129. Multi-turn crisis escalation has been inert on the agentic path since
ff421d3(2026-08-05). This restores it, and adds the gate that would have caught it.What was broken
The crisis score has two halves: the message just sent, and the distress accumulated across the conversation. The second half exists for the case where no single message trips the bell on its own — distress that builds over several turns.
ff421d3correctly moved conversation history to a per-session key viaconv_hist_key(session_id). One consumer did not move with it: the agentic path's L1 screen kept reading the anonymousconv_historybucket. The desktop app always mints a session id (DaemonClient.kt:706), so history was always written undersession_hist_<id>and that read always returned"". The escalation half scored 0 on every real conversation.Single-message hard bell was never affected. The plain path (
soul.el:398) was always correct.The comment on that line documented this exact bug being fixed once already, under issue #9. The fix was right then; the rename re-broke it and the comment went on describing a repair that no longer held. A comment is not a gate — which is most of why this PR is bigger than one line.
The fix
One line:
state_get("conv_history")→state_get(conv_hist_key(session_id)). Same thing the thread-anchoring read thirty lines below it in the same handler already does.The rest is structure so it cannot happen quietly again:
agentic_safety_screen()now owns the two decisions that were inline — which window the screen sees, and the screen call. Inline safety inputs are untestable safety inputs.Two-leg proof — one variable
Both runs are the same tree and the same command; the only difference is that one line.
3. REGRESSION #129 … FAIL got: soft_bell expected: hard_bell— 8 passed, 1 failedThe test also pins the specificity leg (a calm history must NOT escalate), the isolation leg (one session's distress must not score another session), and the anonymous-session fallback, so it cannot pass by hard-belling everything.
Full engine rebuild from these sources is clean:
gen-soul-amalgam.sh→ 1,164,103 bytes / 1226 inlined bodies (gate wants ≥ 1200);cc-brain.sh→ 903,096 bytes, 0 errors.nmconfirmsT _agentic_safety_screenandT _conv_hist_keyin the built binary.Why there is a test runner in this PR
tests/has held 14 test programs for months with no way to run them. CI does not run them. The convention printed in their own headers —elc soul.el && ./soul --test tests/x.el— refers to a--testflag the El runtime does not implement. The tests were documentation, not gates. That is how a P0 safety regression shipped with a test directory sitting right there.scripts/run-el-test.shcompiles and runs one test program, reusing thegen-soul-amalgam.shdiscovery (elc emits an extern prototype for a module with a.elhbeside it and inlines the bodies when it does not, so a test importing../chat.elmust compile in a scratch tree with headers removed). Scratch copy on purpose — the worktree is shared. It runs under a throwawayHOMEso a test can never reach the live engram. Exit status is the gate: these tests print failures and still exit 0, so the runner greps forFAILlines and for a zero assertion count too.Two traps it deliberately does not inherit from the older suites, worth knowing before anyone writes another test:
let pass_count = pass_count + 1inside an assert function declares a local that dies with the call. Every existing suite prints "0 passed, 0 failed" regardless of what happened.cgiblock compiles as a'utility', which may not reference the self-formation primitives (llm_call_system,llm_vision) thatchat.el's agentic loop calls — it fails to build on a capability violation it never triggers at runtime.Rung reached — stated plainly
BUILT + RUNS (discriminating test). NOT in a DMG, NOT verified in the app a human opens. Neither is claimed here.
Not fixed by this PR
feat/soul-openai-tools-v2carries the same defect independently atchat.el:2937. It needs this change or a merge before it ships.dist/soul.c, which was last regenerated 2026-08-03 and is now 8 El-commits behind — it does not containconv_hist_keyat all. Merging this fixes the sources and the desktop build; the cloud engine stays stale until that amalgam is regenerated. Tracked in #111 (with today's measurements) and neuron-ui#209.🤖 Generated with Claude Code