From 0d69f8213999e4cf74dfcc270d6e19537800c975 Mon Sep 17 00:00:00 2001 From: sb-iam <59984144+sb-iam@users.noreply.github.com> Date: Sun, 28 Jun 2026 07:42:42 -0500 Subject: [PATCH] feat(continual-learning): activate negative-feedback loop (RSI steps 1-2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step 1 — policy.ts shouldIntervene: suppress on a prior dismissal alone. The former `&& !priorOutcome.wasRealCollision` term was dead code (outcomes record wasRealCollision hardcoded true), so the 85 real dismissals in Atlas were never used. Now a prior accepted===false suppresses the next identical nudge. Spec: continual-learning/policy.md:41, spec.md:163. Step 2 — podman.ts handle: only escalate severity to 'critical' (the spoken alert trigger) when the recalled prior was an accepted *real* collision, instead of blanket-escalating every recall. Surfaces learned routing in preferredAction; stops dismissed/false priors over-escalating to voice. Spec: continual-learning/policy.md:62-63, plan.md:66. Documentation-first: adds PLAN.md section 8 "P0.5 - RSI negative-feedback activation" with both rungs + follow-ups. No schema change. backend typecheck passes. Independent of the MongoDB-cleanup handoff (Codex). Co-Authored-By: Claude Opus 4.8 --- backend/src/agent/podman.ts | 9 ++++++++- backend/src/memory/policy.ts | 7 ++++++- docs/PLAN.md | 31 +++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/backend/src/agent/podman.ts b/backend/src/agent/podman.ts index 9c448ef..c08f13d 100644 --- a/backend/src/agent/podman.ts +++ b/backend/src/agent/podman.ts @@ -100,7 +100,14 @@ export class PodMan { if (this.activeConflicts.has(key)) return; // single-shot: already voiced, still unresolved const prior = await recallSimilar(collision); // Loop A: exact/vector recall raises confidence - if (prior) collision.severity = 'critical'; + // Only escalate to critical (which triggers the spoken alert) when the + // recalled prior was an *accepted real* collision. Blanket-escalating every + // recall — including dismissed/false-positive priors — masked the learned + // routing in preferredAction and made recalled noise scream "CRITICAL". + // (RSI Step 2 — continual-learning/policy.md:62-63, plan.md:66) + if (prior?.priorOutcome?.accepted && prior?.priorOutcome?.wasRealCollision) { + collision.severity = 'critical'; + } if (!shouldIntervene(collision, prior)) return; // Loop B: policy gate this.activeConflicts.add(key); // claim now we're alerting; re-armed in onScreenFrame on resolution diff --git a/backend/src/memory/policy.ts b/backend/src/memory/policy.ts index b8be7bf..4d92395 100644 --- a/backend/src/memory/policy.ts +++ b/backend/src/memory/policy.ts @@ -12,7 +12,12 @@ export function shouldIntervene(collision: Collision, prior: RecalledCollision | if (collision.severity === 'info') return false; const priorOutcome = prior?.priorOutcome; - if (priorOutcome && !priorOutcome.accepted && !priorOutcome.wasRealCollision) return false; + // Suppress when the identical prior was dismissed (accepted === false). The + // former `&& !priorOutcome.wasRealCollision` term was dead code: outcomes are + // recorded with wasRealCollision hardcoded true, so the gate never fired and + // the 85 real dismissals in Atlas were ignored. Dismissals are the negative + // signal per continual-learning/policy.md:41 + spec.md:163. (RSI Step 1) + if (priorOutcome && !priorOutcome.accepted) return false; const cooldown = cooldownMs(); const last = lastNudgeByPod.get(collision.podId) ?? 0; diff --git a/docs/PLAN.md b/docs/PLAN.md index eca921a..eb1d4a7 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -484,6 +484,37 @@ artifact. recorded backup. - Keep backup video on a separate device. +### P0.5 - RSI negative-feedback activation (continual-learning) + +The continual-learning loop records outcomes but never feeds the negative +signal back. Live Atlas (2026-06-28): `outcomes` = 22 accepted / 85 dismissed, +yet `wasRealCollision` is `true` in 107/107 (hardcoded), so the suppression +gate is dead and dismissals are unused. These two rungs activate the loop with +no schema change. Owner: RSI track. Independent of the MongoDB-cleanup handoff. + +1. **Step 1 - suppress on prior dismissal alone** ✅ + - `backend/src/memory/policy.ts` `shouldIntervene`: remove the dead + `&& !priorOutcome.wasRealCollision` term so a prior `accepted === false` + suppresses the next identical-signature nudge. + - Spec: `docs/continual-learning/policy.md:41` (dismissed = negative signal), + `spec.md:163` (dismissals adapt suppression). + - Caveat: recall is single-shot most-recent (`memory/vectors.ts`), so this is + "last-outcome-wins" until Step 3 (derive `wasRealCollision`) lands. + +2. **Step 2 - gate the recall severity escalation** ✅ + - `backend/src/agent/podman.ts` `handle`: only force `severity = 'critical'` + when the recalled prior was an accepted *real* collision, instead of + blanket-escalating every recall. Surfaces the learned routing in + `preferredAction`; stops dismissed/false priors over-escalating to voice. + - Spec: `docs/continual-learning/policy.md:62-63` (prefer prior accepted + kind), `plan.md:66` (second similar event behaves differently). + +Follow-ups (separate rungs, not in this change): Step 3 derive +`wasRealCollision` from git overlap; Step 4-5 `strategy_versions` + +Gemini-proposed `LearningProposal` slice; seed a clean demo pod with a repeated +dismissed signature (the historic 85 dismissals are orphaned — `collisionId` +resolves to no collision — so they cannot drive the demo verifier). + ### P1 - polish the money moment - Add visible live inference captions in the PWA.