DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path change-requests/cr-create-entry-cold-open-v0_1.md

Change Request — Create-entry cold-open greeting (engine) — v0.1

Document: cr-create-entry-cold-open-v0_1 Version: v0.1 Date: 2026-06-28 Type: Phase change request — engine only (loomworks-engine). Unblocks the already-built door-1 frontend branch (feat-create-conversation-opening-turn, OL fab220d, committed + unpushed). The two ship together. Consumer: Claude Code (executor). Branch base: engine main @ 5ccc3d7. Origin: The door-1 live check found that the create_engagement_entry greeting 400s on an empty openergenerate_response unconditionally appends {"role":"user","content": operator_message}, and the Anthropic Messages API rejects an empty/system-only call. The greeting machinery works in response to a non-empty opener; there is no cold-open path. The frontend correctly fires an empty opening converse; the engine cannot answer it. This adds the cold-open. Grounding: the engine cold-open Step-0 grounding (the generate_response message-array construction, the Anthropic API constraint, the orient server-compose-and-bypass precedent, the turn-recording site, the _scope_creation_history slice — engine 5ccc3d7, confirmed against the live engine + real Anthropic where noted).


Plain-language summary

When the create page opens, the front end asks the engine to greet the Operator before they've typed anything. The engine can't do that today: its way of producing a reply always sends the Operator's message to the AI model, and an empty message is rejected. So the create page currently shows an error fallback instead of a greeting. This fixes it the simplest correct way: the create greeting becomes a fixed opening line the engine composes directly — no AI call, no empty message, no error possible — using the exact same mechanism an existing feature (orient) already uses to compose a message without calling the model. It also stops the engine from recording the empty Operator message as a turn (which was corrupting later turns). The result: open the create page, get a clean greeting, and the conversation begins for real on your first answer.


What's true today (grounded, exists-vs-works distinguished)

Decision — Option T (templated greeting, bypass the LLM like orient)

Chosen: a templated greeting, server-composed, LLM-bypassed — the orient pattern — plus skip recording the empty operator turn.

Considered and set aside: Option S (call the LLM with a synthetic non-persisted user framing — "[The Operator just opened the create surface — greet them]"). Viable and provenance-clean (the synthetic text is never persisted), but it reintroduces the LLM round-trip (and thus the fallback-error risk that just broke) for no benefit at the cold open — there is no Operator input yet for the LLM's persona-awareness to work with. The LLM would be greeting into the void.

Why T is not just simpler but more correct: the create greeting is the opening of a structured flow — the same moment every time, with no context yet (the Operator hasn't spoken). A fixed greeting is honest about what that moment is: a deterministic door-opening, not a conversation. The LLM's warmth has nothing to work with until the Operator's first real answer — which is exactly where the conversation (and the LLM) begins, on turn two. Option S tries to make the model speak first about nothing; Option T accepts the first turn is a fixed invitation and the model joins when there's something to respond to. T also eliminates the empty-user-turn failure mode by construction (no LLM call → no user turn → no 400 → cannot hit the fallback), and reuses a proven in-repo mechanism.

Scope boundary — what this does NOT do


The build — steps

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

Step 0 — inspect before building (no code)

Confirm against engine 5ccc3d7:

  1. Branch base.
  2. The orient bypass shape (converse.py:997) — exactly how orient server-composes companion_message and bypasses assemble_prompt/generate_response, then rejoins turn-recording. The cold-open mirrors this; confirm the precise rejoin point and that the bypass is clean.
  3. The cold-open trigger condition — how the handler detects "create-entry cold open": intent == create_engagement_entry AND body.message empty (the frontend sends intent_hint="create_engagement_entry" + message: ""). Confirm the dispatch point where this branch attaches (before the responder call, parallel to orient).
  4. The turn-recording skip (converse.py:1172-1196) — exactly where to skip the operator-turn write when body.message is empty while still writing the companion greeting. Confirm the companion greeting still forms the create_engagement_entry boundary _scope_creation_history expects.
  5. The greeting text — where the templated greeting string lives (a constant, a strings module, or interpolating companion_name). Lean: a fixed greeting that asks the first elicitation question ("Let's set up a new engagement. What's the work, and who's it for?"), optionally interpolating the companion name. Plain-terms.

Report findings, then build.

Step 1 — The cold-open branch (server-composed greeting, LLM bypassed)

At the dispatch point (Step-0.3), add a cold-open branch: when intent == create_engagement_entry and the opener message is empty, server-compose the greeting (the orient pattern — set companion_message to the templated greeting, bypass assemble_prompt/generate_response). No LLM call.

Suite expectation: an empty-message create_engagement_entry converse returns the templated greeting as companion_message (NOT the LLM fallback, NOT a 400); a non-empty create_engagement_entry still routes to the responder + entry template (unchanged).

Step 2 — Skip recording the empty operator turn

At the turn-recording site (Step-0.4): when the opener message is empty, skip the operator-turn write; still write the companion greeting turn (forming the entry boundary).

Suite expectation: after a cold-open, the turn log holds the companion greeting and no empty operator turn; subsequent converse turns are unpoisoned (no 400 cascade — the live check's failure mode); the active-turn history scoping still finds its [assistant entry-greeting] boundary.

Step 3 — Live verification (the door-1 discipline — distinguish exists from works)

Against the running engine + real Anthropic (not mocks — this is the lesson the door-1 live check taught):

Acceptance is this live sequence passing — not a green suite alone.


Acceptance

What this unblocks / coordinates


DUNIN7 — Done In Seven LLC — Miami, Florida Change Request — Create-entry cold-open greeting (engine) — v0.1 — 2026-06-28 The create_engagement_entry greeting 400s on an empty opener (generate_response always appends a user turn; the Anthropic API rejects empty content — a system-only call is impossible). Fix: a templated greeting server-composed via the proven orient bypass pattern (no LLM call → no empty user turn → no fallback), plus skip recording the empty operator turn (the data-integrity half — empty turns poisoned subsequent converse). Option T over Option S (LLM-with-synthetic-framing) because the cold open has no context for the LLM's warmth — a fixed invitation is more correct, and the LLM joins on the first real answer. Acceptance is a live entry→active→commit sequence (the door-1 lesson: exists is not works). Unblocks the door-1 frontend branch; the two ship together. Base engine 5ccc3d7.