DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path analyses/loomworks-engine-implementation-strategy-v0_2.md

Loomworks — Engine Implementation Strategy

Version. 0.2 Date. 2026-05-03 Provenance. Claude.ai design session, continued from the Operator Layer v0.6 and the v0.1 of this document. Operator: Marvin Percival. Status. Working draft. The companion document to the Operator Layer v0.6. Where v0.6 names what the engine must support, this document names what changes inside the engine to support it, structured as a phased plan against the engine's actual current state at Phase 33. Audience. The Operator and Claude. Translates the Operator Layer architecture into specific engine work. Sits alongside. The Operator Layer v0.6 (which carries the architecture and the Operator-Layer-side development plan). This document covers the engine work; v0.6 covers the Operator Layer work; together they cover the build. Supersedes. v0.1 (same date) — three decisions left open in v0.1 Section 13 are now resolved. Phase 38's grammar set expands from five to six (scene-specification added). Phase 40's composition endpoint behavior is specified explicitly as a stub-then-wire pattern. Section 13 reframed from "open questions" to "decisions resolved." When the engine work moves into execution. This document holds the design intent for each engine phase. Individual phase CRs translate the design intent into Claude Code execution; the CRs derive from this document the same way the Operator Layer arc CRs will derive from v0.6 Part II.


1. What this document is and is not

This document is. A phased engine implementation strategy for the work the Operator Layer architecture commits the engine to. It identifies what changes in the engine, the data model, the test surface, and the dependencies between phases.

This document is not. A redesign of the engine. The engine works. 1,274 substrate tests pass. 33 phases of disciplined construction sit behind it. This document specifies extensions, not corrections — the engine's existing patterns are the foundation, not something to be replaced.

This document is also not. A set of CRs. CRs come later, when each engine phase enters active build. CRs include the order-of-operations details, the kickoff prompts, the auto-mode posture, the per-step test verifications. This document holds the design intent that CRs will execute against.


2. What v0.6 commits the engine to

The Operator Layer v0.6 Section 13 named five engine prerequisites at architectural-reasoning level. Inspection of the engine's actual state at Phase 33 reveals that some of those prerequisites are partially in place, some are differently shaped than v0.6 named, and at least two prerequisites that the architecture genuinely needs were not in v0.6's list. This section reframes v0.6's commitments against what the engine actually has today.

2.1 v0.6 Section 13 reviewed against the actual engine

v0.6 13.1 — Async submit-and-poll specialist production. v0.6 named this as a missing capability. The engine actually has substantial async machinery already: BackgroundAgentRunner exists from Phase 3; shaping_jobs and render_jobs operational tables exist with the queued → dispatched → completed/failed status pattern from Phase 9 and Phase 10; render dispatch is non-blocking (HTTP 202, caller polls for completion). The specific gap is narrower than v0.6 framed it: the current pattern assumes the specialist completes synchronously inside the BackgroundAgentRunner's task. For external long-running services (Claude Code Dispatch, 3D printing services, video generators), the specialist itself dispatches to an external system, returns a handle, and the engine polls the external system for completion. This is the missing piece — external-service polling, not asynchronous dispatch in general. Reframed prerequisite name: External-service polling for specialists.

v0.6 13.2 — Binary and reference render content support. v0.6 named this as a missing capability. The engine actually has the materializer registry pattern from the May 2 render-download work, which produces bytes from JSONB render_content. What's actually missing is upstream of materialization: RenderEvent carries no content-type discriminator and no way to point at non-dict content. Storing a generated STL file or a deployment URL today requires shoving it into the JSONB field, which is structurally wrong. The prerequisite's specific shape is: extend RenderEvent with a content-kind discriminator (inline_dict, binary_blob, external_reference) and add storage paths for binary content (file-system pattern from Phase 16) and reference URIs. Reframed prerequisite name: Render content kind and external reference support.

v0.6 13.3 — Re-render contracts with diff and migration semantics. v0.6 named this. The engine genuinely lacks it. Today's re-render is fresh production with no relationship to prior renders. The shape lineage exists (confirmed_shape_event_ref on RenderEvent), but the render side has no prior_render_ref and no way for a specialist to declare whether it supports incremental update. Confirmed prerequisite. Renamed for clarity: Incremental re-render with prior-render lineage.

v0.6 13.4 — Adapter chaining and composition orchestration. v0.6 named this. The engine genuinely lacks it. There is no multi-step render lifecycle support. No way to declare "produce-then-review" or "produce-then-translate" as a chained operation. Confirmed prerequisite. Renamed: Adapter chaining and composition.

v0.6 13.5 — Specification grammar declaration on shape types. v0.6 named this. The engine partly has it: DeclaredShapeType.shaping_instructions_ref carries the implicit grammar via the shaping instructions. What's missing is explicit declaration of structural elements, completeness criteria, and testability criteria that the engine could evaluate without running a full shaping pass. Confirmed prerequisite. Renamed for precision: Specification grammar declaration on DeclaredShapeType.

2.2 Two prerequisites v0.6 missed

Cross-engagement aggregation endpoints. The Operator Layer Dashboard zone aggregates state across all the Operator's projects (active jobs, items needing decision, recently finished). Today's endpoints are all scoped to a single engagement. The person layer (Phase 14) provides GET /me/memberships, but there is no /me/dashboard or /me/projects/active that aggregates state across engagements. This is genuinely missing and is a substantial new endpoint surface. New prerequisite: Cross-engagement aggregation endpoints.

Operator-vocabulary response schemas. The Operator Layer architecture commits to "Operator vocabulary in API responses" enforced at the Orchestration API. Today's API responses use engine vocabulary throughout — engagement_id, assertion, shape_event, render_event. The Orchestration API needs to do real translation, which means new response schemas, not thin wrappers. This is more work than v0.6's "translation layer" framing suggested. New prerequisite: Operator-vocabulary response schemas.

2.3 The seven engine prerequisites in their final form

Replacing v0.6 Section 13's five with seven, after the inspection:

  1. External-service polling for specialists. The BackgroundAgentRunner and operational tables exist. What needs to be added: a polling mechanism for specialists that dispatch to external long-running services and return handles rather than completing in-process.
  2. Render content kind and external reference support. Extend RenderEvent with a content-kind discriminator. Add binary blob storage and external reference URI handling. The materializer registry already exists; this prerequisite supplies what it materializes from.
  3. Incremental re-render with prior-render lineage. Add prior_render_ref to RenderEvent. Add supports_incremental_rerender declaration to render specialists. Add the engine orchestration that calls the right method based on the declaration.
  4. Adapter chaining and composition. New RenderComposition MemoryObject that holds multi-step lifecycles. Engine orchestration that holds state between adapter calls. Conditional revision triggers for review-driven re-renders.
  5. Specification grammar declaration on DeclaredShapeType. Add explicit grammar declaration to DeclaredShapeType. Define a small grammar-of-grammars. Existing shape types declare their grammar; new shape types declare at creation.
  6. Cross-engagement aggregation endpoints. New endpoints under /me/... that aggregate state across the requesting person's engagements. Active jobs across projects, items needing decision across projects, recently finished artifacts across projects.
  7. Operator-vocabulary response schemas. New schemas at the Orchestration API layer that translate engine vocabulary into Operator vocabulary. Vocabulary wall enforced in the schema layer.

These seven prerequisites are the engine work the Operator Layer architecture depends on. Sections 4 through 10 specify each prerequisite as an engine phase.


3. Phase numbering and sequencing

The engine build is currently at Phase 33 (the most recent tagged work being engagement activity observability). Operator Layer engine work continues the numbering. The seven engine prerequisites become Phases 34 through 40, with sequencing determined by their dependencies.

Sequencing. Phases that have no dependency on other Operator Layer engine work can run in parallel against engine capacity. Phases that depend on others must wait. The dependency graph is:


Phase 34 (External-service polling) ─────────┐
                                              ├─→ Phase 37 (Adapter chaining)
Phase 35 (Render content kinds) ──────────────┤
   └─→ Phase 36 (Incremental re-render) ──────┘

Phase 38 (Specification grammar declaration) — independent

Phase 39 (Cross-engagement aggregation) — independent

Phase 40 (Operator-vocabulary schemas) — depends on Phase 39 conceptually
                                          but can be designed in parallel

Parallelization. Phases 34, 35, 38, and 39 can all start immediately. Phase 36 starts after Phase 35 lands. Phase 37 starts after Phase 34 and Phase 36 land. Phase 40 starts after Phase 39's endpoint shapes are stable.

Operator Layer arc dependencies. The seven phases unblock different Operator Layer arcs:

Estimated effort. Each phase is medium-sized — roughly the scope of Phase 17 (Redirect and Display Numbers) or Phase 28 (Expandable Explanations). Phase 39 (cross-engagement aggregation) is larger because it adds a substantial new endpoint surface. Phase 37 (composition) is larger because it touches orchestration directly. The other five phases are typical CR-sized.


4. Phase 34 — External-service polling for specialists

4.1 Goal

Enable specialists to dispatch to external long-running services (Claude Code Dispatch, 3D printing service APIs, video generation services), return a handle, and have the engine poll the external service for completion rather than the specialist blocking until production finishes.

4.2 What exists today

4.3 What's missing

The specialist contract assumes synchronous completion within the BackgroundAgentRunner's task. There is no mechanism for a specialist to:

  1. Submit work to an external service.
  2. Return a handle representing in-flight external work.
  3. Allow the engine to poll the external service for completion at intervals.
  4. Receive the produced artifact when external work completes and write the RenderEvent then.

For Phase B's Claude Code adapter, this matters concretely: building a website takes minutes to hours. The adapter cannot block in the BackgroundAgentRunner for that long — the FastAPI lifespan would not survive a deploy or restart, and the polling pattern would not surface meaningful progress.

4.4 What changes

Specialist contract extension. RenderSpecialist gains a second return mode. Today the specialist returns RenderEvent directly. Phase 34 adds:


@dataclass
class ExternalProductionHandle:
    """Returned by specialists that dispatch to external services.
    Indicates the engine should poll for completion."""
    external_job_id: str          # Specialist-defined identifier for the external work
    polling_interval_seconds: int  # How often to poll
    progress_hint: str | None      # Optional human-readable status

# Specialists may now return either RenderEvent or ExternalProductionHandle:
async def produce_render(self, ...) -> RenderEvent | ExternalProductionHandle:
    ...

# When ExternalProductionHandle is returned, the specialist also implements:
async def poll_external_work(
    self, *, external_job_id: str, db: AsyncSession
) -> RenderEvent | ExternalProductionHandle | ExternalProductionFailure:
    """Called by the engine at the polling interval. Returns either:
    - RenderEvent: production completed successfully
    - ExternalProductionHandle: still in progress (may update progress_hint)
    - ExternalProductionFailure: external service reported failure
    """
    ...

New MemoryObject: ExternalProductionRecord. Tracks in-flight external work tied to a render_jobs row.


class ExternalProductionRecord(MemoryObject):
    object_type: Literal["external_production_record"] = "external_production_record"

    render_job_id: UUID                      # The render_jobs row this tracks
    specialist_ref: ActorRef                 # The specialist that dispatched the work
    external_job_id: str                     # Specialist-defined external identifier
    polling_interval_seconds: int            # Current polling interval
    progress_hint: str | None                # Last reported progress
    last_polled_at: datetime
    state: Literal["polling", "completed", "failed"]

New render_jobs status: awaiting_external. Between dispatched and completed, a job that returns an ExternalProductionHandle transitions to awaiting_external. The engine's polling loop reads awaiting_external jobs and calls the specialist's poll_external_work.

Engine polling loop. A background task started by the FastAPI lifespan that walks awaiting_external rows on a schedule, calls each specialist's poll_external_work, and acts on the response:

Restart safety. The polling loop must survive process restarts. State lives in external_production_records; the loop on startup reads any awaiting_external rows and resumes polling. No work is lost across restarts.

4.5 Test surface

4.6 Migrations

4.7 What's deferred to a later phase


5. Phase 35 — Render content kind and external reference support

5.1 Goal

Extend RenderEvent to support content kinds beyond inline_dict. Specifically: binary blobs (STL files, generated images, audio) and external references (deployment URLs, cloud storage URIs). The materializer registry already exists; Phase 35 supplies what it materializes from.

5.2 What exists today

5.3 What's missing

RenderEvent cannot represent:

Today, all three would have to be shoved into the JSONB render_content field, which conflates content with metadata and breaks the materializer registry's separation of concerns.

5.4 What changes

RenderEvent extension. Add content_kind discriminator and content storage fields:


class RenderEvent(MemoryObject):
    # ... existing fields unchanged ...

    # New in Phase 35:
    content_kind: Literal["inline_dict", "binary_blob", "external_reference", "multi_file"]

    # When content_kind = "inline_dict": render_content carries the dict (existing behavior)
    # When content_kind = "binary_blob": render_content carries metadata; storage_path points at bytes
    # When content_kind = "external_reference": render_content carries metadata; reference_uri is the link
    # When content_kind = "multi_file": render_content carries the manifest; multi_file_manifest holds file refs

    storage_path: str | None = None              # For binary_blob: path under data/renders/{engagement_id}/
    reference_uri: str | None = None              # For external_reference: the URL or URI
    reference_metadata: dict | None = None        # For external_reference: deployment timestamp, version, etc.
    multi_file_manifest: list[dict] | None = None # For multi_file: list of {filename, content_kind, storage_path/uri, content_type}

Storage convention for binary blobs. Following Phase 16's pattern: data/renders/{engagement_id}/{render_event_object_id}{ext}. Binary content is written to disk; the RenderEvent carries the path. Binary content survives database backups but the path-relative-to-data-directory is what's stored, so backup-and-restore works correctly.

Multi-file renders. A multi-file render carries a manifest listing files. Each file has its own kind (binary_blob or external_reference), so a multi-file render can mix bytes-on-disk with cloud URIs. The download pathway materializes a multi-file render as a zip archive; individual files can be downloaded separately.

Materializer registry extension. Today the registry maps render_format to materializer functions that take dict and return bytes. Phase 35 extends this:


# Materializers now take RenderEvent (not just render_content) so they can dispatch by content_kind:
MaterializerFn = Callable  # async (render_event, engagement_title) -> MaterializedFile

async def materialize_pdf(render_event, engagement_title):
    if render_event.content_kind == "inline_dict":
        # Existing behavior
        ...
    elif render_event.content_kind == "binary_blob":
        # Read from storage_path, possibly wrap in PDF if format declared
        ...
    elif render_event.content_kind == "external_reference":
        # Generate a PDF that names the URL — for cases where the artifact is the URL itself
        ...

Most materializers will keep behaving as today (dict-driven). New materializers handle binary blobs and external references as needed.

API extensions.

5.5 Test surface

5.6 Migrations

5.7 What's deferred to a later phase


6. Phase 36 — Incremental re-render with prior-render lineage

6.1 Goal

When a shape's specification changes, a re-render may be incremental rather than fresh: a website re-render preserves URLs, a contract re-render preserves formatting, a 3D model re-render preserves printer settings. Phase 36 adds the engine support for this lineage and the specialist declaration of incremental-rerender capability.

6.2 What exists today

6.3 What's missing

RenderEvent has no prior_render_ref. Specialists have no way to declare whether they support incremental re-render. The engine has no orchestration that calls a specialist's incremental method when prior-render lineage exists.

6.4 What changes

RenderEvent extension.


class RenderEvent(MemoryObject):
    # ... existing fields unchanged ...
    # New in Phase 36:
    prior_render_ref: MemoryRef | None = None    # Set when this render is an incremental update
    revision_strategy: Literal["fresh", "incremental"] = "fresh"

Specialist contract extension.


class RenderSpecialist:
    # New in Phase 36:
    supports_incremental_rerender: bool = False  # Declared by the specialist class

    async def rerender(
        self, *,
        prior_render_event_ref: MemoryRef,
        revised_shape_event_ref: MemoryRef,
        ...,
    ) -> RenderEvent | ExternalProductionHandle:
        """Called when prior render exists and specialist supports incremental.
        Default implementation raises NotImplementedError; specialists that
        support incremental override this method.

        Returns a RenderEvent with content reflecting the incremental update,
        or an ExternalProductionHandle for async incremental work.
        """
        raise NotImplementedError("Specialist does not support incremental rerender")

Engine orchestration. The render dispatch logic (Phase 10) gains a branch:


# Pseudocode for the dispatch path:
if revised_shape_event has a prior render in the same declared_render_type:
    if specialist.supports_incremental_rerender:
        result = await specialist.rerender(
            prior_render_event_ref=prior_render_ref,
            revised_shape_event_ref=revised_shape_ref,
            ...
        )
        new_render_event.prior_render_ref = prior_render_ref
        new_render_event.revision_strategy = "incremental"
    else:
        result = await specialist.produce_render(...)
        new_render_event.revision_strategy = "fresh"
else:
    # First render for this declared_render_type — always fresh
    result = await specialist.produce_render(...)
    new_render_event.revision_strategy = "fresh"

Lineage chain. Each RenderEvent may reference its prior_render_ref. The chain forms a linked list of revisions. The Library can show the lineage as a version history; the Operator can navigate older versions and revert if needed. This lineage discipline parallels the assertion revision chain established in Phase 3 (wasRevisionOf).

6.5 Test surface

6.6 Migrations

6.7 What's deferred to a later phase


7. Phase 37 — Adapter chaining and composition

7.1 Goal

Support multi-step render lifecycles where one adapter's output becomes another adapter's input. Cross-model review (produce-then-review) is the immediate use case; produce-then-translate, produce-then-validate, parallel reviewers, and other compositions are general patterns the same support enables.

7.2 What exists today

Single-specialist render lifecycles: shape goes in to a specialist, render comes out. There is no engine support for chaining.

7.3 What's missing

7.4 What changes

New MemoryObject: RenderComposition. Represents a multi-step render lifecycle.


class RenderComposition(MemoryObject):
    object_type: Literal["render_composition"] = "render_composition"

    # The chain definition
    steps: list[CompositionStep]                 # Ordered or parallel-grouped steps

    # Lineage
    confirmed_shape_event_ref: MemoryRef         # The shape that initiates the composition
    triggered_by: ActorRef
    trigger_reason: str

    # Execution state
    state: Literal["pending", "running", "completed", "failed", "stopped"]
    current_step_index: int                      # For ordered execution
    step_results: list[dict]                     # Results from each completed step
    failure_detail: str | None

    # Conditional triggers
    revision_threshold: dict | None              # Optional: when to trigger producer re-render
                                                 # e.g., {"reviewer_specialist": "...", "max_severity": "blocking"}


class CompositionStep(BaseModel):
    """A single step in a composition."""
    step_id: str                                 # Identifier within the composition
    step_kind: Literal["produce", "review", "transform", "validate"]
    specialist_ref: ActorRef                     # The specialist that handles this step
    input_refs: list[MemoryRef]                  # References to inputs (shape, prior step output, etc.)
    config: dict                                 # Step-specific configuration
    parallel_group: str | None = None            # If set, all steps in the same group run in parallel

Engine orchestration. The composition orchestrator:

  1. Reads a RenderComposition and walks its steps in declared order.
  2. For each step, resolves the specialist via the registry.
  3. Calls the specialist with the inputs (the shape for producer steps; the prior step's output for reviewer or transformer steps).
  4. Stores the step result in step_results.
  5. If the composition declares a revision_threshold and a review step's findings exceed the threshold, the composition re-runs the producer step with the findings as additional context (bounded re-run, not infinite loop).
  6. When all steps complete, transitions the composition to completed.

Specialist contract. Producer specialists already exist. Phase 37 adds two new specialist categories:


class ReviewerSpecialist(RegisteredAgent):
    """Takes an artifact (and optionally the original spec) and produces a review."""

    async def review(
        self, *,
        artifact_ref: MemoryRef,            # The render to review
        shape_ref: MemoryRef | None,        # The original spec (may be None for some review types)
        review_criteria: dict,              # What to review for
        db: AsyncSession,
    ) -> ReviewReport: ...


class TransformerSpecialist(RegisteredAgent):
    """Takes an artifact and produces a derivative artifact."""

    async def transform(
        self, *,
        source_artifact_ref: MemoryRef,
        transformation_config: dict,
        db: AsyncSession,
    ) -> RenderEvent: ...    # Returns a new RenderEvent for the transformed artifact

Review reports as engagement artifacts. A ReviewReport is itself a RenderEvent (a render type whose shape is "review of an artifact"). The RenderEvent carries prior_render_ref pointing at the artifact reviewed, plus structured findings in the content. The Library presents review reports alongside the artifacts they review with visible lineage.

Composition endpoint. New API endpoint:


POST /engagements/{eid}/compositions
Body: {
    "confirmed_shape_event_ref": MemoryRef,
    "steps": [...],                    # The chain definition
    "revision_threshold": {...}        # Optional
}
Response: 202 with composition_id

The composition runs asynchronously. Status is polled via GET /engagements/{eid}/compositions/{cid}.

7.5 Test surface

7.6 Migrations

7.7 Dependencies

Phase 37 depends on Phase 34 (most reviewer specialists will be external services like Gemini, requiring external-service polling) and Phase 36 (revision triggers re-run the producer adapter, requiring the incremental re-render lineage).

7.8 What's deferred to a later phase


8. Phase 38 — Specification grammar declaration on DeclaredShapeType

8.1 Goal

Make the engine grammar-indifferent in a structurally defensible way. Today, the grammar a shape type produces is implicit in the shaping instructions. Phase 38 adds explicit declaration of the grammar's structural elements, completeness criteria, and testability criteria — what the engine can evaluate without running a full shaping pass.

8.2 What exists today

8.3 What's missing

The engine cannot:

8.4 What changes

DeclaredShapeType extension. Add explicit grammar declaration:


class DeclaredShapeType(MemoryObject):
    # ... existing fields unchanged ...

    # New in Phase 38:
    specification_grammar: SpecificationGrammar


class SpecificationGrammar(BaseModel):
    """Explicit declaration of what a shape type's content looks like."""

    grammar_name: str                            # e.g. "req-table", "narrative-storybook-pages",
                                                 # "procedural-3d", "legal-document"

    structural_elements: list[StructuralElement] # Required and optional sections/fields

    completeness_criteria: list[CompletenessCriterion]  # What must be present for the shape to be confirmable

    testability_criteria: list[TestabilityCriterion]    # How to evaluate a produced render against the spec


class StructuralElement(BaseModel):
    name: str                                    # e.g. "title", "abstract", "requirements_table"
    required: bool
    repeats: bool = False                        # Whether multiple instances are allowed
    description: str | None = None


class CompletenessCriterion(BaseModel):
    name: str                                    # e.g. "all_requirements_have_actors"
    description: str
    evaluator: str | None = None                 # Optional: a small DSL or script identifier


class TestabilityCriterion(BaseModel):
    name: str                                    # e.g. "every_clause_has_jurisdiction_marker"
    description: str
    evaluator: str | None = None

Grammar registry. A small registry of well-known grammar shapes. Phase 38 ships six grammars in the initial registry: REQ table (for application engagements), narrative-storybook-pages (for the Goosey engagement and similar), legal-document (for the Phase A contract template adapter), procedural-3d (for the Phase C 3D printing adapter), application-specification (for the Phase B application-rendering adapter), and scene-specification (for future video adapters; shipped now because the grammar declaration is cheap and pre-positioning it means a future video adapter attaches without re-opening the registry).

Each registered grammar carries a default SpecificationGrammar that shape types can declare against. New grammars (component-specification for design systems, protocol-structure for clinical work, regulatory-submission variants of legal-document) are added when an engagement requires them — the engine itself does not enumerate grammars beyond the initial six.


# src/loomworks/grammars/registry.py

GRAMMAR_REGISTRY: dict[str, SpecificationGrammar] = {}

def register_grammar(grammar: SpecificationGrammar) -> None:
    GRAMMAR_REGISTRY[grammar.grammar_name] = grammar

def get_grammar(name: str) -> SpecificationGrammar | None:
    return GRAMMAR_REGISTRY.get(name)

Existing shape types declare their grammar. As part of Phase 38, the existing five DeclaredShapeTypes in the Loomworks engagement and any others in active engagements declare their grammars. A migration backfills specification_grammar on existing rows.

Specialist matching. Render specialists declare what grammars they accept. The dispatch logic checks grammar compatibility:


class RenderSpecialist:
    accepts_grammars: list[str] = []  # e.g. ["legal-document", "req-table"]

# Dispatch logic gains a check:
if shape.declared_shape_type.specification_grammar.grammar_name not in specialist.accepts_grammars:
    raise GrammarMismatchError(
        f"Specialist {specialist.id} does not accept grammar "
        f"{shape.declared_shape_type.specification_grammar.grammar_name}"
    )

Completeness evaluation at confirmation time. The shape confirmation flow gains a grammar-check step: before a shape can be confirmed, its content must satisfy the grammar's completeness criteria. The check runs the criterion evaluators (where defined) and reports any failures to the Operator. The Operator can override the check (with a deliberate exception) but must do so explicitly.

8.5 Test surface

8.6 Migrations

8.7 Dependencies

None. Phase 38 is independent; it can land at any point relative to Phases 34–37.

8.8 What's deferred to a later phase


9. Phase 39 — Cross-engagement aggregation endpoints

9.1 Goal

Provide endpoints under /me/... that aggregate state across all the requesting person's engagements. The Operator Layer Dashboard shows active jobs across projects, items needing decision across projects, and recently finished artifacts across projects. Today's endpoints are all single-engagement-scoped; aggregating in the frontend by N concurrent calls per page load is wrong.

9.2 What exists today

9.3 What's missing

Aggregation endpoints that:

9.4 What changes

Three new endpoints under /me/...:

GET /me/dashboard/active — Active jobs across all the requesting person's engagements.


class DashboardActiveResponse(BaseModel):
    items: list[DashboardActiveItem]
    total_count: int

class DashboardActiveItem(BaseModel):
    engagement_id: UUID
    engagement_name: str
    item_kind: Literal["shape_production", "render_production", "composition", "external_polling"]
    item_id: UUID                                # The job or composition id
    item_label: str                              # Human-readable label
    started_at: datetime
    progress_hint: str | None

GET /me/dashboard/needs_you — Items requiring the person's decision across engagements.


class DashboardNeedsYouResponse(BaseModel):
    items: list[DashboardNeedsYouItem]
    total_count: int

class DashboardNeedsYouItem(BaseModel):
    engagement_id: UUID
    engagement_name: str
    item_kind: Literal["pending_shape", "held_assertion", "produced_render_for_review",
                       "review_report", "external_failure", "downstream_specification"]
    item_id: UUID
    item_label: str
    detail: str                                  # Brief context
    created_at: datetime

GET /me/dashboard/recent — Recently finished artifacts across engagements.


class DashboardRecentResponse(BaseModel):
    items: list[DashboardRecentItem]
    total_count: int

class DashboardRecentItem(BaseModel):
    engagement_id: UUID
    engagement_name: str
    artifact_kind: Literal["render", "review_report", "transformed_artifact"]
    artifact_id: UUID
    artifact_label: str                          # The render's title
    completed_at: datetime
    download_url: str | None                     # If downloadable

Authorization. All three endpoints require an authenticated person session (Phase 14). The aggregation is bounded to the engagements the person has membership on. Engagements where the person has only read access (per the membership designation) include only items the person can act on.

Performance. Each endpoint runs a single SQL query with engagement-scoped UNIONs or joins via the membership table. Expected latency: under 200ms for a person with 10 engagements. If this becomes a hotspot, materialized cross-engagement views are a future optimization.

Pagination. Each endpoint accepts ?limit and ?cursor for pagination. Default limit 50.

9.5 Test surface

9.6 Migrations

No new migrations — these endpoints query existing tables. New SQL queries; no schema changes.

9.7 Dependencies

None. Phase 39 is independent; it can run in parallel with Phases 34–38.

9.8 What's deferred to a later phase


10. Phase 40 — Operator-vocabulary response schemas

10.1 Goal

Implement the vocabulary wall (Operator Layer v0.6 Section 9) at the Orchestration API layer. Today's API responses use engine vocabulary throughout. The Orchestration API layer needs response schemas that translate engine vocabulary into Operator vocabulary, with the wall enforced architecturally.

10.2 What exists today

10.3 What's missing

A separate endpoint surface (the Orchestration API per v0.6 Section 10) that:

10.4 What changes

New module: src/loomworks/orchestration/. Houses the Orchestration API. Imports engine functions directly (same process), translates responses.

Project structure:


src/loomworks/orchestration/
    __init__.py
    schemas.py              # Operator-vocabulary Pydantic schemas
    translators.py          # Functions that translate engine objects to Operator objects
    routers/
        converse.py         # POST /operator/converse — the Companion brain entry
        dashboard.py        # GET /operator/dashboard — the home aggregation
        inbox.py            # GET /operator/inbox — items needing decision
        library.py          # GET /operator/library — finished artifacts
        narrative.py        # GET /operator/projects/{id}/story — project narrative
        composition.py      # POST /operator/projects/{id}/render_with_review
        downstream.py       # GET /operator/projects/{id}/implied_specifications

Operator-vocabulary schemas. Each engine concept gets an Operator-vocabulary equivalent:


# Engine vocabulary (existing):
class AssertionResponse(BaseModel):
    assertion_id: UUID
    engagement_id: UUID
    state: Literal["held", "committed", "retracted", "redirected"]
    content: str
    contributor: ContributorRef
    # ...

# Operator vocabulary (new in Phase 40):
class NoteResponse(BaseModel):
    note_id: UUID
    project_id: UUID
    status: Literal["waiting", "saved", "discarded", "moved_to_other_project"]
    text: str
    contributor_name: str
    # ...

The translation discipline is one-way: Operator vocabulary in, engine functions called underneath, engine objects translated back to Operator vocabulary on the way out. The translators handle the mapping in both directions.

Vocabulary-wall enforcement test. A new test class scans all /operator/... response schemas and verifies they contain no engine-vocabulary terms. The test maintains a list of forbidden terms (engagement, assertion, shape_event, render_event, etc.) and fails if any appear in /operator/... schema field names or default literal values.

Endpoint mapping. Each endpoint in v0.6 Section 10 gets implemented:

The engine API stays. The existing /engagements/... and /me/... endpoints continue to exist. The Workshop (existing four-room frontend) and any other consumers using engine vocabulary continue to work. The Orchestration API is additive, not a replacement.

10.5 Test surface

10.6 Migrations

No new migrations — the Orchestration API is a translation layer over existing engine state. New schemas; no schema changes.

10.7 Dependencies

Phase 40 depends on Phase 39 (the dashboard endpoint wraps Phase 39's cross-engagement aggregation) and on the existing Phase 31 conversation engine being extended to all intents (which is Operator Layer Arc 2 work, not engine work, but Phase 40's /operator/converse endpoint requires Arc 2's intent classification to actually function). Phase 40 can be substrate-built ahead of Arc 2's completion using the existing Phase 31 engagement-creation intent only, with the other intents added incrementally as Arc 2 lands them.

Phase 40 does not depend on Phase 37. The composition endpoint at Phase 40 ships as a stub returning HTTP 501; Phase 37's CR includes the step that replaces the stub with live composition orchestration. This stub-then-wire pattern lets Phase 40 ship its full endpoint surface as a stable contract before all the engine support exists, so Arc 2 and Arc 3 can build against the contract from the start.

10.8 What's deferred to a later phase


11. Sequencing summary

The seven phases organize into three swim lanes that can run in parallel, with one cross-lane dependency.

Lane A — Specialist plumbing.

Lane B — Grammar.

Lane C — Operator-Layer-facing endpoints.

Cross-lane. Phase 37 (Lane A) and Phase 40 (Lane C) interact, but Phase 40 ships its composition endpoint as a stub returning HTTP 501. Phase 37's CR replaces the stub with live composition orchestration when 37 lands. This means Phase 40 can ship in full once Phase 39 lands, without waiting on Lane A.

Operator Layer arc unblocking.

Recommended kickoff order. Five engine phases can start immediately: 34, 35, 38, 39 in parallel against engine capacity. Phase 36 starts when 35 lands. Phase 37 starts when 34 and 36 land. Phase 40 starts when 39 lands.

Once Phase 38 (grammar declaration) and Phase 39+40 (cross-engagement plus Operator vocabulary) are landed, Operator Layer Arc 1 can begin. Once Arc 1 reaches enough completeness, Arc 3 can begin. Arc 6 Phase A (contract template adapter) is gated only on Phase 38, so it can begin in parallel with Arc 1's later sub-phases.


12. What this strategy does not address

This document covers the engine prerequisites for the Operator Layer architecture. It does not cover:


13. Decisions resolved

Three decisions left open in v0.1 of this document have been resolved by the Operator. The rationale is recorded here so future readers can reconstruct why each path was chosen.

13.1 Arc 1 ships incrementally as engine phases land

Decision. Arc 1 sub-phases (1A, 1B, 1C, 1D) ship as their dependencies land rather than as one monolithic Arc 1 release.

Rationale. The engine build itself runs phase-by-phase rather than as one big release; the Operator Layer arcs should not break that pattern. More specifically: Arc 1A (the conversational endpoint) depends on Arc 2's intent classification reaching maturity, which is Operator Layer work, not engine work. If Arc 1 shipped monolithically, sub-phases 1B, 1C, and 1D would sit unshipped waiting for 1A — the opposite of how the dependency graph should drive sequencing. The dependency graph says 1B–1D ship when Phases 39 and 40 land; 1A ships later when Arc 2 catches up. The incremental approach honors the actual dependency graph.

Alternative considered and rejected. Monolithic Arc 1 release. Rejected because it would force fast-ready sub-phases to wait for slower-ready sub-phases, slowing observable progress without compensating benefit.

Implication for build sequence. Arc 1B (Dashboard endpoint), Arc 1C (Library and project narrative endpoints), and the Inbox endpoint can ship as soon as Phases 39 and 40 land. Arc 1A (converse endpoint) and Arc 1D (composition endpoint) ship later. This shows up in the Operator-Layer-side development plan in v0.6 Part II as sub-phase-specific dependencies.

13.2 Phase 40 ships the composition endpoint as a stub

Decision. Phase 40's POST /operator/projects/{id}/render_with_review endpoint ships in Phase 40 as a stub returning HTTP 501 Not Implemented with a structured response indicating the capability is pending. Phase 37's CR includes the wiring step that replaces the stub with live composition orchestration when Phase 37 lands.

Rationale. The endpoint shape is the contract that the rest of the system builds against, and the contract should be locked in early. If Arc 2's companion offers "I can have Gemini review this" and Arc 3's frontend shows a "Run review" button before the endpoint exists, they'll either invent placeholder shapes that turn out to be wrong, or they'll hold off on the parts that depend on the composition endpoint. Both are worse than building against a stub that returns 501. When 501 turns into real responses, no caller has to change.

Precedent. Phase 3's BackgroundAgentRunner shipped with the seam in place but used synchronously, and later phases filled in the async behavior without callers having to change. Phase 40's stub composition endpoint follows the same pattern.

Alternative considered and rejected. Wait for Phase 37 to land before Phase 40 ships the composition endpoint, in a follow-on phase. Rejected because it delays the contract that Arc 2 and Arc 3 build against, increasing the risk that those arcs build against speculative shapes that turn out wrong.

Implication for build sequence. Phase 40 can ship in full once Phase 39 lands, without waiting on Phase 37. Phase 37's CR includes the stub-replacement step.

13.3 Phase 38 ships six grammars

Decision. Phase 38's grammar registry ships with six grammars: REQ table, narrative-storybook-pages, legal-document, procedural-3d, application-specification, scene-specification.

Rationale. The first five cover the existing engagements (REQ table for the application engagements; narrative-storybook-pages for Goosey) and the Phase A through Phase C adapters (legal-document for Phase A; application-specification for Phase B; procedural-3d for Phase C). That's the immediate must-have set.

The sixth — scene-specification — is added because video and animation are named in v0.6 Section 15.6 as deferred adapter categories that are likely to arrive eventually. The grammar declaration costs almost nothing to ship (it's a small data structure with structural elements, completeness criteria, testability criteria), and shipping it now means a future video adapter attaches without re-opening the registry. Not shipping it would mean a future Phase 38b. The grammar declaration is cheap; the future re-opening is not.

Grammars not shipped in Phase 38, and the reason. Component-specification (design systems), protocol-structure (clinical work), and regulatory-submission variants of legal-document are deferred. Unlike video, these don't have any current trajectory — no existing engagement uses them, no near-term adapter requires them. The deferral discipline says we add grammars when an engagement requires them, and these don't yet have requiring engagements.

Alternative considered and rejected. Ship only the five immediately-required grammars (no scene-specification). Rejected because the cost of pre-positioning scene-specification is small and the cost of re-opening the registry later is larger.

Implication for build sequence. Phase 38's CR will register six grammars at shipment. New shape types declared after Phase 38 must declare against one of these six (or a newly-registered grammar; the registry is extensible).


14. Closing

Seven engine phases. Three lanes. Most run in parallel. The longest dependency chain is Phase 35 → Phase 36 → Phase 37 (specialist plumbing, three phases serial). The other lanes are independent and can start immediately.

Each phase is medium-sized — comparable to the engine phases that came before. Each has a clear data model change, a clear test surface, and clear dependencies. None of them require an engine redesign; all of them are extensions of existing patterns that the engine has been hardening across 33 phases.

With the three decisions in Section 13 resolved (Arc 1 incremental, Phase 40 composition endpoint as stub, Phase 38 ships six grammars), the strategy is ready to drive CR drafting. Each phase has a stable design intent; the next move for any phase is a CR drafted from the relevant section in this document, adding the order-of-operations details, the kickoff prompt, the auto-mode posture, and the per-step test verifications.

The Operator Layer architecture is buildable. The engine work it requires is shaped to fit the engine that exists rather than the engine that would be most convenient. The dependencies are clear. The decisions are made. The work can begin.


DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — Engine Implementation Strategy — v0.2 — 2026-05-03