DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path change-requests/cr-reply-source-legibility-v0_1.md

Change Request — Reply source legibility (structural source-marking) — v0.1

Document: cr-reply-source-legibility-v0_1 Version: v0.1 Date: 2026-06-28 Type: Phase change request — engine + Operator Layer (loomworks-engine + loomworks). Small, additive: one new response field + its population + its render. Consumer: Claude Code (executor). Branch base: engine main @ 5ccc3d7; OL feat-companion-surface @ 9667d60. Origin: Decided this session — the source model, legibility-first. The Companion answers from several bodies (engagement Memory, personal Memory, the model's own knowledge) but does not tell the Operator where an answer came from — and when it does ("you mentioned it's sunny, I don't have live data"), that is the LLM choosing to narrate in prose, free to omit. This makes the source structural. Grounding: loomworks-seed-model-grounding-inspection-v0_1's sibling source-model strand + the CR-drafting grounding pass (the recall reply path, ConverseResponse, the general_conversation dispatch branch, the persona prose, CompanionBubble — engine @ 5ccc3d7, OL @ 9667d60).


Plain-language summary

When the Companion answers you, it draws from a few different places — your engagement's record, your personal memory, or the model's own general knowledge — and blends them into one reply. Right now you can't tell which place an answer came from. Sometimes the Companion says so in words ("you mentioned it's sunny"), but that's the model choosing to mention it — it's free to leave it out, and sometimes does. This change makes the source a real, visible part of every reply: a small marker that says from your record, or from your personal memory, or the model's own knowledge — shown by the surface, not left to the model's prose. It starts with the two clearest cases (recall from your notes, and the model answering from itself) and lays the foundation for marking web answers later.


Why this is the right first piece (the legibility-first decision)

Three gaps make up the full source model: (1) structural source-marking on replies (this CR), (2) a content-origin provenance type (lets a non-Memory answer become a remembered fact — later), (3) web egress (a new capability — last, built onto this foundation). This CR is gap 1 — the cheapest and highest-value, because the source data already exists and is thrown away, and because it relieves the real pain (the Operator couldn't tell a recalled note from a live-data claim) without building any new capability. It is the same instinct as the add-side truthfulness fix: don't trust the LLM's prose for something that should be a guarantee — structure it in code.


What's true today (grounded)

The source data exists, structured, and is discarded before the surface:

The surface has a place for it but no field:


Seed alignment

Directly on the seed's spine. The seed's trust axis is tie-to-source — a contribution's trust is its tie to where it came from, marked non-suppressibly (AI-origin in particular). Today the Companion's answer-source is suppressible (the model can omit it). Making it structural is the seed's own principle applied to the reply stream: source is shown, not narrated-if-the-model-feels-like-it. It also extends the add-side truthfulness standing note ("the Companion must not claim what the system did not do") — here, the Companion must show what it actually drew on, structurally.


Decisions (settled, grounded)

  1. A new structural source field on ConverseResponse, populated from the dispatch outcome, mirrored to the client, rendered in CompanionBubble. Not LLM prose.
  2. Start with the two clearest sources (this CR's scope):

Other intents (tune_setting, create, delegation, etc.) are not answer-from-a-source in the same way — they get no source marker (null), which is correct (nothing to attribute). Confirm the null-for-non-recall default at Step 0.

  1. Distinct from actor_kind labeling. This is the reply's answer-source, not inbound authorship. New field, reuses the render slot's treatment.
  2. The marker is the surface's, not the model's. Populated server-side from the dispatch branch (deterministic), not parsed from the reply text. Out-of-band — the model still speaks naturally; the marker is structural.
  3. Plain-terms labels. "from your record" / "from your personal memory" / "the model's own knowledge" — Operator vocabulary, not codebase intent names. Exact wording confirmed at CR (lean: those three).

Scope boundary — what this does NOT do


The build — steps

Per-step commits, suite green at each, halt-before-push. Engine + OL.

Step 0 — inspect before building (no code)

Confirm against engine 5ccc3d7 / OL 9667d60:

  1. The exact converse-pipeline threading seam: dispatch → assemble_promptgenerate_responsetranslate_converse_response. Where is the dispatched intent (or the RouteResult/None outcome) known at the point translate_converse_response builds the response? The source value must be derivable there. (CC offered to trace this — do it now; it's the load-bearing seam.)
  2. Confirm the ~11 ConverseResponse construction sites and which need the new field populated vs. which default to null (lean: only the recall + general_conversation paths set a non-null source; all others null).
  3. Confirm CompanionBubble's label line (ConversationTranscript.tsx:73-75) as the render seat and the actor_kind precedent's treatment (so the marker reads consistent with existing source labeling without reusing the field).
  4. Confirm the client ConverseResponse → display-turn flow: how a ConverseResponse becomes a DisplayTurn/Companion turn the bubble renders, so the new field threads to the bubble.

Report findings, then build.

Step 1 — Engine: the source field + population

Suite expectation: add tests — a recall reply carries source=engagement_record; a personal recall carries personal_memory; a general-conversation reply carries model_knowledge; a tune_setting/other reply carries null. Existing tests unaffected (additive optional field).

Step 2 — OL: client type + render

Suite expectation: component test — a Companion turn with source=engagement_record renders "from your record"; model_knowledge renders "the model's own knowledge"; null renders no marker. Both desktop and mobile inherit it (shared component). Existing transcript tests unaffected.

Note: because the marker is out-of-band and additive, the existing prose behavior is unchanged — the Companion still speaks naturally; the marker is structural. The persona prose ("do not narrate checking personal memory") is now correct alongside the marker — the model doesn't narrate, the surface shows.


Acceptance

What this sets up (named, not built)


DUNIN7 — Done In Seven LLC — Miami, Florida Change Request — Reply source legibility (structural source-marking) — v0.1 — 2026-06-28 Adds a structural source field to ConverseResponse (engagement_record / personal_memory / model_knowledge / null), populated server-side from the dispatch outcome (recall already holds the structured source and throws it away; LLM-knowledge is the general_conversation→None branch), rendered in the shared CompanionBubble for desktop + mobile. The surface shows the source; the model doesn't narrate it. Legibility-first — gap 1 of the source model. Engine 5ccc3d7 + OL 9667d60.