Version. 0.1 Date. 2026-06-03 Provenance. Engine-source trace, Operator: Marvin Percival. The engine-interface layer beneath the seed-creation mechanism map: what the seed-creation core requires to be handed, and whether structured extraction is callable independently of any conversation flow. Status. Investigation / contract statement. Report-only — no design, no fix, no engine changes. Reads with (the seed/boundary set).
investigations/loomworks-seed-creation-mechanism-map-v0_1.md — the full lifecycle map.investigations/loomworks-seed-creation-three-paths-deep-dive-v0_1.md — why the three terminal paths diverge.investigations/loomworks-engine-boundary-inventory-v0_1.md — the whole-engine boundary inventory this contract feeds into.standing-notes/loomworks-standing-note-engine-wrapper-boundary-v0_1.md — the lens (engine must survive any wrapper).
Code citations. engine: = the loomworks-engine repo at time of writing; line numbers indicative, names load-bearing.
There is no single "structured-extraction capability." There are two, with opposite coupling:
seed_fields anywhere in the engine are two lines, both inside converse_route (engine: seed_conversation.py:472 and :809). The fields come from the LLM following CREATION_CONVERSATION_PROMPT and parsed by the module-private _parse_llm_response (:254), which parses a five-tag response (<reply>/<seed_fields>/<brief>/<completion>/<status>). There is no extract_seed_fields(text) -> dict. The prompt fuses extraction with conversation ("You are Loomworks — a companion… Ask one question at a time", creation_conversation.py:128-153). To obtain Phase 31 structure you must run a full conversation turn — needs an engagement_id, appends operator_turn + companion_turn events, resolves keys/credits, co-produces reply/brief/completion/status. The extraction cannot be invoked alone.extract_discovery_to_seed_skill (engine: skills/discovery_to_seed.py:309) + core _extract_discovery_to_seed (:201) are standalone async functions (ExtractionSkill protocol): single-shot LLM call with a pure extraction prompt ("You return strict JSON. No prose… No multi-turn, no retry", prompts/discovery_to_seed_extraction.py:34-41), validated into the six-field payload by _validate_extracted_payload. Already reused outside its endpoint (Phase 55 discovery_record → converse.py:243). But its input is file_path: Path (:203/:311; file_path.read_bytes().decode("utf-8") :222-223) — not text or turns. To feed it a conversation a caller must first materialize a Markdown file (what Phase 55 discovery_record does: _compose_markdown_discovery_record → Phase 16 upload → extract).Fix-size consequence: reusing Phase 53 over a conversation already exists (at the cost of a throwaway Markdown file). Making Phase-31-grade extraction callable independently of the conversation engine does not exist as a seam — extraction is fused into the conversation prompt + turn handler.
| Entry point | Source | Requires (shape) | Produces |
|---|---|---|---|
| draft_seed | creation.py:82 | The six fields already structured: what_the_work_is/who_consumes_the_work/voice_of_the_work/success_conditions: str, constraints: list[str], additional_assertions: dict[str,str]; plus R-A10 initial_contributors: list[UUID], initial_agents: list[UUID]; an actor: ActorRef; db. | Seed v1 under ADMIN engagement (seed_drafted). Does not extract — pure constructor/persister. |
| amend_candidate_seed | creation.py:145 | A prior_seed: Seed + fields to change (others carry forward); actor, db. Pre-instantiation. | Seed v+1 (seed_amended, wasRevisionOf). |
| induct_seed | creation.py:409 | A Seed + SeedInductionAgent + AgentRunner + actor + db. | One cycle; Findings + SeedInductionLoopCycle; convergence = zero open findings (:501). |
| commit_engagement | creation.py:681 | A converged seed (no open findings, else NotConvergedError) + a human actor (kind != "agent", else AgentCannotCommitError). | Engagement v1 (engagement_committed). |
| commit_engagement_divergent | creation.py:725 | Seed + human actor + non-empty rationale (else EmptyRationaleError). | Divergent Engagement. |
| (row wiring) | engagements.py:255-288 / me_create_engagement.py:216-258 | A caller must separately INSERT an engagements row (state='candidate') and UPDATE candidate_seed_id — draft_seed does not create the row. | Candidate engagement linked to the seed. |
Induction examines the seed against R-A5–R-A11; an empty field yields a finding.
_validate_extracted_payload enforces (what/who/voice/success: non-empty strings; constraints: list of declarative strings; additional_assertions: string→string map), R-A10 endpoint-filled. This is what Phase 54's adapted seed_fields, the form's six fields, and Phase 53's extraction all hand over.what_the_work_is populated, other five empty — _compose_brief_from_turns + _execute_brief_path (me_create_engagement.py:135, :374-385). Induction produces findings → early-return state="candidate" (:642-654).
> To create a well-formed convergent seed, a caller must hand draft_seed all six R-A5–R-A9 + R-A11 fields already populated with substantive, concrete content — in the shape _validate_extracted_payload enforces — plus R-A10 (initial_contributors=[person], initial_agents=[]) and a contributor-kind actor; having FIRST converted its raw material (a conversation, a document, form input) into those six fields by an extraction step the engine does NOT perform inside draft_seed. Then induct_seed to convergence and commit_engagement with a human actor.
The pivotal clause: the engine's seed-construction core is content-agnostic and does not extract. Turning raw material into the six fields is a separate obligation the caller must satisfy before draft_seed — and the engine offers exactly one reusable tool for it (Phase 53, file-shaped) and one non-reusable one (Phase 31, welded to the conversation).
Clean — any wrapper can satisfy: draft_seed, amend_candidate_seed, induct_seed, commit_engagement/_divergent, _create_engagement — they take structured fields / a Seed / an actor and assume nothing about provenance. The form-path HTTP routes are clean surfaces over that core. extract_discovery_to_seed_skill is almost clean — a standalone callable producing the canonical payload — with one coupling: input is a file_path, not text/turns.
Not clean — assumes a particular caller's shape:
converse_route + CREATION_CONVERSATION_PROMPT — "conversation → six fields" is not exposed as a unit; a different wrapper cannot get Phase-31-grade extraction without running this Companion's conversation. The load-bearing not-clean point._adapt_seed_fields_for_draft_seed (seed_commit_from_brief.py:97) — exists solely to translate the Companion's seed_fields emission shape (voice key; list/dict variance) into draft_seed kwargs.
Bottom line: the construction/induction/commit core is clean and wrapper-agnostic; the extraction step is where the engine either assumes the Companion (Phase 31, welded) or offers a file-shaped reusable tool (Phase 53). A wrapper's whole obligation reduces to "produce the six fields in the canonical shape, by whatever means, before draft_seed."
Investigation / contract only. No engine or wrapper code changed. No mechanism redesigned or proposed. Filed in loomworks-record/investigations/. Pairs with the seed-creation map, the three-paths deep-dive, the engine-boundary inventory, and standing-notes/loomworks-standing-note-engine-wrapper-boundary-v0_1.md (the lens).