# PLAN r3 — model-router wave 3-C: `/route forget` (2026-10-11) r1 → r2 after simplicity CONCERNS(3), correctness CONCERNS(4), robustness CONCERNS(4); r2 → r3 after the confirmation (correctness CONCERNS(3)). Precedence r3 > r2 > r1. Contract `.claude/tasks/contracts/2026-10-11-model-router-w3c-forget-1457.md`. Base W3-B (604a6c4 + 32b71d2 + 938c3d1). Corrected facts: `Memory` holds ok/ask/askLocal/base/confirmed/about/local/key only (no `changed`, no `projects`); the composer gate lives in `registerCommandHook`, not in `handleCommand`; `writeNow` always writes once the patch ran and toasts UPDATED, returns false on refusal, throws when the rebuild fails after a landed write; `show` prints no command list (the only visible list is the `argumentHint`); `routeMainBySkill` copies a route into `turnMain`/`runMain` and a rebuild never touches those slots; the shipped file holds one changed row (security-auditor verify → judge) whose frontmatter floor was aligned to `opus`: restoring it without re-aligning the floor makes the census FAIL (no `changed` entry = no WARN exemption). ## Behaviour (composer `/route forget `, exactly ONE argument) - Parsing: no arg or more than one → "usage: /route forget ". Reserved words `all` and `projects`; a row or phase named like them cannot be forgotten by name (answer says "edit routing.json by hand"). - Guard: while a dialog is open (`st.asking !== null`) → "answer the open dialog first", nothing written. The forget confirmation TAKES the slot itself (`st.asking = 'forget:'` in a try/finally around `askOr`) so no first-use dialog opens under it and two forgets cannot stack. - The decision runs on the FRESH file, twice: a PRE-read (after awaiting `st.writes`, so a landed Keep is visible) decides the no-write paths and the confirmation counts; the PATCH itself re-runs `forgetPlan(file, target)` on `writeNow`'s own freshly read `file` (the closure exports the counts actually applied; the answer reports those). Pre-read missing or unparsable → the writer's refusal path ("routing.json is missing or unreadable: nothing saved"); nothing to remove, no refused restore and the key(s) not in `st.asked` → "nothing to forget for " / "nothing to forget", NO write; only a refused restore → its reason, NO write; key(s) only in `st.asked` → reset them, NO write, "asked again at the next use, nothing was saved" (when the key is decided by the machine override or a project row, say "still decided by …" instead). - ``: for EVERY kind in KINDS (skills, agents, phases): delete `confirmed..`; restore `changed..` → row = `from` ONLY when `from` is a string, the row exists, the row equals `changed.to`, and `from` is a phase of `file.phases` or `DEFAULT_CONFIG.phases` (else the entry is kept and the answer says why); delete `projects[k]..` for every k, pruning an emptied kind table then an emptied `projects[k]`; then `st.asked.delete(key)`. - `all`: the same over every entry of `confirmed`, `changed`, `projects`; `projects`: only `projects` emptied. Both ASK FIRST through `askOr`: "Forget decision(s): row(s) restored, project exception(s) in repo(s)?" options ["Cancel", "Forget"] (Cancel first; any other answer, a dismissed dialog or headless = cancelled, nothing written); skipped when every count is zero ("nothing to forget"). - Write: ONE `writeRouting` patch (the closure carries the counts out); outcomes: false → "nothing saved" (the writer's toast already explains); true → asked keys deleted, config rebuilt; a rebuild failure after a landed write is signalled by a dedicated error class (`RebuildFailed`, thrown by `writeNow` there and only there) → "saved, config not rebuilt: /route reload" (asked keys untouched); any other throw (patch, fs.write) → "forget not applied ()", asked keys untouched. - Answer, one formatter (used by every form, per restored row): "forgot