What this CR does. Phase 64 built the spend mechanism — the gate, the metering, the FIFO draw-down — as substrate only. A pipeline run today spends silently after a one-time up-front authorization; the Operator sees nothing at spend time. This CR builds what the Operator sees and approves:
What it does NOT do. It does not pause synchronous Manifestation spend — that needs a sync-request→job re-architecture, deferred to Phase B. Manifestation shows-and-proceeds; the help text says so plainly.
The load-bearing piece. The async park/resume mechanism is net-new substrate (Phase 64's jobs run to completion or fail; nothing parks for an Operator decision and resumes). It gets its own checkpoint (Step 3).
One scope adjustment found while drafting (§3.3). The scoping note assumed visibility needs no substrate change. It needs a thin one: the actual per-run cost is currently exposed by no endpoint (only aggregate balance via /me/credits). Step 1 therefore adds a small read endpoint. Balance movement is frontend-derivable; the cost figure is not.
What the Operator does. Review this CR; on approval it goes to Claude Code, which builds across the numbered steps with a halt at the Step 3 checkpoint.
Phase 64 (CR-2026-095, tag phase-64-pipeline-spend-credit-gate, manifest v0.45) shipped the pipeline-spend gate as substrate only — the CR names "No Operator Layer frontend" in §1, §3.1, §23. Phase 66 builds the Operator-facing surface that completes the loop: the Operator sees spend, and — until they choose otherwise — approves async spend before it debits.
The design is an Operator-set preference, not an inferred gate (the scoping-note discovery trajectory §7 records why every inference required a substrate signal that does not exist on the hot path — decisively, no committed/purchased credit exists yet, so the funding-source distinction a gate would key on is empty). The Operator declares the behavior; the gate obeys.
persons field mirroring exhaustion_preference's shape plus a server_default so it defaults on. Settings toggle + PATCH /me/settings.max_tokens ceiling), not exact cost. Carries approve / decline / stop-pausing; the third writes the preference off.main at 95e072d, alembic head 0076 (Phase 64 landed). Operator Layer main at 1ce2542. Manifest v0.45; latest closed phase 64.main. Two pinned engine test failures accepted (test_api_assertion_retract, test_phase_44_evaluator dedup) — not regressions.gate_room_spend(, person_id, engagement_id, actor_kind, key_source, db) -> GateDecision (credit/gate.py); gated_room_complete(, ctx, room, fallback_llm, system_prompt, user_prompt, max_tokens=4096, temperature=None, db) (credit/room_gate.py). Pre-debit park point is inside gated_room_complete, between the gate check (:96) and the model call (:121). Three call sites: agents/shaping.py:535, agents/render_specialist.py:921, engagement/manifestation_organization.py:421.shaping_jobs = queued/dispatched/completed/failed — no parked state, no resume. render_jobs adds awaiting_external (Phase 34) — a park precedent, but resume is poll-driven, not approval-driven (re-dispatches a fresh continuation). Dispatch is runner.dispatch(agent_fn, …) (engagement/executor.py:108, BackgroundAgentRunner agents/runner.py:81).persons.exhaustion_preference = TEXT NULL + CHECK (ck_persons_exhaustion_preference), migration 0064, no server_default.loomworks/src/app/settings/components/ExhaustionPreferenceSection.tsx (client component, reads via AuthProvider /me, PATCHes on select).action_type (approve handler + optional on_decline); dispatch_action(*, action_params: dict, …) passes card action_params (JSONB) into the handler. Card/notification infra: notifications/{models,schemas,service}.py; approve route at api/routers/authority.py:105; registry delegation/action_dispatch.py.0077.The actual per-run cost is exposed by no endpoint today. Only GET /me/credits exists (aggregate per-asset balance); the converse response does not carry the spent cost; gated_room_complete computes credit_debit but discards it (it returns the CompletionResult, not the debit — room_gate.py:130). The consumption flows are written to credit.flows but nothing surfaces them.
Therefore the scoping note's "no substrate change is required for visibility" (§2.1) holds only for balance movement (derivable from /me/credits before/after). The actual per-run cost figure is net-new substrate. Resolution (recommended): Step 1 adds a thin read endpoint GET /me/spend returning recent consumption transactions (per transaction_id: room, credit asset_id, credit_debit, occurred_at, and the balance-after) — additive, read-only, serves both the per-run cost and the balance movement cleanly. Alternative, lighter: thread credit_debit out of gated_room_complete onto the room's produced event so the existing room read path carries it. Step 0 confirms the choice with the Operator.
CC re-confirms baseline + §3.2 names at the build SHA, resolves the §3.3 read-endpoint choice, confirms no spend_pause/park-state structures pre-exist, and archives this CR to docs/phase-crs/ (engine) and docs/phase-crs/-equivalent. Halt-and-amend if the §3.2 load-bearing items (gate seam, job dispatch, ApprovalCard registry) diverge.
server_default.max_tokens) is the only pre-run-true number. Actual shows post-run on the visibility surface.awaiting_external's fresh-continuation precedent and the runner.dispatch model, and avoiding mid-function state capture. The cap is a ceiling, not a commitment, so a re-run landing under a different actual cost is acceptable (and never exceeds the approved cap, which the re-run re-checks). Cache-and-resume is recorded as the more-faithful-but-more-complex alternative; adopt only if caching proves cheap at Step 3 — otherwise re-cost. Step 3 halt threshold covers this fork.GET /me/spend (engine) returning the current person's recent consumption transactions — for each, transaction_id, room/pipeline_stage, credit asset_id, credit_debit, occurred_at. Reads credit.flows (event_kind='consumption_credit') for the person; no write.loomworks): a spend readout (where the Operator sees recent runs' actual cost + balance), polling /me/spend + /me/credits. This is also the home of the re-enable affordance (Step 5). Ships independent of the pause preference.0077: persons.spend_pause TEXT NOT NULL with CHECK IN ('on','off') and server_default 'on' (the one deviation from the exhaustion_preference mirror; existing rows backfill to 'on'). (A boolean column is the alternative shape; TEXT+CHECK mirrors the precedent — Step 0 may confirm.)PersonRow; surface on the /me payload (AuthProvider reads it); PATCH /me/settings accepts it.pending_approval on both shaping_jobs and render_jobs (distinct from awaiting_external), via migration (the CHECK lists + a mark_pending_approval writer). Migration 0078.gated_room_complete, when key_source == 'system', the gate passes, and the person's spend_pause == 'on' and the room is shaping/rendering → raise a new RoomSpendPauseRequired(cap, …) before the model call (§3.2 insertion point). Manifestation (the third call site) never raises it.produce_shape / produce_render catch RoomSpendPauseRequired ahead of their existing handlers → mark_pending_approval(job_id, cap, …) and surface an ApprovalCard (Step 4) carrying the job id + the cost cap. No debit occurs.runner.dispatch) — the agent re-runs the gate + model, which on a now-not-pausing path (pause consumed for this run via a one-shot token, or the approval recorded against the job) proceeds to spend. The resume re-checks balance/authorization at re-entry (the cap is re-derived; actual spend ≤ cap).on_decline) marks the job failed (or a declined terminal) — no debit, the Operator sees the decline on the visibility surface.action_type='spend_pause_approval'; action_params carry {job_id, room, asset_id, cap, …}. Registered via register_action_dispatcher('spend_pause_approval', approve_handler, delegation_required=False, on_decline=decline_handler) (Operator-direct, mirroring reconciliation's delegation_required=False).spend_pause='off'; the off-switch rides the approve action (action_params remember_off=true consumed by the approve handler) and resumes the run — or a separate PATCH /me/settings fired client-side alongside approve. Step 4 picks one (the action_params ride is preferred — no second round-trip).ApprovalCard-family component renders the three actions.ExhaustionPreferenceSection.tsx (single toggle on/off, PATCHes the engine, reads current via AuthProvider). Help text, verbatim intent: pause applies to rendering and shaping; Manifestation shows spend and proceeds until Phase B.0077 — persons.spend_pause (TEXT, CHECK, server_default 'on'; backfill existing rows to 'on'). Down: drop column + CHECK.0078 — pending_approval state on shaping_jobs + render_jobs (CHECK extension + any state-bookkeeping columns the resume needs, e.g. cap). Down: revert CHECK, drop added columns. (Step 0 may combine 0077/0078 or keep separate; separate is cleaner since Step 2 ships before the Step 3 checkpoint.)Auto-mode posture: Steps proceed in auto-mode; the Step 3 checkpoint halts for Operator confirmation; the final tag executes after Operator approval.
GET /me/spend (§5.1). Engine read endpoint + frontend readout + re-enable affordance shell. Ships value alone; no pause dependency.0077 (default ON), ORM, /me payload, PATCH /me/settings.pending_approval state (migration 0078), pre-debit park branch (shaping/rendering only — must not touch Manifestation), approval-driven re-cost resume (D7), decline path. Halt and report at Checkpoint: park parks before debit, approve resumes-and-spends ≤ cap, decline ends with no debit, Manifestation untouched, shaping + render both covered, suite green.action_type='spend_pause_approval', three actions, off-switch on approve.phase-66-spend-visibility-and-pause (post-checkpoint, after Operator approval).Confirm alignment with loomworks-candidate-seed-v0_9: default-on pause = "the Operator approves spend" as the out-of-box stance (seed: "the system surfaces and signals; the Operator approves"); in-card off-switch = Operator authority to relax; "only show what is available" = cards stop appearing when pause is off. If any requirement would introduce an automatic spend state-transition the Operator has not authorized, raise it against the seed before implementing (the gate already requires up-front authorization; pause adds, not removes, Operator control — so this is expected to pass).
GET /me/spend returns the person's recent consumption (per-run credit_debit, room, asset, time); the visibility surface shows actual per-run cost + balance movement, all metered rooms.persons.spend_pause exists, TEXT CHECK ('on','off'), defaults 'on'; existing rows backfilled 'on'; PATCH /me/settings updates it; migration 0077 round-trips.on, an async Shaping or Rendering run parks before debit (pending_approval) and surfaces an approval card showing the cost cap ("up to N"); no debit before approval.spend_pause to 'off' (in place) and resumes; subsequent async runs spend without a card.off, async spend proceeds and is shown, not paused.ExhaustionPreferenceSection; the re-enable affordance turns pause back on from the visibility surface.0078 (park state) round-trips; pending_approval distinct from awaiting_external.0078.Engine: GET /me/spend; persons.spend_pause (default on); pending_approval job state + approval-driven re-cost resume; alembic 0078. Operator Layer: a spend-visibility surface (actual cost + balance movement + re-enable), an approval card (cap + approve/decline/stop-pausing), a settings toggle. The Operator now sees spend on every metered room and approves async spend until they graduate themselves off. Manifestation pause remains Phase B. Tag phase-66-spend-visibility-and-pause.
(Restated for Checkpoint clarity.) Synchronous Manifestation pause (Phase B); funding-source/commitment-keyed gate; per-lot/"free credits remaining" readouts; credit purchase (Phase 65); real-currency cost display; multi-Operator approval delegation.
Read CR-2026-096 (Phase 66 — Spend Visibility and the Pause Preference) at
loomworks-record/phases/phase-66-spend-visibility-and-pause/loomworks-phase-66-cr-spend-visibility-and-pause-v0_1.md,
and the scoping note + handoff v0.2 beside it.
Phase 66 builds the Operator-facing surface Phase 64 left out: spend visibility on
every metered room, and a default-ON spend-pause preference that parks async
Shaping/Rendering spend behind an approval card showing the cost CAP (exact cost is
unknowable pre-run), with an in-card off-switch. Manifestation shows-and-proceeds
(pause is Phase B). Primary repo loomworks (frontend); substrate changes in
loomworks-engine (the spend read endpoint, the spend_pause field, the async
park/resume). Baseline: engine main 95e072d / alembic 0076; OL main 1ce2542.
Run Step 0 pre-flight per §3.4 — confirm the §3.2 names hold at the build SHA,
resolve the §3.3 read-endpoint choice and the §5.4 off-switch mechanism, archive
the CR. HALT and amend if the gate seam, async job dispatch, or ApprovalCard
registry diverge.
Build Steps 0–5 per §20. STEP 3 (async park/resume) IS ITS OWN CHECKPOINT — halt
there and report before continuing. Resume strategy is RE-COST (re-dispatch the
agent after approval) unless caching proves necessary (§20.1 fork). The park branch
MUST NOT touch the synchronous Manifestation path. Halt thresholds per §20.1; seed
check per §20.2. Then the final checkpoint halts for Operator confirmation; tag at
Step 6 after approval.
Implementation notes after the final checkpoint:
docs/phase-impl-notes/phase-66-implementation-notes-v0_1.md
Tag (post-checkpoint): phase-66-spend-visibility-and-pause.