Version. 0.1 · Date. 2026-08-10 · Author. Claude Code (drafting, from the 2026-08-10 scoping read) / Marvin Percival (approving).
Target. /Users/dunin7/loomworks-engine. Surface work is verify-then-do (§5 Step 5); /Users/dunin7/loomworks is touched only if the verify fails.
Baseline. Engine d85154d, surface 62b2f27. CC confirms both at Step 0. The live surface is /Users/dunin7/loomworks — not loomworks-ui, which is six weeks stale.
Companion to. scoping-notes/loomworks-b8-slice-two-scoping-note-v0_1.md (the framing; its §6 lean is inverted by the code read recorded in build list v0.55) and the B-8 entry in standing-notes/dunin7-build-list-v0_55.md (the rulings). Every file:line in this CR was read at the baseline SHA; where the CR and the code disagree, the code governs and CC reports the divergence.
Charter. Autonomous regime. Status. DRAFT — awaiting the gate. Do not execute.
Operator rulings, 2026-08-10:
pre_authorized stays supported in the machinery but nothing ships using it in this slice. A standing grant can be created in one conversational sentence and makes dispatch immediate with only an informational row — authority given before anyone has watched the Companion write once. Loosening later is cheap; the reverse is not.delegation/schema.py:33-45), per the single-source-of-truth vocabulary discipline: produce_specification = shape production with specification grammar; produce_artifact = render production for a non-specification artifact; initiate_render = ad-hoc/re-run render from the revision path. The docstrings are the declared meaning of the granted authority; the dispatchers must not reinterpret them.execution_result (and the Companion's reply) names exactly what was resolved and enqueued. Moving resolution to propose time — so the card itself names the target — is the tightening move if this proves too blind; it is not built now.produce_artifact is a render capability by declaration. The Shaping room surface remains the way to produce non-spec shapes.
Slice one taught the Companion to describe the rooms. The path for it to act is fully built and ends in a deliberate hole: classification (request_draft / request_revision), default-deny authorization (verify_companion_authorization, delegation/authorization.py:98), conversational grant/revoke, approval cards with click-time re-verification (notifications/service.py:702-728), and audit-grade ActorRef attribution (memory/base.py:96-153) all exist and run — but dispatch_action finds no handler for any Tier-1 capability and returns {"status": "failed", "reason": "No dispatcher registered…"} (delegation/action_dispatch.py:170-180). The only registered dispatchers are the three credit-layer ones.
This CR registers the three Tier-1 dispatchers — produce_specification, produce_artifact, initiate_render — and forces per-action approval for them at the registry level. No new intent, no new capability, no new consent machinery: companion-triggered jobs inherit the existing spend gates unchanged, including the pre-debit spend-pause park with its cap-named card.
Engine, four pieces:
_DispatcherEntry (action_dispatch.py:73-87) gains per_action_only: bool = False; register_action_dispatcher (:93-123) gains the matching keyword; a small accessor exposes it. _route_request_draft (router.py:2043) and _route_request_revision consult it before the pre_authorized branch: when the entry demands per-action, an authorization that came back pre_authorized is handled on the approval-card path instead, and the Companion's reply says so plainly (the standing grant is honored as authority, not as immediacy). execute_pre_authorized is not called for such entries from any path.src/loomworks/agents/companion_room_dispatchers.py — with three handlers matching the dispatcher contract exactly (async def …(*, action_params: dict, actor: ActorRef, engagement_id: UUID, db: AsyncSession) -> dict, per action_dispatch.py:148-165), a configure_companion_room_dispatch(agent_runner=…) mirroring configure_spend_pause_dispatch (app.py:160-163), and a register_companion_room_dispatchers() called from the lifespan next to the existing three registrations (app.py:107-127). All three register with delegation_required=True (the default) and per_action_only=True.produce_specification (action_params = {"shape_type", "grammar"?} per router.py:2030-2033): active DeclaredShapeType matched on shape_type_name (case-insensitive, trimmed) via list_declared_shape_types, requiring specification grammar; → the active Shaping bound to it via list_shapings; → the current Manifestation; → version-pinned refs into request_shape_production(engagement_id=…, manifestation_ref=…, shaping_ref=…, triggered_by=actor, trigger="agent_request", runner=…, db=…) (engagement/shaping.py:146-156 — refs must be pinned or it raises ValueError). Returns {"status": "completed", "shaping_job_id": …, "shape_event_object_id": …} from the ShapeProductionTicket.produce_artifact (same action_params shape; the value is an artifact label like "report"): active DeclaredRenderType matched on its render-type name via list_declared_render_types; → the most recent confirmed shape event of the DRT's source_shape_type_ref type; → open_render_production(engagement_id=…, confirmed_shape_event_ref=…, declared_render_type_ref=…, triggered_by=actor, db=…) (engagement/render.py:268-275; no runner — it resolves the registered RenderDispatchAgent itself). No confirmed shape of the right type is a named failure explaining what must exist first, not a fallback.initiate_render (action_params = {"target_description", "revision_guidance"?} per router.py's request-revision branch, whose own comment already states this contract: the router forwards the natural-language target and the dispatcher does the substrate lookup): resolve target_description against confirmed shape events (title, then type name); DRT resolution as above; same open_render_production call. revision_guidance is carried into the render request where the engine surface accepts guidance; if it does not at the baseline, it is recorded in execution_result as not-applied — never silently dropped.ShapingStateRefusedError, UnproducibleShapeTypeError, RenderDispatchAgentNotRegisteredError, UnproducibleRenderTypeError, AdHocRenderNotSupportedError) are caught into graceful {"status": "failed", "reason": …} results carrying the exception's own sentence — dispatch_action would catch them anyway (action_dispatch.py:184-189), but catching them in the handler names them properly instead of as repr(exc).ActorRef(kind="companion", capability_ref=auth.delegation_ref, approval_mode="explicit") and re-verifies authorization at click time (notifications/service.py:702-728). The dispatchers pass that actor through as triggered_by, so every companion-triggered job and event carries the authorizing delegation's id. Nothing new is built; the acceptance gate proves it arrives.
Spend posture — nothing new, both existing gates bind, and this is load-bearing: the jobs run gated_room_complete with room="shaping" / "rendering" exactly as surface-triggered jobs do. The agent_spend_authorized license (credit/authorization.py, default-absent = deny, covering companion actors) must be on for the engagement or the job fails with the refusal recorded; with spend_pause on, the job parks pre-debit and the existing cap-named card (credit/spend_pause_card.py) goes to the Operator. A Companion-triggered draft therefore costs nothing until the Operator has approved twice: once for the act, once (if pause is on) for the capped spend.
pre_authorized shipping — the mode remains in the machinery, exercised by existing tests, reachable by no registered Tier-1 dispatcher.request_draft / request_revision already classify and route.commit_notes gets no dispatcher; the two-layer human-only guard on commit_assertion (assertions.py:230-247) is out of reach and stays that way.test_new_room_read_intents_never_reach_a_write stays as written and must stay green; the three read intents still never write. (The scoping read initially assumed this test must change; it must not — the writes enter through different intents.)
The Q-7 conversational confirm; B-77's cache remedy; B-78; B-79; any surface redesign of the inbox cards; the sync→job re-architecture; the request_redirect stub.
d85154d, surface 62b2f27, clean trees). Confirm the hole: grep shows no register_action_dispatcher call for any Tier-1 capability outside tests. Confirm companion_notifications.action_type column width admits the three capability strings — they are already written by the card-creation path today, so this is a re-verify, not a risk (pre-flight shape 4).action_dispatch.py, router.py, and the request_draft / request_revision instruction templates only if their prose contradicts the new behavior.app.py:107-127); configure with the runner after it is built (app.py:158-163 pattern).
a. Per-action delegation committed; converse "draft a specification" → outcome: "approval_card", card row exists, no shaping_jobs row.
b. Approve the card → shaping_jobs row exists; the shape event's triggered_by has kind="companion" and capability_ref equal to the delegation assertion id; execution_result carries the job and object ids.
c. Decline → no job, card marked declined.
d. Pre-authorized delegation present → still an approval card, and the reply states the posture. execute_pre_authorized is not called (assert via recording stub).
e. No delegation → outcome: "denied" with the grant instruction (existing behavior, regression-pinned).
f. Resolution failures: no matching declared type / no active Shaping / no current Manifestation / no confirmed shape — each its own test asserting its own sentence.
g. agent_spend_authorized absent → the dispatched job fails with the refusal recorded and visible (the metered branch proven reachable from the conversation entry point; the refusal, not a 502, is what surfaces).
h. spend_pause on → job parks pending_approval, spend-pause card created, no debit — companion-triggered jobs inherit the park unchanged.
i. Own-key control: an engagement bringing its own key dispatches unmetered, no card, no park (standing-note item 5).
j. Revoked-between-card-and-click → denied at approve (existing service.py:702 behavior, regression-pinned for these action types).
k. Slice-one guard test untouched and green.
/Users/dunin7/loomworks src/components/notifications/InboxView.tsx) renders a card whose action_type is a Tier-1 capability with approve/decline reachable — the generic proposal path may already cover it. If it does not render, the fix is the smallest component addition that does, and nothing more.Recorded per the B-69 standing instruction; entries state their Kind.
get_current_seed, _load_seed_by_ref, seed_ref fields) — no new call sites.seed_version_at_production (agents/shaping.py:699-703); companion-triggered productions stamp it identically. Seed-aware, not seed-dependent, per the CR-2026-190 worked example.spend_pause on, the cap-named card appears before any debit.agent_spend_authorized, the job's failure is visible and names the spend refusal.pre_authorized grant does not bypass the card for these three action types — demonstrated in the product, not only in tests.
Execute CR-2026-197 v0.1 at
change-requests/cr-2026-197-loomworks-b8-slice-two-companion-drafts-v0_1.md
in loomworks-record. Confirm the CR number and both baselines first.
§0 carries four Operator rulings and §0b three drafting decisions the
gate approved. Don't re-raise any of them.
The live frontend is /Users/dunin7/loomworks. loomworks-ui is stale —
do not read it as the surface.
The tests that matter are 4d and 4g: (d) proves per-action is forced at
the registry, not promised in prose — execute_pre_authorized must be
shown NOT called; (g) proves the spend refusal is reachable from the
conversation entry point, which unit coverage against the gate cannot
show.
HALT if resolution needs a judgment call the CR doesn't decide —
ambiguity fails by design (§0b.2); do not add a heuristic to make a
test pass.
DUNIN7 — Done In Seven LLC — Miami, Florida CR-2026-197 — B-8 slice two, part one — v0.1 — 2026-08-10 Per-action only: the Operator watches the first writes happen one approval at a time.