Version. 0.1
Date. 2026-07-01
Status. Drafted, not staged for build. Number 131 confirmed free at staging (highest prior: CR-2026-130). Base: engine main @ 2abcfd1 (post-CR-2026-129); OL main @ d0cae54 (post-CR-2026-127 merge). Independent of CR-2026-130 (open on branch cr-2026-130-upload-content-reaches-memory, not yet pushed) — this CR does not touch CR-130's scope and CR-130's push decision does not depend on it.
Grounded on: seed v0_12 (§Memory line 43 — intake commitment, provenance-carrying contributions), manifest v0_72, CC diagnosis 2026-07-01 (read-only, confirmed against the dev DB for engagement E0060).
Uploading a file works — the file is stored, described, and drafted as a held assertion. That part is correct and verified (Memory panel is intact; View source works).
But the conversation — the "Attached 1 file" bubble and the Companion's "Here's what I found" reply — disappears on refresh or when the Operator navigates away and back. The exchange only exists in the browser's temporary state; the engine never saves it. Normal typed/spoken messages don't have this problem because the engine saves those turns immediately.
Upload-triggered turns are composed entirely client-side (useEngagementUpload.ts) and pushed into local React state (useConversation.ts's optimistic tail — appendOperatorTurn / appendCompanionTurn, both local-<uuid> entries that are never sent to the engine). No engine route for uploads (src/loomworks/uploads/, api/routers/uploads.py) ever calls record_turn. Converse turns persist because orchestration/routers/converse.py calls record_turn for both the Operator and Companion turn (~line 1285, ~line 1319); uploads have no equivalent.
Confirmed empirically against the dev DB: zero conversation_turns rows matching any upload exchange for engagement E0060 (a83c3ff5-a842-4b3f-aea5-aa60a1adc368), though the resulting held assertions (upload_event_received objects in current_memory_objects) are present and correct — 4 uploads recorded, matching the Operator's confirmed-intact Memory panel.
Scope: affects every upload surface — in-engagement desktop (ConversationPane), mobile (MobileSurface), and /chat (ChatView). Predates CR-2026-130 (the pattern was extracted verbatim from the Phase-60-era handleUpload); not caused or worsened by CR-130's changes.
The seed's Memory commitment (accumulate with provenance; lineage recorded automatically for typed/spoken/extracted/described contributions) is satisfied — the held assertion carries correct provenance regardless of this bug. The seed does not speak to conversation-turn persistence specifically. No seed conflict. This is an engineering-consistency fix: upload turns should persist the same way converse turns do.
Engine-side: the upload route(s) record the turn pair — the Operator's "attached" turn and the Companion's descriptive reply — via the existing record_turn, the same mechanism converse.py uses. No new persistence mechanism; reuse the one that already works.
Frontend: once the engine persists these turns, the history fetch on load will include them naturally (no separate frontend fix needed — fetchConversationHistory already reads the turns table; there is no filter excluding upload-type turns, there is just nothing there to fetch today).
Independent of CR-2026-130 (confirmed not implicated — CR-130's push can proceed on its own merits). This is its own CR, next in the queue after Room 1 items are resolved, or sooner if the Operator wants conversation continuity fixed before further upload use.
conversation_turns immediately after the upload completes.
An uploaded file's conversation exchange (Operator's "attached" turn, Companion's descriptive reply) persists via record_turn and survives a hard refresh and navigate-away-and-back, on every upload surface (desktop, mobile, /chat). Verified at runtime: upload a real file, hard refresh, confirm both turns are still rendered. Suite green.
loomworks-record/change-requests/. — Done, this document (131).record_turn from the upload route(s), mirroring converse.py's pattern.