Version. 0.1
Date. 2026-05-01
Author. Marvin Percival (DUNIN7), prepared via Claude.
Target. /Users/dunin7/loomworks (substrate) and /Users/dunin7/loomworks-ui (frontend) on DUNIN7-M4 (MacMini).
Baseline reference. Substrate: tag phase-29-assertion-lifecycle-ui. 1181 tests, 2 skips. Frontend: tag phase-29-assertion-lifecycle-ui. 151 component tests, 6 Playwright (1 skip). Lint + tsc + build + test clean. Fourteen surfaces.
Priority. Standard (sequential phase).
Confidential. Internal DUNIN7.
Supersedes. No prior Phase 31 CR — v0.1 is the first.
Companion to. loomworks-phase-31-scoping-note-v0_4.md (all design decisions settled); loomworks-product-identity-standing-note-v0_1.md (governing identity); current-status-manifest-v0_22.md; loomworks-marketing-creation-flow-content-v0_3.md (Discovery conversation and seed content); phase-25-cr-amendment-foray-attestation-v0_1.md (FORAY hooks pattern); phase-16-cr-addendum-api-key-store-v0_1.md (per-engagement key store pattern); phase-16-cr-memory-contribution-ui-v0_1.md (voice contribution pattern).
Status. Pre-execution CR. Ready for Operator review and approval.
Phase 31 refactors the engagement creation surface from a form into a conversation. The current creation flow (Phase 25) presents the Operator with a six-field seed form that assumes knowledge the Operator does not have. Phase 31 replaces it with a conversational interface — the first surface built under the companion identity.
The work has two substantive areas.
Area A — Substrate: conversational seed synthesis. A server-managed conversation endpoint that accepts the Operator's natural-language responses, determines the next question, and iteratively produces a one-page seed brief. Conversation turns are stored as events on the candidate engagement. A system-level key store holds a Loomworks-managed key for credits-based usage. The conversation prompt is a versioned substrate asset. All events carry FORAY hooks per the Phase 25 attestation pattern.
Area B — Frontend: two creation surfaces. Surface A is the conversational path (default): a vertical exchange flow with text and voice input, a live seed document on a companion tab, and a completion thermometer celebrating progress. Surface B is the enhanced form path (proficient Operators): the existing Phase 25 form with simplified language and expandable help on each field. Both surfaces share the live seed document and thermometer. Both produce the same output: a one-page seed brief that enters the existing induction loop.
Phase 31 is the first encounter with the companion. Loomworks is the Operator's companion on the way to idea resolution as a completed reality. Every element — the opening question, the acknowledgments, the live seed, the thermometer, the voice option, the tone — serves the companion identity. The environment is never clinical. Every interaction leaves the Operator feeling accompanied and that the journey advanced.
Phase 31 is complete when the acceptance gate passes and the Operator has confirmed Checkpoint C. Both repos are tagged phase-31-conversational-engagement-creation.
Phase 31 is grounded in four layers, consulted in order. Claims in this CR cite the layer they rest on.
Layer 1 — Product identity standing note. loomworks-product-identity-standing-note-v0_1.md. The companion identity governs this phase. Every design decision is tested against: does this element make the Operator feel they are working alongside a capable, attentive presence that understands what they are trying to do and cares about getting them there? Surfaces that are functionally correct but emotionally empty fail the companion test. The companion is capable but does not announce its capabilities. The companion reads you, not the other way around. The companion does not rush you. The companion celebrates what you build.
Layer 2 — Marketing Discovery conversation. loomworks-marketing-creation-flow-content-v0_3.md. Five corrections and five crystallizations from the Discovery process. The form is the wrong entry point (Correction 3). The opening question is "What would you like to be working on?" (Correction 1). The AI is invisible (Correction 5). Three proficiency paths, proficiency discerned not declared (Crystallization 5). Seven questions produced the seed — the template for the conversational path.
Layer 3 — Methodology document v0.19. "The seed is a brief, not a blueprint." "How engagements begin." The seed-induction loop. Seed-required elements R-A5 through R-A11 in Spec v0.10 Sec-A.2.
Layer 4 — Existing substrate and patterns. Phase 25 engagement creation endpoints (being refactored). Phase 16 per-engagement key store (Fernet encryption, CRUD endpoints). Phase 16 voice contribution (WebM recording, transcription skill). Phase 25 FORAY amendment (content hashing, _foray namespace, attestation seam). Phase 28 expandable explanation toggles (reused for form field help).
phase-29-assertion-lifecycle-ui. 1181 tests, 2 skips.phase-29-assertion-lifecycle-ui. 151 component tests, 6 Playwright (1 skip). Lint + tsc + build + test clean. Fourteen surfaces.
| Infrastructure | Source | Used by Phase 31 |
|----------------|--------|-------------------|
| Candidate engagement creation | Phase 25 POST /engagements | Unchanged. Creates candidate before conversation. |
| Seed submission | Phase 25 POST /engagements/{eid}/seed | Unchanged. Receives the synthesized seed after conversation. |
| Induction loop | Phase 25 POST /engagements/{eid}/seed/induct | Unchanged. Runs after seed submission. |
| Findings and commit | Phase 25 endpoints | Unchanged. Review, amend, commit with attestation seam. |
| Per-engagement key store | Phase 16 addendum | Pattern reused for system-level key store. |
| File upload | Phase 16 POST /engagements/{eid}/files | Unchanged. Audio file upload for voice input. |
| Transcription skill | Phase 16 src/loomworks/skills/transcription.py | Unchanged. WebM → text via Whisper API. |
| Content hashing | Phase 25 FORAY amendment | Unchanged. content_hash on events. |
| _foray namespace | Phase 25 FORAY amendment | Extended to conversation turn events. |
| Explanation toggles | Phase 28 | Pattern reused for form field help. |
phase-29-assertion-lifecycle-ui tag.uv run pytest -v green on substrate (1181 passed, 2 skips).LOOMWORKS_SECRET_KEY environment variable is set (Fernet encryption)./engagements/new.The creation conversation requires an LLM key to power the conversational engine. The primary path is an Operator-provided key. The fallback is Loomworks credits — usage against a Loomworks-held key. Phase 31 builds the system-level key store that holds this Loomworks key.
A new Alembic migration creates a general-purpose system configuration table:
| Column | Type | Constraint |
|--------|------|------------|
| id | UUID | PRIMARY KEY |
| config_key | VARCHAR(128) | NOT NULL, UNIQUE |
| encrypted_value | TEXT | NOT NULL |
| label | VARCHAR(256) | NULL — optional display label |
| created_at | TIMESTAMPTZ | NOT NULL DEFAULT now() |
| updated_at | TIMESTAMPTZ | NOT NULL DEFAULT now() |
Encryption uses Fernet with LOOMWORKS_SECRET_KEY, same scheme as the per-engagement key store (Phase 16 addendum).
Known config keys for Phase 31:
| config_key | Used by | Required for |
|---------------|---------|-------------|
| loomworks_llm_key | Conversational seed synthesis endpoint | Credits-path creation conversations |
# src/loomworks/system_config/store.py
async def get_system_config(
*, config_key: str, db: AsyncSession
) -> SystemConfigRow | None:
"""Retrieve a system configuration value. Returns None if not set."""
...
async def set_system_config(
*, config_key: str, value: str,
label: str | None, db: AsyncSession
) -> SystemConfigRow:
"""Store (upsert) a system configuration value. Encrypts before storage."""
...
async def delete_system_config(
*, config_key: str, db: AsyncSession
) -> bool:
"""Remove a configuration value. Returns True if a row was deleted."""
...
These mirror the per-engagement key store utilities from Phase 16 §3.2.
The conversation endpoint resolves the LLM key through a two-step fallback:
service_name = "anthropic". If present, use it.system_config for config_key = "loomworks_llm_key". If present, use it. This is the credits path.If neither key is available, the endpoint returns 503 with a message indicating that an API key is required.
Phase 31 does not build the metering logic. It builds the hook. Each conversation turn that uses the system-level key (credits path) records a credits_consumed field on the conversation turn event:
{
"credits_consumed": {
"key_source": "system",
"model": "claude-sonnet-4-20250514",
"input_tokens": <int>,
"output_tokens": <int>
}
}
When the Operator provides their own key, this field is absent. The metering infrastructure (aggregation, billing, limits) is future scope. The event-level record is sufficient for future metering to operate on.
POST /engagements/{eid}/seed/converse
Auth: person-session (seed drafter required)
Request body:
class ConverseTurnRequest(BaseModel):
message: str # The Operator's response (typed or transcribed)
source_mode: Literal["text", "voice"] = "text" # Provenance
source_file_id: UUID | None = None # If voice, the uploaded audio file
Response body:
class ConverseTurnResponse(BaseModel):
companion_message: str # The companion's reply (acknowledgment + next question)
seed_document: str # The current synthesized seed as prose (markdown)
completion_percentage: int # 0–100, drives the thermometer
status: Literal["in_progress", "ready_for_review"] # Whether the seed has sufficient coverage
turn_number: int # Sequential turn number in this conversation
Conversation turns are stored as events on the candidate engagement. Each converse call:
operator_turn event with the Operator's message.companion_turn event with the companion's response, the synthesized seed, completion percentage, and status.Event kinds:
| Event kind | Payload | Actor |
|------------|---------|-------|
| operator_turn | { message, source_mode, source_file_id, turn_number } | Person (seed drafter) |
| companion_turn | { message, seed_document, completion_percentage, status, turn_number, model } | System |
| conversation_started | { key_source: "operator" \| "system" } | System |
The conversation history is fully server-side. The frontend sends only the latest message. This ensures the conversation survives page refresh, is auditable, and conversation turns are FORAY-anchorable.
GET /engagements/{eid}/seed/conversation
Auth: person-session (seed drafter required)
Response:
class ConversationResponse(BaseModel):
turns: list[ConversationTurn]
seed_document: str | None # Latest synthesized seed, or None if no turns
completion_percentage: int
status: Literal["not_started", "in_progress", "ready_for_review"]
class ConversationTurn(BaseModel):
role: Literal["operator", "companion"]
message: str
turn_number: int
source_mode: str | None # Only on operator turns
timestamp: datetime
This endpoint reconstructs the conversation from the event log. The frontend calls it on page load to restore conversation state after refresh or return.
When the conversation reaches ready_for_review and the Operator confirms, the frontend calls the existing POST /engagements/{eid}/seed endpoint with the synthesized seed content. The seed is structured identically to a form-submitted seed — six fields (what the work is, who consumes the work, voice, constraints, success conditions, additional assertions). The conversational engine extracts and structures the content from the Operator's natural-language responses.
The existing induction loop, findings presentation, amendment flow, and commit ceremony apply unchanged.
The conversation prompt instructs the LLM to return a structured response. The endpoint parses the LLM's output to extract:
companion_message: the natural-language reply to show the Operator.seed_fields: a structured object with the six seed field values, updated progressively.completion_percentage: an integer 0–100 reflecting seed coverage.status: whether the seed has sufficient coverage for review.
The prompt instructs the LLM to return these in a JSON block within its response. The endpoint strips the JSON block before storing companion_message and renders seed_fields into the prose seed document.
CC determines the exact parsing strategy. The requirement is: the LLM response is a single API call, and the endpoint reliably extracts structured data and natural language from it.
The endpoint renders the structured seed fields into a prose document — the one-pager. The document is markdown, readable without knowing what a seed is. The rendering follows the "seed is a brief, not a blueprint" principle. Field labels do not appear. The document reads as a narrative:
# [Engagement title, derived from Field 1]
[What the work is — Field 1, rendered as a paragraph describing the work.]
[Who this is for — Field 2, woven into the narrative.]
[How it should sound — Field 3, described as the voice and tone.]
[What it should not do — Field 4, presented as boundaries.]
[What success looks like — Field 5, framed as the outcome.]
[Additional context — Field 6 entries, integrated naturally.]
The exact prose rendering is produced by the LLM as part of the conversation prompt. The prompt instructs the LLM to produce both the structured fields (for the seed submission) and the prose one-pager (for the Operator to read).
The conversation prompt is a versioned substrate asset stored in src/loomworks/prompts/creation_conversation.py as a Python string template. It is not hardcoded in the endpoint handler. Version tracking is embedded in the template itself (a PROMPT_VERSION constant).
PROMPT_VERSION = "1.0"
CREATION_CONVERSATION_PROMPT = """
{template_content}
"""
The system prompt establishes the companion voice and instructs the LLM on conversation structure. The full template:
You are Loomworks — a companion helping someone turn an idea into a
completed reality. You are not an AI assistant. You are not a chatbot.
You are a knowledgeable, patient presence that understands what this
person is trying to do and cares about getting them there.
Your job in this conversation is to help the person describe what they
want to build. You ask questions, listen to their answers, and produce
a brief — a one-page document that captures what they told you. The
brief is for them. It should read clearly, use their language, and
reflect their intent.
## How to conduct the conversation
Ask one question at a time. After each answer, acknowledge what the
person said — show them you understood. Then ask the next question.
Your acknowledgments should reflect genuine understanding, not
mechanical confirmation. The person should feel heard.
The conversation follows this shape, but you adapt it. If the person
answers multiple questions at once, do not re-ask what they already
covered. If their answer suggests they need more context, provide it
naturally — not as a lecture, never using technical terminology. If
their answers are confident and detailed, step back and move quickly.
### The questions (guidance, not a script)
1. "What would you like to be working on?"
Seeds: what the work is. This is the opening question. Meet the
person at aspiration. Let them describe the work in their own words.
2. "Who is this for?"
Seeds: who consumes the work. Who will read, use, or benefit from
what they build?
3. "How should this talk to them?"
Seeds: voice. What tone, style, or manner should the work use when
it reaches its audience?
4. "Is there anything this should not do?"
Seeds: constraints. Boundaries, limitations, things to avoid.
5. "What does success look like?"
Seeds: success conditions. How will they know the work accomplished
what they intended?
6. Follow-up questions as needed.
Seeds: additional context. Anything the person mentioned that adds
depth — context, examples, special considerations.
7. "Is there anything else this work needs to account for?"
Seeds: remaining context. A catch-all before closing.
You do not need to ask all seven. You do not need to ask them in this
order. The person's responses guide the conversation. Some people
answer three questions in their first response. Some need follow-ups
within a single question. Read them.
### Adjusting for proficiency
If the person's responses are brief, uncertain, or exploratory, provide
more context with your questions. Help them think through what they
want. Weave in gentle explanations of why each question matters.
If the person's responses are detailed, confident, and specific, move
quickly. Acknowledge concisely and ask the next question. Do not
over-explain to someone who already knows what they want.
This adjustment is continuous. The same person may be uncertain about
their audience but very clear about their constraints. Adapt per
question, not per person.
### Adjusting for domain
The nature of the work determines the register of the conversation.
Read the domain from the person's first response and match it.
A person describing a knitting pattern tracker gets a warm,
exploratory conversation. A person describing a clinical research
protocol gets a precise, attentive conversation that recognizes
the weight of regulatory and methodological language. A person
building a corporate website gets a practical, design-aware
conversation. A person building a mortgage transaction system gets
a conversation that understands that "constraints" in that domain
means compliance obligations, not stylistic preferences.
These are independent from proficiency. A first-time Operator
building a derivatives settlement platform still needs full
guidance through the creation process — but the companion meets
them in the language of their domain. A proficient Operator
describing a weekend craft project still moves quickly — but the
companion does not treat the work with unearned gravity.
Mirror the person's domain language back to them. If they say
"stakeholders," say stakeholders. If they say "the people who
will read this," say that. If the domain has precise terminology
the person uses naturally, use it. If it does not, do not
introduce any. The companion speaks the language the person
brought with them.
## What you produce
After each of the person's responses, you produce three things:
1. **Your reply** — an acknowledgment that shows understanding, followed
by the next question (or, if all fields are covered, a closing
message inviting review).
2. **The seed fields** — a structured JSON object with the six seed
fields, updated with whatever you learned from the latest response.
Fields you have not yet covered are null.
3. **The brief** — a prose rendering of the seed fields as a readable
one-page document. This is what the person sees on the seed tab. It
should read as a narrative, not a form. No field labels. No
methodology terminology. Their words, organized.
4. **Completion percentage** — an integer 0–100 reflecting how much of
the seed has substantive coverage. This drives a visual indicator the
person sees. It should advance meaningfully with each response. 100
means all fields have substantive content.
5. **Status** — "in_progress" while the conversation continues.
"ready_for_review" when all six fields have substantive content and
you believe the brief captures the person's intent. When you reach
ready_for_review, your reply should invite the person to review the
brief: "Take a look at what we have so far — does this capture what
you are trying to do?"
## Response format
Return your response in exactly this format:
<reply>
[Your natural-language reply to the person — acknowledgment + next question]
</reply>
<seed_fields>
{
"what_the_work_is": "..." or null,
"who_consumes_the_work": "..." or null,
"voice": "..." or null,
"constraints": "..." or null,
"success_conditions": "..." or null,
"additional_assertions": [{"label": "...", "content": "..."}] or []
}
</seed_fields>
<brief>
[The prose one-pager — a readable document in the person's language]
</brief>
<completion>
[integer 0-100]
</completion>
<status>
[in_progress or ready_for_review]
</status>
## Things you never do
- Never say "I'm an AI" or "As an AI." You are Loomworks.
- Never use methodology terminology: "seed," "assertion," "engagement,"
"induction," "manifestation," "shaping," "rendering." The person does
not need to know these words.
- Never rush. No urgency language. No "let's get started quickly."
- Never present a numbered list of questions to answer. One question at
a time.
- Never break the companion voice. You are warm, capable, patient, and
genuinely interested in what the person wants to build.
The endpoint builds the LLM messages array from the event log:
messages = [
{"role": "user", "content": turn.message}
if turn.role == "operator"
else {"role": "assistant", "content": turn.raw_llm_response}
for turn in conversation_turns
]
# Append the new operator message
messages.append({"role": "user", "content": request.message})
The system prompt is included once at the start. The full conversation history is sent with every call so the LLM has complete context for synthesis.
The endpoint uses claude-sonnet-4-20250514 for conversation turns. The model string is a constant in the prompt module, not hardcoded in the endpoint:
CONVERSATION_MODEL = "claude-sonnet-4-20250514"
Per the Phase 25 FORAY amendment pattern, all new events carry content hashing and the _foray namespace.
| Event kind | Anchor priority | Actor |
|------------|----------------|-------|
| conversation_started | Standard | System |
| operator_turn | Standard | Person (seed drafter) |
| companion_turn | Standard | System |
| key_provided | High | Person (authority assignment — the key enables system capabilities) |
All new events use the existing compute_content_hash function from the Phase 25 FORAY amendment. The hash covers the event payload excluding the _foray namespace.
_foray namespaceEach event payload includes:
"_foray": {
"anchorable": True,
"anchor_priority": "<priority>",
"content_hash": "<sha256>",
"attestation_ref": None # No attestation on conversation turns
}
The key_provided event records when the Operator provides their API key (or when the credits path is selected). Its anchor priority is High (authority assignment — the key enables system capabilities on the engagement).
The seed submission, induction, findings, and commit events retain their existing FORAY hooks from Phase 25. The attestation seam on the commit ceremony is unchanged.
The creation surface at /engagements/new opens with the key prompt before anything else. The prompt asks the Operator to provide their API key.
A centered card on {colors.bleached} background. The Loomworks horizontal lockup in the top chrome.
Card content:
{typography.h3} (IBM Plex Serif): "Before we begin"{typography.body} (Inter): a short explanation that Loomworks needs an API key to help with the conversation. Non-technical. Does not say "LLM key" or "Anthropic API key." Says something like: "To power the conversation, Loomworks needs an API key. If you have one, enter it below. If you do not, you can use Loomworks credits."{typography.caption}.{components.button-primary} (white text, font-weight 600). Submits the key.{components.button-ghost}. Takes the credits path.
"Continue" calls PUT /engagements/{eid}/settings/api-keys/anthropic on the candidate engagement (existing per-engagement key store pattern). The key prompt also records a key_provided event with { key_source: "operator" }.
"Use Loomworks credits" checks for the system-level key (loomworks_llm_key in system_config). If present, records a key_provided event with { key_source: "system" } and proceeds.
If the system-level key is not configured, the button routes to a placeholder state: a message in {typography.body} saying "Loomworks credits are not yet available. Please provide an API key to continue." This is the credits-not-yet-available fallback while the credits purchase mechanism is future scope.
The candidate engagement is created (POST /engagements) when the Operator arrives at /engagements/new, before the key prompt is displayed. This is unchanged from Phase 25 — the candidate exists so that the key can be stored on it and conversation turns can be recorded against it.
A new endpoint to check whether a usable LLM key exists for a candidate engagement:
GET /engagements/{eid}/seed/converse/ready
Auth: person-session (seed drafter required)
Response: { "ready": bool, "key_source": "operator" | "system" | null }
The frontend calls this after key submission (or credits selection) to confirm the conversation can proceed.
After the key prompt succeeds, the creation surface presents the opening question:
"What would you like to be working on?"
The question appears in {typography.h3} (IBM Plex Serif) — the companion's voice. Below it, a multi-line text input area with the voice option (§12). Below the input, a submit button: "Share" or equivalent — {components.button-primary}.
Below the submit area, a secondary link in {typography.caption} with {colors.type-metal}: "I know what I need" — clicking it navigates to Surface B (the form path at the same route, toggled by a query parameter or internal state).
There is no "choose your level" selector. The path selection is embedded in the Operator's first action: answering the question enters Surface A; clicking the link enters Surface B.
The conversational interface is not a chat bubble interface. It is a structured vertical exchange — one question, one answer, one acknowledgment-and-next-question. Each exchange is a section, not a chat message.
Visual treatment of each exchange:
{typography.display-italic} (IBM Plex Serif, 1.2rem, 400 weight). Color: {colors.press-ink}. No avatar, no "Loomworks" label — the companion's voice is recognized by its typography, not by a name badge.{typography.body} (Inter). Color: {colors.type-metal}. A subtle left border or indentation to distinguish from the companion's text. If the response was spoken (voice input), a small microphone icon appears next to the text as provenance.{colors.rule-soft} horizontal rule between exchanges. Subtle, not dominant.Prior exchanges are visible above, scrollable. The current question and input area are at the bottom. The page scrolls to keep the active input visible.
The Operator sees only the current question and an input area (text + voice). Prior exchanges are read-only — the Operator cannot edit them directly. To amend a previous answer, the Operator says so in the conversation: "Actually, what I said about the audience — let me change that..." The companion updates the seed accordingly.
Per "only show what is available" — the Operator's attention is on the current question.
While the LLM is generating a response (after the Operator submits), the interface shows a loading indicator below the Operator's response. The indicator is subtle — a small animated element or a pulsing line — not a spinner. The companion is thinking, not processing.
When the conversation returns status: "ready_for_review", the companion's final message invites the Operator to review the brief. The interface transitions:
{components.button-primary}.POST /engagements/{eid}/seed with the structured seed content. The existing induction loop takes over.
The Operator can continue the conversation even after ready_for_review — they may want to adjust. The status reverts to in_progress if they add more turns. The "Submit for review" button appears only when the most recent status is ready_for_review.
If the Operator leaves and returns (navigates away and back, or refreshes the page), the frontend calls GET /engagements/{eid}/seed/conversation to restore the conversation state. Prior exchanges render in the exchange flow. The current question and input area appear at the bottom. The thermometer reflects the current completion percentage.
The companion remembers where you left off.
The creation surface has two tabs: Conversation (default, active) and Your brief (the seed document).
Tab bar appearance: tabs sit below the top chrome and above the content area. Active tab has a {colors.press-ink} bottom border (2px). Inactive tab text in {colors.type-metal}. Tab labels in {typography.body-em} (Inter, semi-bold).
CC determines the exact tab treatment for mobile (stacked or swipeable) and desktop. The requirement is discoverability: a first-time Operator should notice the seed tab without being told about it.
The seed tab displays the prose one-pager returned by the conversation endpoint. The document is rendered as markdown, using the brief's narrative structure (§5.6). Typography:
{typography.h2} (IBM Plex Serif).{typography.body} (Inter).
Background: {colors.bleached}. The document sits in a centered content area, like a page.
Before the Operator's first response, the seed tab shows an empty state: a short message in {typography.body} with {colors.ink-faint} — "Your brief will take shape as we talk." (Or equivalent. CC determines the exact copy. The message should be warm and anticipatory, not clinical.)
After the first response, the seed document appears — sparse at first, growing with each exchange.
When the seed document updates (after each conversation turn), and the Operator is on the Conversation tab, the seed tab shows a subtle notification: a small dot in {colors.state-attest} on the tab label, or equivalent. The notification clears when the Operator switches to the seed tab.
The form path (Surface B) also has access to the seed tab. When the Operator fills in form fields, the frontend calls a new lightweight endpoint (or reuses the LLM synthesis on the client side — CC determines the approach) to produce the prose one-pager from the form field values. The form Operator can switch to the seed tab at any time to see how their entries read as a brief.
If CC determines that client-side rendering (simple template, no LLM call) is more appropriate for the form path, that is acceptable. The form path's seed preview does not need the same depth of prose as the conversational path — a structured rendering of the field values is sufficient.
The thermometer is a fundraising-goal indicator — it celebrates what has been built, not what remains. The visual treatment:
{colors.cartridge} (the unfilled portion).{colors.state-attest} (the committed/progress color — a muted green that feels earned, not urgent).completion_percentage increases.Persistent across both tabs. Appears below the tab bar and above the content area. Visible at all times — the Operator always knows where they are in the journey.
The thermometer is the companion celebrating progress. The visual design should make the Operator feel that each response advanced the work. The fill should advance meaningfully — not in tiny increments that feel like a progress bar, but in satisfying jumps that feel like milestones.
The thermometer does not show a percentage number or "3 of 7 steps." It shows progress visually. The Operator sees it filling and thinks: "look how far we have come."
The form path (Surface B) also shows the thermometer. The fill is driven by which fields have content — as the Operator fills in form fields, the thermometer advances. Six fields, six increments (or continuous fill proportional to content length — CC determines the most satisfying behavior).
Same pattern as the Memory room voice contribution (Phase 16 §16.3). The input area shows a text area with a microphone button alongside.
{components.button-ghost} with a microphone icon from the icon set.MediaRecorder API (WebM audio).{colors.iron-oxide} background). A timer shows elapsed recording time.POST /engagements/{eid}/files (existing file upload endpoint).A new endpoint that transcribes voice without creating an assertion:
POST /engagements/{eid}/seed/converse/transcribe
Auth: person-session (seed drafter required)
Body: { "file_id": UUID }
Response: { "text": str }
This endpoint calls the existing transcription skill (transcribe_audio) and returns the text. Unlike the Memory room's contribute-voice endpoint, it does not create an assertion — it returns the text for the Operator to review and then submit as a conversation turn.
The transcription uses the same key resolution as the conversation endpoint: per-engagement openai key first, system-level fallback second.
After transcription, the transcribed text appears in the text input area. The Operator can edit it before submitting. This is the review step — the Operator confirms that the transcription matches what they said.
When the Operator submits a voice-transcribed turn, the ConverseTurnRequest.source_mode is set to "voice" and source_file_id carries the uploaded audio file's ID. The conversation turn event records this provenance.
If the browser does not support MediaRecorder (or microphone access is denied), the microphone button is absent. Per "only show what is available."
Surface B is the existing Phase 25 creation form, reached by clicking "I know what I need" on the conversational door (§9.1). It can also be reached via a "Switch to form" link on the Conversation tab (placement: CC determines — near the tab bar or at the bottom of the exchange flow).
The form path has a reciprocal link back to the conversational path: "Switch to conversation" or similar. If the Operator switches mid-flow, the conversation endpoint receives whatever was already filled in the form as the first turn's context.
The current Phase 25 field labels are replaced with simpler language:
| Current label | Simplified label | |---------------|-----------------| | What the work is | What you want to build | | Who consumes the work | Who this is for | | Voice | How it should sound | | Constraints | What it should not do | | Success conditions | What success looks like | | Additional assertions | Anything else |
CC determines the exact wording. The requirement is that every label is immediately understandable to someone who has never encountered Loomworks before.
Each seed field gains a "What is this?" toggle — the same pattern as the Phase 28 explanation toggles. Clicking the toggle expands a short help text below the field label, explaining what the field is asking for in plain language with an example.
Help content source: assertions in the Loomworks commons engagement Memory, queried via the explains relationship using concept identifiers concept:seed-field:{field-identifier}:
| Concept identifier | Field |
|--------------------|-------|
| concept:seed-field:what-you-want-to-build | What you want to build |
| concept:seed-field:who-this-is-for | Who this is for |
| concept:seed-field:how-it-should-sound | How it should sound |
| concept:seed-field:what-it-should-not-do | What it should not do |
| concept:seed-field:what-success-looks-like | What success looks like |
| concept:seed-field:anything-else | Anything else |
The toggles use the existing TypeExplanationToggle pattern from Phase 28 — live query against the concept identifier, static fallback if no assertion exists. The static fallback for each field is a one-sentence description.
Six explanation assertions need to be contributed to the Loomworks commons engagement Memory and linked via explains relationships during or after the phase. This is operational work (same process as Phase 28's eighteen explanation assertions).
The form retains all existing Phase 25 behavior: induction trigger after submission, findings presentation, amendment, commit with attestation seam. The form now additionally gives access to the live seed document (tab) and the completion thermometer.
The dashboard "New engagement" button and the welcome page "Create an engagement" door continue to route to /engagements/new. No changes to entry point components.
/engagements/new now renders:
The route no longer renders the form directly. The form is reached through the "I know what I need" link.
Per Phase 25 and the scoping note §S13:
New Next.js API proxy routes for the substrate endpoints:
| Frontend proxy | Substrate endpoint |
|----------------|-------------------|
| /api/engagements/[id]/seed/converse | POST /engagements/{eid}/seed/converse |
| /api/engagements/[id]/seed/conversation | GET /engagements/{eid}/seed/conversation |
| /api/engagements/[id]/seed/converse/ready | GET /engagements/{eid}/seed/converse/ready |
| /api/engagements/[id]/seed/converse/transcribe | POST /engagements/{eid}/seed/converse/transcribe |
Existing proxy routes for key store, file upload, seed submission, induction, and commit are unchanged.
{colors.bleached} (#FFFCF5).{colors.cartridge} (#F2EDE0).{colors.state-attest} (#6B7B6E).{colors.press-ink} (#1A1814).{colors.type-metal} (#4A4843).{colors.press-ink}.{colors.type-metal}.{colors.type-metal}.{colors.rule-soft} (#EDE7D7).{colors.state-attest}.{typography.display-italic} — IBM Plex Serif, 1.2rem, 400 weight, line-height 1.55.{typography.body} — Inter, 1rem.{typography.body-em} — Inter, semi-bold.{typography.caption}.{typography.mono} — IBM Plex Mono.{typography.h2} — IBM Plex Serif.{typography.body} — Inter.{colors.type-metal} text.{components.card} treatment.{components.input} styling.Loomworks horizontal lockup in the top chrome. Same as all post-auth surfaces.
test_system_config_crud — Set a system config value. Retrieve it — verify decryption matches original. Delete it. Retrieve again — verify None. Set two values — verify list returns both.
test_converse_endpoint_basic — Create a candidate engagement. Provide a key. Call POST /seed/converse with a first message. Verify response contains companion_message, seed_document, completion_percentage (> 0), status ("in_progress"), and turn_number (1). Verify two events recorded (operator_turn and companion_turn).
test_converse_multi_turn — Three turns of conversation. Verify completion_percentage advances. Verify turn_number increments. Verify the seed_document grows with each turn.
test_conversation_history — Three conversation turns. Call GET /seed/conversation. Verify all six turns returned (3 operator + 3 companion) with correct ordering and content.
test_conversation_persistence — Two conversation turns. Call GET /seed/conversation — verify state. Call POST /seed/converse with a third message — verify it continues from the existing history.
test_converse_no_key — Create a candidate engagement without providing a key. No system-level key configured. Call POST /seed/converse. Verify 503.
test_converse_system_key_fallback — Create a candidate engagement without an Operator key. Set loomworks_llm_key in system config. Call POST /seed/converse. Verify success. Verify credits_consumed field on the companion_turn event.
test_converse_ready_endpoint — Create a candidate with a key. Call GET /seed/converse/ready. Verify ready: true, key_source: "operator". Remove the key. Verify ready: false.
test_converse_transcribe — Upload a WebM audio file. Call POST /seed/converse/transcribe (transcription mocked). Verify transcribed text returned.
test_converse_foray_hooks — One conversation turn. Read events. Verify content_hash present on both operator_turn and companion_turn. Verify _foray namespace with correct anchor_priority.
test_key_provided_event — Provide a key on a candidate engagement. Verify a key_provided event with anchor priority "high" and key_source: "operator".
LLM mocking: Conversation tests mock the Anthropic API call. The mock returns a well-formed response matching the prompt's expected format (reply + seed_fields + brief + completion + status). Tests do not make real LLM calls.
ConversationExchange.test.tsx — Renders a conversation exchange (companion question + operator response). Verifies companion text uses serif typography. Verifies operator text uses body typography. Verifies voice provenance icon appears when source_mode is "voice".
SeedDocumentTab.test.tsx — Renders the seed tab with markdown content. Verifies prose rendering (no field labels). Verifies empty state message when no content. Verifies title rendering.
CompletionThermometer.test.tsx — Renders the thermometer at 0%, 50%, 100%. Verifies fill width corresponds to percentage. Verifies completion state at 100%.
KeyPrompt.test.tsx — Renders key prompt. Verifies "Continue" button submits key. Verifies "Use Loomworks credits" button is present. Verifies password input masking.
CreationTabs.test.tsx — Renders tab bar. Verifies switching between Conversation and seed tabs. Verifies notification dot appears after update.
FormPathEnhanced.test.tsx — Renders the enhanced form. Verifies simplified field labels. Verifies "What is this?" toggles present on each field. Verifies "Switch to conversation" link present.
VoiceInput.test.tsx — Renders voice input button. Verifies recording state change (MediaRecorder mocked). Verifies transcribed text appears in input area.
conversational-creation.spec.ts — Navigate to /engagements/new. Enter API key. Submit first response. Verify companion acknowledgment appears. Verify seed tab notification. Switch to seed tab — verify content. Switch back to Conversation tab. Submit second response. Verify thermometer advanced. (LLM endpoint mocked.)
Auto-mode posture: Step 0 auto. Steps 1–4 auto, Checkpoint A. Steps 5–9 auto, Checkpoint B. Steps 10–11 auto, Checkpoint C (final).
Step 0 — CR archival.
Archive this CR to docs/phase-crs/phase-31-cr-conversational-engagement-creation-v0_1.md.
Commit (substrate repo): Phase 31 step 0: CR archival.
Step 1 — System config table and utilities.
system_config table (§4.2).src/loomworks/system_config/store.py (§4.3).test_system_config_crud.
Verification: uv run pytest -v green.
Commit (substrate repo): Phase 31 step 1: system config table and utilities.
Step 2 — Conversation prompt template.
src/loomworks/prompts/creation_conversation.py (§6).PROMPT_VERSION, CREATION_CONVERSATION_PROMPT, CONVERSATION_MODEL constants.Verification: module imports cleanly.
Commit (substrate repo): Phase 31 step 2: conversation prompt template.
Step 3 — Conversational seed synthesis endpoint.
src/loomworks/api/routers/seed_conversation.py.POST /seed/converse (§5.1, §5.2, §5.5).GET /seed/conversation (§5.3).GET /seed/converse/ready (§8.6).POST /seed/converse/transcribe (§12.3).
Verification: uv run pytest -v green.
Commit (substrate repo): Phase 31 step 3: conversational seed synthesis endpoint.
Step 4 — Substrate tests.
Verification: uv run pytest -v green. All new tests pass.
Commit (substrate repo): Phase 31 step 4: substrate tests.
Checkpoint A — Substrate complete. All endpoints functional. Operator verifies: conversation endpoint returns well-formed responses with mocked LLM. Key store works. History endpoint reconstructs conversation state. FORAY hooks present on events.
Step 5 — Key prompt surface.
/engagements/new route to render key prompt first (§14.2).Verification: lint + tsc + build clean. Operator can enter a key and proceed past the prompt.
Commit (frontend repo): Phase 31 step 5: key prompt surface.
Step 6 — Conversational interface (Surface A).
POST /seed/converse and GET /seed/conversation.Verification: lint + tsc + build clean. Operator can have a multi-turn conversation.
Commit (frontend repo): Phase 31 step 6: conversational interface.
Step 7 — Live seed document tab and thermometer.
Verification: lint + tsc + build clean. Seed tab shows growing document. Thermometer advances.
Commit (frontend repo): Phase 31 step 7: seed tab and thermometer.
Step 8 — Voice input.
POST /seed/converse/transcribe.Verification: lint + tsc + build clean. Voice recording works (in supported browsers).
Commit (frontend repo): Phase 31 step 8: voice input.
Step 9 — Form path enhancement (Surface B).
Verification: lint + tsc + build clean. Form has simplified labels and help toggles.
Commit (frontend repo): Phase 31 step 9: form path enhancement.
Checkpoint B — All frontend surfaces functional. Operator verifies: key prompt works, conversation flows naturally, seed tab updates, thermometer advances, voice input works, form has simplified labels with help, switching between paths works. Companion identity confirmed — the environment feels warm, not clinical.
Step 10 — Component and E2E tests.
Verification: lint + tsc + build + test clean.
Commit (frontend repo): Phase 31 step 10: component and E2E tests.
Step 11 — Implementation notes and tagging.
Create docs/phase-impl-notes/phase-31-implementation-notes-v0_1.md.
Commit (substrate repo): Phase 31 step 11: implementation notes.
Checkpoint C — Final. Both repos green. Tag both repos as phase-31-conversational-engagement-creation.
Phase 31 is accepted when:
/engagements/new. Operator can enter a key and proceed. Credits option is present.ready_for_review, "Submit for review" button appears. Clicking it triggers the existing induction loop.content_hash and _foray namespace on all new event kinds.key_provided event recorded with correct anchor priority.credits_consumed field present on companion_turn events when using system key.{colors.bleached} content background. IBM Plex Serif for companion voice. Inter for body text. No pure white. Thermometer in {colors.state-attest}. Tabs, dividers, buttons per §16.
On acceptance: tag both repos as phase-31-conversational-engagement-creation. Write implementation notes.
system_config table, conversational seed synthesis endpoint, conversation history endpoint, readiness check endpoint, voice transcription endpoint, conversation prompt template, LLM key resolution with fallback, credits metering hook, FORAY hooks on conversation events.PROMPT_VERSION constant. Tracking which version of the prompt produced which conversation turn is not yet implemented (the version could be recorded on companion_turn events). Future improvement.
Read the Change Request document at the path I supply below. This is
CR-2026-043 v0.1, the Phase 31 Change Request. You are the executing
agent named in the CR.
CR path: ~/Downloads/phase-31-cr-conversational-engagement-creation-v0_1.md
Phase 31 refactors the engagement creation surface from a form into
a conversation. This is the first surface built under the companion
identity.
Also read the product identity standing note — it governs the tone
of everything you build:
~/Downloads/loomworks-product-identity-standing-note-v0_1.md
Key requirements:
- System-level key store (system_config table, Fernet encryption).
- Conversational seed synthesis endpoint (POST /seed/converse).
Server-managed conversation state (turns as events). LLM key
resolution: per-engagement first, system-level fallback.
- Conversation prompt template as a versioned substrate asset.
Full template text in CR §6.2. The companion's voice.
- Key prompt before conversation. Operator-provided key primary,
credits fallback.
- Conversational interface: vertical exchange flow, not chat
bubbles. One question at a time. Text + voice input.
- Live seed document on a companion tab. Prose one-pager, visible
at all times, updated after each turn.
- Completion thermometer. Fundraising-goal pattern. Celebrates
progress. Persistent across both tabs.
- Voice input: same MediaRecorder pattern as Memory room Phase 16.
Transcribe, review, submit.
- Form path enhancement: simplified field labels, expandable
"What is this?" help on each field (Phase 28 pattern).
- FORAY hooks on all new events (Phase 25 amendment pattern).
- Conversation persistence: server-managed state survives refresh.
The companion identity governs: the environment is never clinical.
Every interaction should leave the Operator feeling accompanied and
that the journey advanced. Warmth, not efficiency.
Substrate baseline: 1181 tests, 2 skips.
Frontend baseline: 151 component tests, 6 E2E (1 skip).
Run Step 0: archive CR in substrate repo.
Steps 1-4 auto, Checkpoint A halts for substrate verification.
Steps 5-9 auto, Checkpoint B halts for frontend smoke test.
Steps 10-11 auto, Checkpoint C halts for tagging.
Brand enforcement: bleached (#FFFCF5) content background. IBM Plex
Serif for companion voice (display-italic). Inter for body text.
IBM Plex Mono for labels. State-attest (#6B7B6E) for thermometer
fill. Cartridge (#F2EDE0) for thermometer background. No pure white.
Press-ink (#1A1814) for companion text. Type-metal (#4A4843) for
operator text. Rule-soft (#EDE7D7) for dividers.
Implementation notes at Step 11:
docs/phase-impl-notes/phase-31-implementation-notes-v0_1.md
DUNIN7 — Done In Seven LLC — Miami, Florida Phase 31: Conversational Engagement Creation — CR v0.1 — 2026-05-01