DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path change-requests/loomworks-cr-add-side-rework-v0_1.md

Loomworks — CR — add-side rework: classification, confirm-back, commit-gate, held discard — v0.1

Document: loomworks-cr-add-side-rework-v0_1 Version: v0.1 Status: change request — drafted against live code (grounded read-only inspection at engine main ed5b2b4). Phased, engine-first then frontend. Operator authorizes; CC executes per-step with halt-before-push. Date: 2026-06-27 Grounding: read-only seam inspection 2026-06-27 (classifier pipeline, _route_remember_about_me, _normalize_personal_fact, commit_assertion gates, MemoryRoom.tsx held card, dn-18 state). Scoping notes folded in: loomworks-remember-about-me-confirmation-loop-scoping-note-v0_1 (F1/F2/F3), loomworks-voice-entry-confirm-back-simplification-scoping-note-v0_1 (acknowledge-without-asking + Dismiss). Standing notes in force: "Companion must not claim what the system did not do"; "Database as definition layer, not decision layer"; "Merged is not deployed — restart dev after a runtime-affecting merge."


Plain-language summary

This reworks how a spoken note becomes a remembered fact, fixing several things the Operator hit in testing — all on the "add a note" side.

Today: you say "park the car on level 8," and several things go wrong. The system often mis-reads a command-shaped fact as just conversation (so nothing gets saved). When it does save, it asks "is that right?" and waits — and the matching that ties your "yes" to the right draft is brittle, so it loops. And personal notes get committed by the Companion on your "yes," not by you — which isn't how the rest of the system works (everywhere else, you approve by pressing Confirm).

After this CR: command-shaped facts are recognized and held. The "is that right?" round-trip is gone — you just hear "Got it — parked on level 8, held," and the note waits for you to approve it by pressing Confirm (the same way every other note works). If a note was misheard, you press Dismiss and it's cleanly marked as never-was-a-fact (not a "correction" of something real). The whole add-side becomes consistent with how the seed says approval should work: you approve the held→committed step, not the Companion.

What the Operator does after this lands

  1. Restart the dev engine (this CR changes runtime behaviour and prompts — "merged is not deployed").
  2. Test the add-side: speak a command-shaped fact ("park the car on level 8") → it should be held with a "Got it… held" ack, no "is that right?" question. Then open the note's Memory room → press Confirm to commit, or Dismiss to discard a mishear.
  3. The dn-18 orphan ("Car parked on Level 8") is handled naturally: open "Your space" → its Memory room → Confirm it (you wanted it recorded) or Dismiss it.

The seven decisions, settled

| # | Decision | Settled as | |---|---|---| | 1 | F1/F2/F3 scope | F1 + F2 together; F3 (routing) explicit below | | 2 | F2 approach | Match held by id (recency result), drop text comparison — largely dissolved by the confirm-back drop | | 3 | F3 personal-store routing | Explicit decision §Phase 2 — kept as-is for now (personal facts → personal engagement), flagged for a later product call | | 4 | dn-18 orphan | Commit it (via the new held-card, opening "Your space") | | 5 | Confirm-back shape | Acknowledge-without-asking + mandatory held-card Dismiss | | 6 | Discard disposal | New discarded terminal state — append-only-faithful, distinct from retracted | | 7 | Sequencing | Unified into this one CR |

The load-bearing finding (why this CR is shaped as it is)

Dropping the confirm-back does most of F2's work for free. Today the confirm-back ("is that right?") creates a conversational "yes" that must be matched back to the pending held — and that match (text-equality in _normalize_personal_fact) is the brittle thing that loops. Remove the confirm-back, and there is no "yes" to match — the held just waits for the held-card Confirm. So F2's matching surface largely dissolves; what remains is a much smaller "match by id" for any conversational refinement still supported.

But dropping the confirm-back removes personal memory's only current commit gate: today personal memory commits as the Companion (actor_from_companion, which commit_assertion permits — it only blocks kind='agent') on the "yes." With no "yes," personal memory needs the human held-card Confirm as its gate — which is more seed-aligned (the Operator approves the held→committed transition, same as engagement assertions). So this CR consciously moves personal-memory commit onto the human held-card gate.

The three held-states, now distinct (the §6 substrate addition)

retracteddiscarded is the seed-alignment: collapsing them would make a mishear look like a retracted fact. Append-only is preserved (the draft's existence stays; nothing is hard-deleted) — discarded is a terminal state, not a deletion.


Phase 1 (engine) — F1: classifier recognizes command-shaped facts

Seam: prompts/intent_classifier.md (LLM classifier; mechanism unchanged). Today facts are routed by declarative examples only (domain → add_knowledge, personal → remember_about_me); a fact-bearing imperative ("park the car on level 8") matches neither and falls to general_conversation.

Change: prompt edit only — add imperative-as-fact discrimination and examples, with the personal-vs-domain split applied to imperatives ("park the car on level 8" = personal; "set the soil pH to 6.5" = domain/add_knowledge). No new intent label, no router change, no mechanism change.

Tests: test_phase_43_classifier, test_phase_42* — add imperative-fact cases (personal + domain) asserting correct routing, and a guard that genuine commands/conversation still go to general_conversation.

Commit, suite green.

Phase 2 (engine) — F3 routing: explicit decision, kept as-is

Decision: personal facts stated inside an engagement currently route to the personal engagement (personal_eid), not the active engagement (E0007). For this CR, keep that behaviour — it's coherent (personal memory is personal, regardless of which engagement you're in when you say it). Flag for a later product call: whether some in-engagement personal facts should instead attach to the active engagement. Not changed here; named so it's a conscious decision, not an accident.

No code change in this phase — it documents the routing as intended-for-now. (If the inspection of Phase 1's imperative examples surfaces a routing ambiguity, raise it; otherwise this is a no-op decision phase.)

Phase 3 (engine) — F3/confirm-back: drop the question, move the commit gate

Seam: _route_remember_about_me (router.py ~323/326 held write; the confirm-back is responder text in remember_about_me.md:7-8); commit_assertion gates.

Changes:

  1. Drop the confirm-back question. On a new personal fact, hold immediately (the held write via actor_from_companion already happens on the first turn — unchanged) and have the responder acknowledge without asking: server-composed "Got it — [X], held." (naming the captured content so the Operator sees it for a transcription glance). Remove the "is that right?" responder text and the Action: held prompt's forbidding of "Got it" (invert it — "Got it… held" is now the correct ack).
  2. Move personal-memory commit to the human held-card gate. Remove the conversational-"yes" → commit_assertion(actor_from_companion) path for personal memory. Personal held drafts now commit only via the held-card Confirm (actor_from_person), same as engagement assertions. This is the seed-aligned approval gate.
  3. F2 reduces to match-by-id for any remaining refinement. With no confirm-back "yes," the common-path continuation-matching dissolves. If conversational refinement of a pending held is still supported (e.g. "no, make it level 9"), match the pending held by id (the recency-query result already returns the held's id — _find_recent_held_companion_assertion), and drop the _normalize_personal_fact text-equality test (router.py:196) and the pending_held_blocks_new_fact guard's text-dependence (router.py:2442). If refinement is judged better handled at the held-card (edit there), F2 can be deferred to that — [CC reports whether any conversational refinement path remains after the confirm-back drop; if none, F2 is fully dissolved and the text-comparison code is removed, not replaced.]

Tests: the remember-about-me flow tests — a personal fact holds with a "Got it… held" ack and no question; no conversational-yes commit for personal memory; the loop case ("Car parked on Level 8" re-extraction mismatch) no longer loops because there's no text-match step to fail. Truthfulness gating intact (no "Done"/"saved" claim without the real state).

Commit, suite green.

Phase 4 (engine) — the discarded terminal state (substrate)

Seam: engagement/assertions.py (today: add_assertion, commit_assertion, revise_assertion, retract_assertion — no discard path; Loom is append-only, only retract removes a held, writing a correction).

Change: add a clean held-draft discard.

  1. A new terminal state discarded (distinct from retracted), valid only as a transition from held (you cannot discard a committed assertion — that's what retract is for). Append-only preserved: discarding writes a new version with state="discarded", the draft's existence retained, no hard delete.
  2. A discard_assertion operation, human-gated the same way commit is (the Operator dismisses; an agent cannot — mirror the commit_assertion actor logic). [CC confirms whether discarded needs a migration — likely yes if state is an enum/constraint; if so, it's an ordered migration on 0096. Confirm head sequencing.]
  3. discarded assertions are excluded from the record exactly as held is — never in Manifestation (committed-only), never in recall (committed-only), and not surfaced as a correction (unlike retracted). Corrections-preserved is untouched: it governs superseded committed facts; a discarded never-committed draft is non-record noise, honestly marked.

Tests: discard from held → discarded state, draft preserved, excluded from Manifestation/recall, not surfaced as a correction; discard from committed → rejected (must retract instead); agent cannot discard; the version history shows held→discarded.

Commit, suite green. This is the phase that may carry a migration — treat with the migration discipline (IMMUTABLE/constraint care, single head).

Phase 5 (frontend) — held-card Confirm + Dismiss

Seam: MemoryRoom.tsx HeldCard (lines ~48-77) — today Confirm-only (onConfirmcommitAssertion); renders title={note.content} (full text, readable for transcription catch).

Changes:

  1. Add a Dismiss action alongside Confirm. Dismiss → the new discard_assertion (Phase 4) → the held becomes discarded, cleanly, no retraction correction.
  2. Confirm stays as-is (commits via actor_from_person). With Phase 3, this is now the commit gate for personal memory too — so personal held drafts (which previously committed on the conversational "yes") now land here for the Operator's Confirm.
  3. Keep the full-text rendering (the transcription glance) — the Operator reads the captured text and chooses Confirm or Dismiss.

Build to the existing held-card visual language; Dismiss is a peer affordance to Confirm, clearly distinct (not destructive-styled alarm, but clearly "drop this").

Tests: held card shows Confirm + Dismiss; Confirm commits; Dismiss discards (→ discarded, card leaves the held list); full text visible.

Commit, suite green.

Phase 6 (frontend, optional / separable) — personal held discoverability

Observation (from grounding): personal held drafts only appear in the personal engagement's Memory room ("Your space"), not in the engagement (E0007) where the Operator was working when they spoke. So after capture, the Operator must navigate to "Your space" to find/commit them — they aren't surfaced at the point of capture.

Optional change: surface a personal-held count/badge or a held-review entry point so personal drafts are discoverable without hunting. Separable — not required for the core rework; flagged for the Operator to include or defer. Lean: defer to its own small piece unless the Operator wants it folded in.

dn-18 handling

No special code — handled naturally by Phases 4/5. Once the held-card has Confirm/Dismiss, the Operator opens "Your space" → the personal Memory room → finds dn-18 ("Car parked on Level 8.", held) → Confirm (the Operator wanted it recorded). Or Dismiss if no longer wanted. The decision (commit it) is the Operator's action via the new affordance.


Sequencing & discipline

Engine phases (1→4) land first, each commit-and-green, then frontend (5, optionally 6). Phase 4 may carry a migration (the discarded state) — apply migration discipline (single head off 0096, constraint/enum care). Inspect-first at each engine seam before editing. Halt-before-push throughout; per-push authorization. After merge: restart dev before testing (this CR changes prompts and runtime behaviour).

What this CR deliberately does NOT do


DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — CR — add-side rework: classification, confirm-back, commit-gate, held discard — v0.1 — 2026-06-27 F1 classifier recognizes command-shaped facts (prompt edit). Confirm-back dropped → "Got it… held" ack, which dissolves most of F2. Personal-memory commit moved onto the human held-card gate (seed-aligned). New discarded terminal state (append-only-faithful, distinct from retracted) + held-card Dismiss. dn-18 handled via the new affordances. Engine-first, then frontend; Phase 4 may carry a migration.