DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path change-requests/cr-2026-131-upload-turn-persistence-v0_1.html

CR-2026-131 — Upload Turn Persistence

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).

Plain-language summary

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.

Root cause (CC diagnosis, confirmed against DB)

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.

Seed check

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.

The fix

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).

What must NOT change

Sequencing

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.

Tests

Pass condition

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.

Action path

  1. Operator or CC confirms next-free CR number in loomworks-record/change-requests/. — Done, this document (131).
  2. Stage this document (md + HTML companion) at that number. — Done, this commit.
  3. Step-0 inspect-first against current main (both repos) before build.
  4. Build: engine seam calling record_turn from the upload route(s), mirroring converse.py's pattern.
  5. Verify live (upload → refresh → turns persist).
DUNIN7 — Done In Seven LLC — Miami, Florida · Loomworks — CR-2026-131 — Upload Turn Persistence — v0.1 — 2026-07-01