Version. 0.1 Date. 2026-06-03 Provenance. Engine-source audit, Operator: Marvin Percival. Increment 0 of the engine boundary-enforcement project (M2 / C0 in the master build plan). Status. Investigation / parity audit — report only. NO code, NO API design, NO commits. This commits to nothing; it establishes what the API must expose at parity before any later increment moves code. Cross-links.
investigations/loomworks-engine-boundary-inventory-v0_1.md — the current-state finding this expands (UI is a client; brain is an in-process insider).planning/loomworks-engine-boundary-enforcement-plan-v0_1.md — the sequenced plan; this is its Increment 0.standing-notes/loomworks-standing-note-engine-wrapper-boundary-v0_1.md — the lens (engine must survive any wrapper).
Code citations. engine: = loomworks-engine repo at time of writing. Line numbers indicative; names load-bearing. The privileged delegations and the commit_engagement/WebAuthn finding are source-verified directly; the broad orchestration-router call inventory (§1c) is from a thorough sweep and should be spot-verified before the API contract is finalized.
The boundary project ends with the Companion's brain refactored to consume the engine through the API as a normal wrapper — not co-resident, no privileged in-process shortcuts. That refactor is the endpoint, not optional (Operator direction, 2026-06-03). Therefore: **every in-process privilege inventoried below is something the refactored Companion will instead do through the API.** This audit is the parity checklist for that refactor — the API must expose, with equivalent capability and no insider shortcut, everything the brain reaches in-process today. Where it can't yet, that is a named hole the later increments must fill.
"The brain" = the orchestration layer at engine: src/loomworks/orchestration/ (the /operator/* HTTP surface + its router.py / prompt.py / helper modules). It is co-resident in the engine process and reaches engine capabilities by direct Python calls, not via the engine's own HTTP API.
These call functions that are themselves engine HTTP route handlers, but reach them in-process with auth/billing/transaction privileges an external HTTP client could not replicate.
| Brain call site | Engine handler (its HTTP route) | Privilege used that a client can't replicate | Parity gap (what the API must offer) |
|---|---|---|---|
| converse.py:185 _call_phase31_converse → converse_route(_check=None, _internal_delegation=True) | converse_route = POST /engagements/{id}/seed/converse | auth skip (_check=None bypasses assert_engagement_creator) + billing/usage skip (_internal_delegation=True skips re-opening the usage buffer and firing the consumption hook, so a multi-step turn bills once in aggregate) + shared session | acting-as-person auth; a composite/aggregated billing model a client can drive; (transaction — see §2 cross-cutting) |
| converse.py:232 _call_seed_commit_from_brief → commit_seed_from_brief(_check=None) | commit_seed_from_brief = POST /engagements/{id}/seed/converse/commit | auth skip (_check=None) + shared app.state runner (runner resolved from request.app.state / dependency_overrides) + in-process-built SeedInductionAgent | auth; a way for a client to drive induction without reaching into app.state |
| converse.py:302 _call_extract_seed_from_discovery → extract_seed_from_discovery(_=None) | extract_seed_from_discovery = POST /engagements/{id}/seed/extract | auth skip (_=None skips the redundant assert_engagement_creator) + shared runner/agent | auth |
| converse.py:371 _call_create_engagement_from_conversation → create_engagement_from_conversation(...) | create_engagement_from_conversation = POST /me/engagements/create-from-conversation | shared session + runner via dependency_overrides + person object passed directly (no token) | auth; runner provisioning for a client |
| router.py:971 _route_create_project → draft_engagement(...) | draft_engagement = POST /engagements | person object passed directly + shared session (no HTTP auth) | acting-as auth |
| inbox.py:32 → list_needs_you_route(...); dashboard.py:18 → list_active_route / list_needs_you_route / list_recent_route | dashboard routes = GET /me/dashboard/{active,needs_you,recent} | shared session + person (no HTTP auth) | acting-as auth (capability already routed; the gap is the auth context) |
finalize_project bypasses the WebAuthn commit ceremony
router.py:1020 _route_finalize_project imports and calls commit_engagement directly (engine: engagement/creation.py).
commit_engagement has NO direct HTTP route. Its only HTTP path is POST /engagements/{id}/instantiate (instantiate_engagement, engine: api/routers/engagements.py:467), which enforces the WebAuthn commit ceremony via Depends(get_committing_contributor), Depends(assert_engagement_creator), Depends(get_commit_challenge_store), Depends(get_webauthn_config).finalize_project calls commit_engagement directly, skipping that ceremony entirely./instantiate and perform WebAuthn; the insider does not. For the brain to become a client, either it performs the WebAuthn ceremony like any client, or the commit-without-ceremony path is deliberately specified — this is a security-bearing decision, not a mechanical one.
(Same pattern, lower stakes: me_create_engagement.py:723 also calls commit_engagement directly inside the create-from-conversation endpoint — but that endpoint is at least HTTP-reachable; the finalize_project route reaches the core function with no ceremony at all.)
The brain also reaches many engine core functions that are not HTTP route handlers — reachable only in-process. (Inventory from a thorough orchestration sweep; classifications to spot-verify before the contract is finalized.) Grouped by what an external client would need:
| Engine capability (core fn) | Reached from | Route today? | Parity gap |
|---|---|---|---|
| commit_engagement | router.py:1028, me_create_engagement.py:723 | only via /instantiate (WebAuthn) — bypassed (§1b) | a client-reachable commit path + the WebAuthn decision |
| _load_candidate_seed | router.py:1027, others | no route | a candidate-seed read route |
| _current_manifestation | prompt.py:42 (context assembly) | no route (Manifestation read) | a manifestation read route the prompt-builder wrapper can call |
| get_current_seed | prompt.py:43 | ~ GET /engagements/{id}/seed (seed_view) exists | likely covered — verify the payload matches what prompt assembly needs |
| list_assertions | prompt.py:41, router.py (ask_about_past_input) | assertions router exists (GET) | likely covered — verify |
| add_assertion / commit_assertion / retract_assertion / revise_assertion | router.py (add_knowledge, remember_about_me, forget_about_me), inbox.py | contribution/assertion routes exist for some | partial — verify each verb has a route a client can call |
| _retire_render, decide_composition, open_shape_confirmation_consideration, close_shape_confirmation, get_shape_event | inbox.py (the /operator/inbox/{id}/respond action) | no / partial routes — the inbox respond endpoint calls engine core directly | routes for the inbox actions, or the inbox stays engine-side and the brain calls it as a client of /operator/* |
| notification service fns (list_notifications, mark_seen, mark_dismissed, create_approval_card, process_approval, process_decline, …) | notifications.py | to-verify (notifications routes may exist) | verify the full set is client-reachable |
| delegation fns (verify_companion_authorization, execute_pre_authorized, list_delegations, revoke_delegation) | router.py (request_draft, remember/forget_about_me) | to-verify | verify routes exist for the delegation operations |
| open_composition | composition.py | = POST /engagements/{id}/compositions (engine route exists) | covered — auth-context gap only |
| context-tier loaders, determine_context_tier, personal-memory SQL (prompt.py) | prompt.py | no route (direct SQL on current_memory_objects) | read routes for the context the prompt assembler needs |
AsyncSession, so multi-step flows (converse→commit, create-from-conversation) are atomic for free. An external client making N HTTP calls gets N transactions — atomicity lost. Gap: composite endpoints, or idempotent/compensating semantics.app.state / runner / dependency_overrides. Induction runners and the agent are resolved from request.app.state / dependency overrides — unavailable across a process boundary. Gap: a client-drivable way to run induction.person object directly and/or _check=None; there is no token/service-principal by which a client authenticates as the person so the gated routes pass legitimately. Gap: acting-as / delegated auth — the universal precondition._internal_delegation=True exists precisely to bill a multi-step turn once; an external client has no equivalent. Gap: a billable-operation model.What the engine HTTP API actually exposes today, focused on the surface a wrapper needs. No new endpoints designed here — this describes what exists and names the holes.
Shape. One FastAPI app (engine: api/app.py, title="Loomworks", versioned by package version — no /v1, no governed contract), ~50 routers / ~197 routes mounted flat (boundary inventory §1). Auth is per-route via FastAPI deps (get_current_person, assert_engagement_creator, require_operator_designation). The OL frontend already consumes this surface as a remote HTTP client.
Route families a wrapper would need, and their parity status:
| Capability family | Routes that exist | Parity status for a client brain |
|---|---|---|
| Engagement draft / candidate | POST /engagements, POST /engagements/{id}/seed (amend), POST /engagements/{id}/seed/induct, GET /engagements/{id}/seed, GET /engagements/{id}/seed/findings | mostly complete — but the brain reaches draft_engagement in-process and reads candidate seed via _load_candidate_seed (no read route for the candidate); auth-context gap |
| Conversational creation | POST /engagements/{id}/seed/converse (+ /conversation, /converse/ready, /converse/transcribe, /converse/key-provided), POST /engagements/{id}/seed/converse/commit, POST /engagements/{id}/seed/extract, POST /me/engagements/create-from-conversation | routes exist — but all reached in-process with auth/billing bypass (§1a); the capabilities are routed, the parity is not (auth, billing, transaction) |
| Commit / instantiate | POST /engagements/{id}/instantiate (+ /instantiate/divergent, /instantiate/challenge) — WebAuthn-gated | route exists but BYPASSED — finalize_project calls commit_engagement core directly, skipping WebAuthn (§1b). The hole is not a missing route; it's the insider bypass of the route's ceremony |
| Dashboard / inbox | GET /me/dashboard/{active,needs_you,recent} exist; inbox respond actions (retire render / decide composition / shape confirm) have no/partial engine routes | partial — dashboard routed; the inbox-respond engine actions are reached as core functions |
| Memory / context for prompt assembly | GET /engagements/{id}/seed exists; assertions GET exists; Manifestation read has no route; personal-memory read is direct SQL | hole — the context the brain assembles (_current_manifestation, tier loaders, personal-memory SQL) has no read API |
| Notifications / delegation | to-verify (some routes likely exist) | verify — confirm the full notification + delegation operation set is client-reachable |
| Shapes / renders / compositions | POST /engagements/{id}/compositions exists; render/shape views exist; _retire_render core | partial — composition creation routed; retire/shape-confirm actions reached as core |
The holes, summarized:
commit_engagement reachable only via WebAuthn /instantiate, but bypassed in-process — the parity question is whether a client commits via WebAuthn or a specified non-ceremony path (security decision)._current_manifestation, candidate-seed read, context-tier loaders, personal-memory (direct SQL)._retire_render, decide_composition, shape-confirmation) reached as core functions — no client route, or the inbox stays engine-side._internal_delegation.app.state / dependency_overrides)./v1, completeness guarantee).Every row in §1 is work the refactored Companion will do through the API. The §1d cross-cutting privileges + the §2 holes are therefore the parity preconditions for the brain-to-client refactor: until acting-as auth (hole 1), the commit-path decision (hole 2), the context read routes (hole 3), the inbox-action routes (hole 4), composite/transaction semantics (holes 5/7), and a billing model (hole 6) exist, the brain cannot become a pure client without losing capability or smuggling privilege. The audit makes that list explicit so the later increments fill it deliberately, and so "the Companion is now a client" can be checked against a concrete parity list rather than asserted.
This is Increment 0: it names the surface and the holes. It designs nothing and commits to nothing.
Investigation / parity audit only. No engine or wrapper code changed; no API designed; nothing committed. Filed in loomworks-record/investigations/. Increment 0 of planning/loomworks-engine-boundary-enforcement-plan-v0_1.md. Pairs with investigations/loomworks-engine-boundary-inventory-v0_1.md and standing-notes/loomworks-standing-note-engine-wrapper-boundary-v0_1.md (the lens). §1c classifications are from a thorough sweep and warrant spot-verification before the API contract is finalized.