Implementation spec · partners · 2 Sep 2026

How the machine runs

Everything below sits under SPEC.md (rules) and IMPLEMENTATION.md (gap analysis). This page is the engineering view: what a backer's dollar does, how a Gauntlet Loop survives being hosted, what the creator actually gets to touch, and what ships first. For product status, phases and next steps see /mvp; for voice, vocabulary and visual identity see /brand.

01Three layers, one rule 02Funding: wireframes and money flow 03Campaign + run state machine 04Running the Gauntlet Loop through OpenRouter 05The sandbox: what is in the box 06Creator tools: console wireframes 07Watch, episodes, streaming 08Observability and routing optimization 09Phases: MVP → nice-to-have → next

01 · Three layers, one rule

A Gauntlet Loop is an overseer (the prompt: split the goal, builder + fresh-context critic per piece, compare against a real bar, keep looping) running inside a harness (Claude Code: real subagents, files, shell, headless Chromium, live workbench). Loopfund adds a third layer, the platform: money, the VM, the wave supervisor, observability, publishing.

The one rule. The platform touches the loop at exactly two points: the budget brake (OpenRouter key limit, pause) and the wave boundary (nudges appended to NUDGES.md, next-wave prompt). It never prescribes decomposition, never lets the builder grade itself, never caps rounds.

flowchart LR subgraph O[Overseer · the prompt] G[creator goal] --> B[bar/ reference artifacts] end subgraph H[Harness · Claude Code, unmodified] L[lead agent] --> BU[builder subagents] L --> CR[critic subagents
fresh context, no Edit] BU --> ART[artifact: repo, playable, shots] CR -- inspects pixels, blind A/B vs bar --> ART CR -- biggest gap --> BU L --> WB[workbench/index.html + WAVE.json] end subgraph P[Platform · Loopfund] LD[loopd wave supervisor] KEY[OpenRouter key
limit = pot − reserve] NUD[NUDGES.md at wave start] PUB[snapshot → CDN, GitHub Release] end O --> L LD -- starts each wave --> L KEY -. bills every call .- H NUD -.-> L WB --> PUB
InvariantHow the platform keeps itWhat would break it
Goal, not implementationRun prompt = creator goal + bar + a 12-line reporting addendum. No workstreams, no architecture.Wizard fields that become a task list; backer "add workstream" UI.
A real, inspectable barStep 05 requires uploaded references stored read-only at bar/. No bar → first wave's job is to find one.Text-only bars; letting the builder redefine the bar.
Builder never grades itselfCritics are subagents (fresh context by construction) and a PreToolUse hook denies Edit/Write when agent_type matches critic*.Routing critics to a cheaper model; showing critics the builder's transcript.
Unbounded until a human (or the pot) stops itExternal wave supervisor loopd starts the next turn after every wave. Stop = pot empty, creator Pause/Ship, plateau checkpoint answered "stop".Using Claude Code's Stop hook as the loop (capped at 8 consecutive blocks).
Watch without interruptingWorkbench snapshotted to CDN; nudges land only at wave start.Chat piped into the live session; a TTY for backers.

02 · Funding: wireframes and money flow

Backers buy prepaid compute earmarked for one loop. Loopfund is merchant of record. The creator's withdrawal is $0. Copy uses held for this loop, never escrow (decided 2 Sep; legal meaning implies a fiduciary we are not). Charged immediately at pledge so the pot is real money on day one and Start run can happen the moment the threshold is hit, with no campaign deadline.

Campaign card · four pot states

Funding Example projectstate A
Hero shot · latest artifactupdates every wave once running
$180held for this loop · $500 start threshold · 9 backers
Fund this loopRefunded in full if the loop never starts
Funded · waiting for creator state B
$620Threshold metCreator can Start run · you can keep fueling
Running ● livestate C
Live 11-shot stripours vs bar · wave 7 · 3 wins / 4 losses
$380spent of $1,240 in pot · wave cost $41 · Fable 5.1 via OpenRouter
Add fuelWatch
Paused · pot empty ⏸ pausedstate D
$0left · same machine, same disk, resumes on top-up
Add fuel to resumeLast critic round: lighting LOST, gun WON

Checkout · what the money is

Fuel this loop · Claude of the Wild Stripe Checkout
$25$50$100$250custom
Where your $50 goes
  • Held for this loop and spent only as model tokens on this run
  • Fees: [unset · do not invent a % here]
  • Creator receives $0 cash. Start run boots a machine, not a payout.
  • Unused money refunds pro-rata when the creator ships or the loop is cancelled
Emailguest checkout · optional GitHub login to follow and vote
CardStripe Payment Element · Stripe Tax on
Pay $50 · held for this loop
Reward: early access to the playable, repo credit. Not equity, not revenue share.

Creator funding panel · three numbers

Fuel · Claude of the Wild creator
In pot$1,240
Spent$380
Refundable on ship$860
Key limit $848 (reserve $12)Upstream: OpenRouter → Anthropic 1P
Add my own fuelShare linkCash out: $0. Always.

Money flow · pledge to token

sequenceDiagram autonumber participant Bk as Backer participant St as Stripe participant App as Loopfund app participant L as Ledger (Postgres) participant Orc as Orchestrator participant OR as OpenRouter Mgmt API Bk->>St: Checkout $50 (charged now) St-->>App: webhook payment_intent.succeeded App->>L: pledge_captured +50 · pot_credit +50 Note over L: pot_available = Σ credits − Σ spend − Σ refunds alt threshold reached & creator clicks Start run Orc->>OR: POST /keys {name: run-slug, limit: pot_available − reserve} OR-->>Orc: sk-or-v1-… (shown once) Orc->>L: key_minted · key_limit_set Orc->>Orc: create sandbox, inject key as ANTHROPIC_AUTH_TOKEN end loop every 30–60 s while RUNNING Orc->>OR: GET /keys/{hash} OR-->>Orc: usage, limit_remaining Orc->>L: spend_settled (delta) end Bk->>St: Top-up $100 St-->>App: webhook App->>L: topup +100 Orc->>OR: PATCH /keys/{hash} {limit: new pot − reserve, disabled:false}

Key limit is a hard backstop, not the primary brake: loopd refuses to start a wave when limit_remaining < reserve, so most pauses land on a clean wave boundary. Reserve ≈ max($10, 3% of pot) covers in-flight Fable output on parallel subagents.

Unit economics before any take rate

Per $100 pledgedCostNote
Stripe processing≈ $3.202.9% + $0.30. Not returned on refunds.
OpenRouter credit-purchase fee≈ $5.305.5% on credits Loopfund buys in bulk; platform carries the float; credits expire after 365 days.
Sandbox compute≈ $6.50 / run-day4 vCPU / 8 GB on E2B or Daytona; $0 while paused (storage only).
Gone before Loopfund earns anything≈ 8.5% + $6.50/dayTake rate is an open question; 0% is a loss.

03 · Campaign + run state machine

One run per campaign in MVP. Three resources are bound to the run and must move together: the VM (disk + memory), the OpenRouter key, and the Claude Code session.

stateDiagram-v2 [*] --> DRAFT: wizard 04→05→credentials DRAFT --> FUNDING: publish FUNDING --> FUNDED: Σ pledges ≥ threshold FUNDING --> CANCELLED: 60 d without threshold · creator cancel FUNDED --> PROVISIONING: creator clicks Start run FUNDED --> CANCELLED: 14 d without Start run PROVISIONING --> RUNNING: mint key · boot VM · loopd heartbeat RUNNING --> PAUSED_POT: limit_remaining < reserve · 402 from gateway RUNNING --> PAUSED_PROVIDER: 6 wave failures after failover RUNNING --> PAUSED_CREATOR: Pause (wave boundary or now) RUNNING --> PAUSED_PLATEAU: N flat waves → ask humans RUNNING --> PAUSED_PLATFORM: incident / ops PAUSED_POT --> RUNNING: top-up → PATCH limit → resume VM PAUSED_PROVIDER --> RUNNING: retry every 15 min succeeds PAUSED_CREATOR --> RUNNING: Resume PAUSED_PLATEAU --> RUNNING: creator Resume · backer vote keep going PAUSED_PLATEAU --> SHIPPED: creator Ship PAUSED_PLATFORM --> RUNNING RUNNING --> SHIPPED: creator Ship PAUSED_POT --> CANCELLED: 60 d paused PAUSED_PLATEAU --> CANCELLED: creator silent 72 h + vote stop SHIPPED --> SETTLED: final build → CDN · release tag ship · pro-rata refunds CANCELLED --> SETTLED: full or pro-rata refunds SETTLED --> [*]
StateVMOpenRouter keyClaude sessionBackers see
FUNDING / FUNDEDpot meter, threshold, credentials
PROVISIONINGcreate from template, clone repo, write bar/, .claude/POST /keys limit = pot − reservenone"Booting" (mock 08), spend $0
RUNNINGrunningenabledactive; --resume across wavesworkbench, 11-shot, verdicts, spend
PAUSED (pot)pause() with memorydisabled: truefrozen in RAM"Paused · add fuel to resume"
PAUSED (provider)pause()enabledfrozen"Provider outage · retrying"
PAUSED (creator / plateau)pause() at wave boundaryenabledfrozen"Paused by creator" / vote widget
SHIPPEDfinal capture + build → CDN; kill after 7 ddisabled → deletedarchived to storageplayable, final episode, refund notice

04 · Running the Gauntlet Loop through OpenRouter

OpenRouter replaces the pipe, not the harness. Claude Code speaks its native Anthropic protocol to https://openrouter.ai/api with the per-run key as bearer token. Provider pinning, failover and model choice live in OpenRouter Presets referenced by name from Claude Code's model-slot env vars, so no proxy is needed for MVP.

flowchart LR subgraph VM[Sandbox] LD[loopd] -->|"claude -p --resume … (one wave)"| CC[Claude Code lead] CC --> SB[builder subagents] CC --> SC[critic subagents] end CC & SB & SC -->|"ANTHROPIC_BASE_URL=openrouter.ai/api
Bearer sk-or-… (limit = pot − reserve)"| ORK[OpenRouter key check
limit_remaining > 0 ?] ORK -->|"model = @preset/loopfund-fable"| PRE[Preset: pinned model
provider.order anthropic → bedrock → vertex
only those 3 · allow_fallbacks true] PRE --> A1[Anthropic 1P] PRE -. 429 / 5xx .-> A2[Amazon Bedrock] PRE -. 429 / 5xx .-> A3[Google Vertex] ORK -->|"402 when limit hit"| CC ORC[Orchestrator] -->|"POST/PATCH /keys · GET usage"| ORK ORC -->|edit presets for routing experiments| PRE

Env in the VM (from the OpenRouter Claude Code cookbook)

ANTHROPIC_BASE_URL=https://openrouter.ai/api
ANTHROPIC_AUTH_TOKEN=<per-run OpenRouter key>      # minted via Management API, limit = pot − reserve
ANTHROPIC_API_KEY=                                  # must be explicitly empty
ANTHROPIC_DEFAULT_FABLE_MODEL=@preset/loopfund-fable
ANTHROPIC_DEFAULT_OPUS_MODEL=@preset/loopfund-opus
ANTHROPIC_DEFAULT_SONNET_MODEL=@preset/loopfund-sonnet
ANTHROPIC_DEFAULT_HAIKU_MODEL=@preset/loopfund-haiku
CLAUDE_CODE_SUBAGENT_MODEL=@preset/loopfund-fable  # default for subagents the lead spawns
CLAUDE_CODE_ENABLE_TELEMETRY=1
OTEL_METRICS_EXPORTER=otlp  OTEL_LOGS_EXPORTER=otlp  OTEL_EXPORTER_OTLP_ENDPOINT=<orchestrator collector>

Per-role routing table

RoleHow Claude Code picks itPreset (MVP)Pinned modelProvider orderMay be downgraded in experiments?
Lead / overseersession model (Fable slot)@preset/loopfund-fableanthropic/claude-fable-5.1 (never a -latest alias; price must be predictable)anthropic → amazon-bedrock → google-vertex; only those threeNo
Builder subagents.claude/agents/builder.md model: fable or opus@preset/loopfund-fable / -opusFable 5.1 / Opus 5sameYes — this is where "optimize routing" happens (Opus or Sonnet builders on cheaper campaigns)
Critic subagents.claude/agents/critic.md model: fable@preset/loopfund-criticFable 5.1sameNever (spec rule 5)
Smoother (optional)smoother.md@preset/loopfund-opusOpus 5sameYes
Auxiliary (titles, compaction)Haiku slot@preset/loopfund-haikuHaikusameYes

Changing a preset changes routing for every run pointing at it without touching the harness. For cohort experiments, mint per-cohort presets (@preset/loopfund-opus-expA) and set the env at provisioning.

The wave loop · what loopd does

flowchart TD S([resume / boot]) --> G[GET run state:
pot, paused?, nudges, session_id] G --> C{paused or
limit_remaining < reserve?} C -- yes --> P[request pause → orchestrator pauses VM] --> S C -- no --> N[append nudges → NUDGES.md] N --> W["run one wave:
claude -p --resume $SESSION --output-format stream-json <wave prompt>"] W --> R{exit} R -- ok --> T[parse transcript → api_calls per agent/model
read WAVE.json] T --> PUB[publish: workbench + 11-shot + build → CDN
git push · GitHub Release wave-N] PUB --> RP[report wave → orchestrator] --> PL{plateau: N flat waves?} PL -- yes --> P PL -- no --> G R -- api_error 429/529 --> BO[backoff 60 s → 10 min, ≤ 6] --> W BO -- cap hit --> P R -- 402 limit --> P

Why an external supervisor: Claude Code force-ends a turn after 8 consecutive Stop-hook blocks and expects hooks to yield when stop_hook_active is set. A Stop hook can guard one turn ("you did not write WAVE.json, keep going"); the loop across turns must be started from outside. That is also what gives us clean boundaries for pause, nudges and publishing.

The wave prompt (kept in Matt's register)

Loopfund wave protocol. You are running inside a hosted Gauntlet Loop.
- The goal and the bar are above / in bar/. Do not change the bar.
- Split the work however you judge best. Every important piece gets a builder and a separate,
  fresh-context critic that inspects the real artifact and compares it blind against bar/.
- Read NUDGES.md at the start of each wave. Treat entries as notes for the critics, not as
  architecture instructions. Never wait for input.
- Keep workbench/index.html current (shots, scores, what changed, what is next).
- At the end of each wave write WAVE.json: pieces, per-piece critic verdict and score,
  biggest remaining gap, next step. Commit. Then keep going; there is no final round.
- Method reference is in /opt/loopfund/reference/. Smoothing pass at wave end is your call.

Provider 429 and empty pot · what the ledger records

sequenceDiagram participant CC as Claude Code participant OR as OpenRouter participant A as Anthropic 1P participant B as Bedrock participant LD as loopd participant Orc as Orchestrator CC->>OR: messages (preset loopfund-fable) OR->>A: forward A-->>OR: 429 OR->>B: same request, next provider in order B-->>OR: 200 (usage, provider=amazon-bedrock) OR-->>CC: 200 Note over LD,Orc: ledger row: spend_settled · upstream=amazon-bedrock (handoff visible in ops, 0 s downtime) CC->>OR: messages OR-->>CC: 402 key limit reached CC-->>LD: turn ends with API error (StopFailure hook fires) LD->>Orc: GET key → limit_remaining 0 → request PAUSED(pot_empty) Orc->>OR: PATCH disabled:true Orc->>Orc: sandbox.pause() (filesystem + memory) Note over Orc: top-up later → PATCH limit, disabled:false → sandbox.resume() → loopd continues
Mock 17 vs MVP. The mock shows per-call "reserved → settled" rows with Loopfund choosing the upstream. With OpenRouter direct, MVP records settled spend (key polling every 30–60 s) and per-call detail after the fact from the transcript. Per-call reserve/commit and exact per-generation cost arrive with the Phase-2 pass-through proxy. Decide whether that is acceptable for the ops ledger on day one.

05 · The sandbox: what is in the box

A Firecracker microVM (E2B or Daytona; Fly Sprites as the fallback) built from one template. The creator never logs into it. It holds one OpenRouter key scoped to the pot, a short-lived GitHub App token scoped to one repo, and a run token for the orchestrator API. Pause keeps memory and disk; resume brings back running processes.

flowchart TB subgraph VM["Sandbox · 4 vCPU / 8 GB · egress allowlist only"] direction TB LD["loopd (wave supervisor, hook tailer, publisher)"] CC["claude (lead) · headless / Agent SDK"] SUB["subagents: builder-*, critic-*, smoother"] CH["Chromium headless=new · ANGLE/SwiftShader WebGL2"] VITE["vite dev server :5173"] TOOLS["/opt/loopfund/tools: capture, shotset, baseline, imagediff, playtest, grid"] HK["hooks → /var/loopfund/events.jsonl"] OT["OTel exporter"] FS["/workspace/<slug>: bar/ · workbench/ · WAVE.json · NUDGES.md · ARCHITECTURE.md · .claude/"] TR["~/.claude/projects/… session transcripts"] LD --> CC --> SUB SUB --> TOOLS --> CH --> VITE CC & SUB --> FS CC & SUB --> HK --> LD CC & SUB --> TR --> LD CC & SUB --> OT end LD -->|snapshots, WAVE.json, api_calls, heartbeat| ORC[Orchestrator] LD -->|git push · release| GH[GitHub org loopfund-runs] CC & SUB -->|only model traffic| OR[openrouter.ai] OT --> ORC ORC --> R2[(R2 / CDN)] --> WATCH["/watch/slug"]
ConcernDecision
ImageUbuntu 24.04, Node 22, pnpm, git, Python, ffmpeg, ImageMagick, Chromium, Claude Code (pinned), Agent SDK, loopd, tools, reference docs.
WebGL without GPUSwiftShader: seconds per frame, fine for stills and 11-shot capture; not for frame-time profiling. GPU tier (Modal/Daytona/Beam) later, not MVP.
Egressopenrouter.ai, registry.npmjs.org, github.com/api.github.com, orchestrator API + OTel. Nothing else. No inbound.
Pause / resumeSandbox pause() with memory (≈4 s/GB) → processes and RAM survive. Fallback: kill + claude --resume <session> on the same disk. E2B 24 h continuous clock → loopd schedules a pause/resume at the first wave boundary after 20 h.
DurabilityRepo pushed to GitHub every wave; transcripts mirrored to R2 every wave. Host loss = new VM, clone, restore transcript, resume. Loses ≤ 1 wave.
Critic enforcementPreToolUse hook denies Edit/Write/MultiEdit when agent_type ~ /critic/i. Default critic.md has tools Read/Glob/Grep/Bash only.
Serving the workbenchStatic snapshot to CDN every 5 min and at wave end. Never a tunnel to :5173 (exposes the VM; dies on pause).
Break-glassCreator PTY attach via the sandbox provider, logged as a ledger event, visible to creator + ops only. Rotate the OpenRouter key on detach.

06 · Creator tools: console wireframes

Creator control is a capture/review surface plus a brake. It is not a weaker Claude Code; it is Claude Code the creator is not allowed to babysit. Four verbs: watch, playtest, nudge (queued for the next critic round), pause / ship. Attach is break-glass.

GAUNTLET · run console · Claude of the Wild ● live · wave 7spend $380 / pot $1,240 · Fable 5.1 · 11 subagents · effort max
Live artifact previewlatest playable build (Vite prod build from wave 6) · open in new tab playtest
Sandbox · hosted by Loopfund · you are not in the machine
#1 ours 0.58 · bar 0.62
#2 ours 0.61 · bar 0.58 ✓
#3 ours 0.71 · bar 0.63 ✓
#4 ours 0.54 · bar 0.59
#5
#6
#7
#8 … #11
Critic rounds · 7 rounds · 3 wins / 4 losses
  • 007 Lighting WON mood and readability consistent
  • 006 Trees LOST pop-in and clustering at mid distance
  • 005 Gun WON recoil and muzzle feedback
  • 004 Lighting WON god rays support focal read
  • 003 Trees LOST foliage density
Each row: piece · blind A/B verdict · biggest gap · score 1–10 · tokens/$ for this round
Spend this wave · by rolelead $6 · builders $28 (Fable) · critics $7 · cache-hit 81%
from transcript parsing, reconciled to OpenRouter key usage
Nudge · queued for the next critic round"Improve weapon readability at mid range. Keep lighting moody."
Queued · lands at wave 8 start view queue (2)
Nudges are notes for the critics, not orders to the builder. They never interrupt a live wave.
⏸ Pause at wave end ⏸ Pause now ⇧ Ship >_ Attach Claude Code (break glass · logged)

Elaborates mocks 10 and 14. New vs the mocks: per-role spend, "pause at wave end" vs "pause now", queued-nudge ETA tied to the wave boundary, break-glass rotates the key on detach.

Sandbox status creator + ops
VMvm-7f4a9c3d · us-east-1 · disk 2.4 / 10 GB · RAM 5.1 / 8 GB · uptime 6h12m · last heartbeat 8 s
Processesloopd · claude-lead · builder-weapons · builder-lighting · critic-weapons · critic-lighting · chromium-headless · vite
from SubagentStart/Stop hook events, not ps
Sessionintact · wave 7 · session 3 (fresh per wave, memory in WAVE.json + repo)
GatewayOpenRouter key ••••3f2a · limit $848 · remaining $468 · last upstream anthropic · 429s this wave: 1 (handed off to bedrock)
Plateau checkpoint ⏸ paused · 5 flat waves
Critics found no meaningful gain in waves 12–16score 5.0 → 5.1 → 5.0 → 5.1 → 5.1 · every A/B still picks the bar
Resume · keep goingShip as isCancel · refund $612
Backers were asked too"Keep going?" 61% yes (48 votes) · advisory · becomes binding if you are silent 72 h

Wizard · what the creator supplies (and what they do not)

StepCreator suppliesPlatform doesDeliberately absent
04 GoalDestination in a paragraph, category, model class (Fable 5.1 default)Stores verbatim as the top of the run promptFeature list, architecture, task breakdown
05 BarReference screenshots / playable URL / test suite / reference repo; homage checkbox with IP warningStores at bar/, read-only; if empty, first wave = find a barText-only "make it AAA"
CredentialsGitHub OAuth (required), prior projects, jobs, press, testimonialsRenders the sidebar (mock 15); marks linked vs verified
ThresholdStart threshold (default $500), optional goalEnables Start run at threshold; refunds if never startedReward tiers, deadline pressure
07 Start runOne click after reading "this is not a payout, cash-out $0"Mint key, boot VM, inject goal + bar, first waveAny withdrawal path

07 · Watch, episodes, streaming

The stream is the workbench, not a terminal. The agent writes it; the platform snapshots and serves it. Episodes are GitHub Releases, one per critic wave. Twitch is an optional ingest of the same composite, later.

flowchart LR subgraph VM[Sandbox] WB[workbench/index.html
lead-maintained] SH[tools/shotset → shots/wave-N/*.png
+ one shot every 5 min] BUILD[vite build → dist/] WJ[WAVE.json] LD[loopd publisher] WB & SH & BUILD & WJ --> LD end LD -->|every 5 min + wave end| R2[(R2 / CDN)] R2 --> W1["/watch/slug · live workbench, 11-shot, spend, verdicts"] R2 --> W2["/watch/slug/wave/N · episode page"] R2 --> PLAY["/play/slug · playable (signed URL for backers until ship)"] LD -->|"git push · gh release create wave-N"| GH["GitHub Release wave-N
score table · biggest gap · next step
assets: grid.png, shots, playable.zip, WAVE.json"] R2 -.->|"Phase 3: 5-min shots → ffmpeg → 60 s speedrun.mp4"| VOD["2×/5× VOD on /watch"] LD -.->|"Phase 3 optional: ffmpeg composite → RTMP"| TW[Twitch ingest
workbench + playable + critic overlay]
/watch/claude-of-the-wild ● live · wave 7public · no login
Latest artifactplayable build from wave 6 · Play (backers) · 1× live 2× last 6 h
2×/5× = speedrun VOD stitched from 5-minute shots (Phase 3)
#1 ours
#2 ours ✓
#3 ours ✓
… #11
Reference (bar) frames are never published; the grid shows our frames and the critic's verdict only.
Pot$1,240 · spent $380 · wave cost $41
Add fuel
Critic rounds007 Lighting WON · 006 Trees LOST · 005 Gun WON …
Episodeswave-7 · wave-6 · wave-5 … (GitHub Releases)
Between-wave vote"Next: trees or enemy AI?" · from the lead's own next_step_options · closes at wave end · result becomes one line in NUDGES.md
Creator credentialsshipped loops · jobs · press · GitHub-verified

Elaborates mock 16. Chat is votes between waves only; no free-text from backers reaches the harness in MVP (prompt-injection surface, and rule 3).

SurfaceSourceCadencePhase
Live workbenchworkbench/index.html snapshot5 min + wave endMVP
11-shot grid + verdictsshots/wave-N, WAVE.jsonwave endMVP
Episode (GitHub Release)loopd via GitHub Appwave endMVP
Playable for backersVite build → CDN, signed URLwave endMVP
2×/5× speedrun VOD5-min shots → ffmpeghourlyPhase 3 (start archiving shots in MVP so it is cheap later)
Twitch ingestffmpeg composite in VM → RTMPcontinuous while runningPhase 3, optional

08 · Observability and routing optimization

Three sources, reconciled per wave. Financial truth is the key. Attribution truth is the transcript. Live cross-check is OTel.

SourceGivesDoes not giveUsed for
OpenRouter key usage / limit_remaining (poll)Exact USD debited per runPer call, per agentPot, pause decisions, refunds
Claude Code session transcript JSONL (+ subagent transcripts), parsed by loopdPer API turn: model, input/output/cache tokens; joined to SubagentStart/Stop hook events → agent_id, agent_typeExact price (estimated from list)Builder vs critic split, per-piece cost, cache-hit ratio
Claude Code OTel metrics/eventstoken.usage, cost.usage by model, type, query_source, effort; api_request eventsCustom agent names (redacted to "custom")Live dashboards, alerts (cache-hit collapse, spend spikes)
Phase 2 proxy: GET /api/v1/generation?id=Exact native cost + serving provider per callReplaces estimates; per-call reserve/commit; per-run provider prefs for experiments

The metric to optimize

Critic-score delta per dollar per wave, split by builder model. Stored from WAVE.json as a series so it is queryable without parsing HTML. Secondary: cache-hit ratio (first efficiency signal; loops reread the growing repo), 429 count and provider mix, wall time per wave, verdict win rate. Experiments change presets for a cohort of runs; critics are never in the experiment.

SignalBackerCreatorOps
Workbench, grid, playable, verdicts, pot totals
Per-role spend, processes, nudge queue, provider handoffs
Per-agent api_calls, transcripts, break-glass log, full ledgerneverown break-glass only✓ audited

09 · Phases

Build the machine before the marketplace. The spec's success bar is entirely about the run: one hosted loop survives a provider 429 and an empty-pot pause/resume, with a public workbench and a GitHub Release episode, funded by a real Stripe test payment.

Phase 0 · harness in a box · ~1 wk

Prove the loop survives hosting

  • Docker image: Claude Code, Chromium, tools, loopd, hooks, subagent defaults
  • One OpenRouter key, limit: 25, presets created
  • Run Matt's prompt.md against bar/ of CoD frames
  • Transcript parser ↔ key usage within 5%
  • Gate: preservation test passes (critic denied on Edit, wave 2 starts unattended, stops only at $25)
Phase 1 · MVP core · ~3 wk

Sandbox, brakes, money, one public run

  • E2B/Daytona template; orchestrator state machine; key mint/patch/disable
  • Chaos: 429 for 10 min, limit→$0 mid-wave, kill VM → recover
  • Stripe charge-now pledges, threshold → Start run, top-ups, pro-rata refunds, ledger + reconciliation
  • /watch from R2 snapshots; GitHub Release per wave; playable via signed URL
  • Creator console: nudge, pause (wave end / now), ship, break-glass (logged)
  • Real campaign + checkout pages; wizard 04/05/credentials; example campaigns labeled
Phase 2 · nice-to-have · after first funded run

Make it measurable and safer

  • Pass-through gateway proxy: exact per-generation cost, per-call reserve/commit (mock 17 for real), per-run provider prefs
  • Routing experiments: builder-model cohorts, Δscore/$ dashboard
  • Plateau checkpoint with backer vote; between-wave votes from next_step_options
  • Stripe Identity gate above a pot threshold; manual review queue
  • Ops runbook; alerts on idle, spend spikes, cache-hit collapse
Phase 3+ · next phase

Distribution and new verticals

  • 2×/5× speedrun VODs from archived 5-min shots; 60 s clip attached to each release
  • Optional Twitch ingest (workbench + playable + critic overlay)
  • GPU sandbox tier for real WebGL performance
  • Open-source-on-ship flow; hosted playable at /play/slug
  • Second gateway (Anthropic Admin API workspaces) for cost; Codex harness option
  • Interactive tools, then movies/episodic; research only with a public eval

MVP in / out (unchanged from SPEC.md)

In. Games only; creator submit (destination, Fable 5.1 via OpenRouter, critic bar, credentials); Stripe Checkout into the pot; no creator withdrawal; Start run boots a scoped VM and mints an OpenRouter key with limit = pot − reserve; public workbench; 11-shot critic grid; empty-pot pause / top-up resume same disk; pro-rata unused refund; GitHub Release each wave; creator watch / nudge / pause / ship; break-glass logged and hidden from backers.

Out. Creator cash-out; equity; chat interrupting the builder; pooling Max subs; movies/research; full 2×/5× pipeline; multi-gateway beyond OpenRouter; custom domains; GPU sandboxes; per-call reserve/commit ledger (Phase 2).

Decided 2 Sep 2026

QuestionDecision
Sandbox providerE2B first; Daytona as tested fallback. Re-check after Phase 1 chaos tests.
Pledge modelCharge now; full refund if the run never starts. Loopfund eats the Stripe fee on those.
Take rate15% all-in. "85% becomes tokens." Shown before payment.
Critic modelAlways Fable 5.1. Builders may be downgraded in experiments; critics never.
Per-call ledger (mock 17)Do it right: pass-through proxy comes right after Phase 0 passes, not in Phase 2.
"Escrow"Removed. "Held for this loop."
Session strategyOne long resumed session per run; fresh session only on corruption.
Backer votesNot in MVP. Creator-only nudges.
Plateau5 flat waves → pause and ask the creator; advisory; auto-cancel + refund after 14 days of silence.
Reference-bar IPPrivate in bar/, never published.

Still open