Version. 0.1
Date. 2026-06-29
Repo. loomworks-engine (+ OL loomworks for the conversation tray UI)
Branch base. main @ d32c99b (rebase onto post-CR-2026-120 head before starting)
Author. Claude.ai. Executor. Claude Code (DUNIN7-M4). Operator. Marvin Percival.
Room. Memory — Pipeline-walk Finding 2 of 3.
Depends on. CR-2026-120 (actor-kind read contract) merged + verified — held items must render before a tray grammar over them is usable.
Today you can speak a note and it becomes a held draft, but you cannot reliably save, change, or remove it by talking normally. The only working save phrase is "commit held N" — which no one would think to say — and there is no way at all to list your held items in conversation, change one, or discard one by reference. "Save" and "yes" route to a queue that never sees your draft, so nothing commits and the Companion sometimes says "Logged" when nothing was logged.
This CR builds one consistent grammar over an addressable held tray:
One grammar: verb + number. You name the target; the system acts on exactly that. No magic phrases, no guessing which draft you mean, no false success.
The grounding walk found three-quarters of this already in the substrate:
display_number — the stable, short, per-engagement handle. Assigned at held-creation (not commit), preserved across revise/discard/commit/retract. E0060's held items are 1–5 now. This is the "stable IDs that don't renumber when one leaves" the Operator asked for — already built.commit N / commit all — fully wired, resolving by display_number, committing as the human. One of the four motions, working today. It is the reference implementation the other motions mirror.revise_assertion and discard_assertion — both exist, both append-only/recorded (discard writes a discarded version + event, never hard-deletes; revise supersedes with wasRevisionOf, prior preserved). They lack only a by-number conversational route.HeldCard (Memory room) and HeldNoteCard (Dashboard) already render "Held N" lists. The conversation tray copies this pattern; it is not designed from nothing.change N on a committed fact supersedes (new version, prior stays in record, wasRevisionOf link). Discard is recorded, not erased. Aligned — the CR enforces these, does not bypass them._route_commit_assertion's by_number map (router.py:456-463) into one helper: held-reference (number | "all") → assertion(s), keyed on display_number. All four motions call it. Single source of truth for number→assertion resolution.show_held — list held assertions for the engagement with their display_numbers, returned as structured data (not prose) so the conversation surface can render an addressable tray. Add to IntentLabel + classifier prompt.discard_held ("discard N" / "drop N") — resolve N → assertion, call existing discard_assertion (append-only). Human-gated (companion cannot discard — CR-2026-120 closed that gate).change_assertion ("change N to X") — resolve N → assertion, then branch on state:revise_assertion to edit the held draft in place. Frictionless (nothing durable changing).revise_assertion (new version, wasRevisionOf set, prior preserved) with a confirm step: the Companion surfaces "this corrects a saved fact — 'X' becomes 'Y' — the original stays in the record. Apply?" and only proceeds on Operator yes. Consequential — durable memory changing.approve_draft→card-queue path is routed around, not repaired; mark it deprecated-by-this-change (do not delete in this CR).show_held (or a held-context reply) returns structured held data, render the numbered tray inside the Companion conversation stream, copying the HeldCard/HeldNoteCard pattern. ConverseResponse must carry a structured held-list array (currently companion_message: string only — extend it). The "What's held for me?" chip routes here instead of to a prose bubble.approve_draft card queue — routed around here, removed in a later cleanup.compositions.py:73 companion→contributor relabel (CR-2026-120 residue).Rebase onto the post-CR-2026-120 head first. Then CC reads and confirms at current HEAD, halting on any divergence:
_route_commit_assertion / _parse_held_reference (router.py:298-340, 380, 456-468) — the resolver to generalize and the commit reference impl.revise_assertion (assertions.py:312) and discard_assertion (assertions.py:458) signatures + that both are append-only and human-gated.display_number assignment-at-add (assertions.py:124-163) and preservation across lifecycle — confirm E0060 held items still 1–5.IntentLabel enum + intent_classifier.md — confirm show_held/change/discard still absent, commit_assertion present with held_reference.ConverseResponse shape (OL lib/types.ts:142-160) and the HeldCard/HeldNoteCard components to copy.Report the chosen home for the shared resolver and the confirm-step mechanism before editing.
Stage by explicit path. Per-step commits. Push is separately authorized — do not push until the Operator says so.
Step 1 — Shared resolver. Extract commit's by_number logic into one held-reference resolver. Repoint commit_assertion to use it (no behavior change — regression-locked by existing commit tests). Commit. Suite green.
Step 2 — show_held intent + structured response. Add show_held to IntentLabel + classifier; handler returns held assertions with display_number as structured data; extend ConverseResponse to carry the held-list array. Commit. Suite green.
Step 3 — discard_held intent. Add intent + handler → resolver → discard_assertion. Human-gated. Truthful confirmation. Commit. Suite green.
Step 4 — change_assertion intent, held branch. Add intent + handler → resolver → revise_assertion for held items, in place, frictionless. Commit. Suite green.
Step 5 — change_assertion, committed branch (supersession + confirm). Extend the handler: committed target → confirm-step → supersede on yes (prior preserved, wasRevisionOf set). Reuse the existing two-turn confirm mechanism. Commit. Suite green.
Step 6 — Natural-language routing. Classifier routes "save N"/"save that"/"ok save" → commit; bare affirmative in held-tray context → commit addressed item. Truthful confirmations across all motions. Mark approve_draft deprecated-by-this-change (comment, no deletion). Commit. Suite green.
Step 7 — OL conversation tray. Render the structured held list as a numbered tray in the Companion stream (copy HeldCard pattern); route the "What's held for me?" chip here. Frontend tests. Commit. Suite green.
Step 8 — Tests. Engine: each intent resolves by display_number; held-change edits in place; committed-change supersedes-with-confirm and preserves prior; discard is recorded; natural save phrasing routes to commit; confirmations gated on real writes (no false success). Commit. Suite green.
HALT. Report terminal SHA + suite count. Await push authorization.
Restart dev after merge. Then:
E0089 (clean loop, the headline test):
E0060 (committed-revision):
Pass condition: the three-line example works end to end on E0089; commit/discard/change-held are addressable and truthful; committed-revision supersedes-with-confirm and preserves the prior; natural save phrasing reaches commit. Only then does Room 1 advance to Finding 3.
revise_assertion append-only path (proven in the Room-1b walk), never an overwrite.commit is regression-sensitive — Step 1 refactors its resolver; existing commit tests must stay green to prove no behavior change.