cadabra Docs

Architecture

Cadabra has three runtime parts:

Phones and the TV talk directly over WebRTC when they can, and through the backend when they cannot. The AI only ever produces data (a GameSpec), never code, and every spec is played by bots before a human sees it.

Cadabra architecture Phones load the controller site from S3 and CloudFront and join rooms over an API Gateway WebSocket. The Fire TV Stick runs the game and talks to phones directly over a WebRTC DataChannel. In AWS ap-south-1, the WsFn Lambda handles rooms, signaling and relay and stores state in DynamoDB. It invokes ForgeFn, the AI designer, and HostFn, the captions writer. Both call Amazon Bedrock and Bedrock Guardrails. ForgeFn reads the Anthropic API fallback key from Secrets Manager and falls back to the Anthropic API while Bedrock quota is zero. AWS ap-south-1, one CDK stack HTTPS WSS WSS async async fallback while Bedrock quota is 0 WebRTC DataChannel 13 to 16 ms Phones controller web app browser, no install Fire TV Stick HD Kotlin shell + WebView engine, 9 blocks, bots planck physics, PixiJS authoritative host S3 + CloudFrontcontroller site, assets API GatewayWebSocket rooms Secrets Managerfallback API key Lambda WsFnrooms, signaling, relay Lambda ForgeFnAI designer, tweak, playtest Lambda HostFncaptions, roast DynamoDBone table + TTL Amazon BedrockConverse, tool use Guardrailscadabra-family-safe Anthropic APIClaude 5.5 models

Components

ComponentPathResponsibility
Fire TV shellapps/androidLeanback launcher and banner, keeps the screen on, full-screen WebView that serves the bundled TV app from https://appassets.androidplatform.net (a secure context, so WebRTC and WebGL work). Maps remote keys into JS, passes volume and mute through, and defines the Back contract (exit / handled).
TV web appapps/tvPS5-style home, the Create flow (storm, pitches, vote, forge), intro, play, results, Tweak and Remix, Party, Arcade, Settings, trophies, D-pad focus, UI sounds, and the CouchHost that talks to phones.
Enginepackages/engineDeterministic, seeded runtime. A GameSpec composes blocks, with a planck physics layer, bots, an event log, and the playtest gate.
Specpackages/specZod schemas shared by every side: GameSpec (including world.physics, world.props, theme.look), the wire protocol, phone screens, tweak chips, and the 7 built-in games.
Renderpackages/renderPixiJS game view: 4 looks, physics prop views, HUD, popups, camera director, particles with an adaptive budget, procedural music and sfx per look.
Couchpadpackages/couchpadPhone-as-controller transport and widgets, also published as MaanavKrishna/couchpad.
Controllerapps/controllerPhone app: join, orb or selfie avatar, Idea Storm, vote, tweak chips and text, per-game controls built from the spec's widget list.
Backendapps/backendOne CDK stack and the Lambda handlers below.

The AI designer (tool calling)

The designer runs in ForgeFn (apps/backend/src/ai/designer.ts, tools.ts). It gets a system prompt that lists the block kit, the looks, the physics world schema (generated from the zod .describe() text) and fun rules: a clear goal, risk and reward, a comeback, physical comedy. The system prompt and tools are shared by design and tweak jobs, so they hit the same prompt cache.

Tools:

ToolWhat it does
list_blocksThe kit: block id, summary, controls, arena kinds
get_block_schemaOne block's params as JSON Schema with defaults, plus the controller widgets it needs
validate_specSchema check, returns {ok, errors}
playtest_specValidate, then play a full round with 4 bots (skills 0.4 to 0.9). Returns {ok, reasons, stats}, and the reasons say exactly what to fix
patch_specRFC 6902 JSON Patch on the latest spec. Tweak jobs also pass a summary and changes tags
submit_specKept for compatibility. A passing playtest of the latest spec already counts as the submission

Because a passing playtest_spec is the submission, a typical design takes one model reply. Measured on the live backend: pitches in about 6 s (Haiku 5.5, 8 s budget, else fallback pitches), and the winner ready about 4 to 7 s after the vote.

The Forge flow

Sequence
TVWsFnForgeFnGuardrailsBedrock / Anthropic API
  1. TV→WsFn
    forge.pitch {ingredients, players, settings}
  2. WsFn↺
    forge lock (conditional write)
  3. WsFn→ForgeFnasync
    async invoke
  4. ForgeFn→Guardrails
    screen ingredients (blocked ones dropped)
  5. ForgeFn→Bedrock / Anthropic API
    3 pitches with a look each (8 s budget)
  6. ForgeFn→TVreply
    forge.pitches
  7. In paralleldesigns A, B, C in parallel
  8. ForgeFn→Bedrock / Anthropic API
    design(pitch): tool calls
  9. ForgeFn↺
    validate + bot playtest
  10. ForgeFn→TVreply
    forge.progress {step, pct}
  11. TV→WsFn
    forge.vote {pitchId}
  12. ForgeFn→TVreply
    forge.ready {spec, fallback?}

Deterministic engine and the bot playtest gate

Physics

Looks

A spec's theme.look picks one of four fully procedural looks (packages/render/src/looks.ts, lookArt.ts, lookScenes.ts). The AI picks a look per pitch.

LookArenaSpritesMusic
neondark grid, additive glowneon bodies with eyessynthwave
toyboxpolka-dot table, candy kerbglossy vinyl, toy-brick crates, pinball bumpersbouncy major key with swing
cosmosnebula, starfield, glass deckastronaut orbs, crystals, moon ballambient pad and plucks
retro1/8-resolution pixel art, scanlineshand-typed 16 px spriteschiptune

Each look bakes its arena into one opaque layer, because full-screen alpha layers are what the stick's GPU cannot afford.

The Tweak loop

Sequence
PhonesTVForgeFnClaude
  1. Phones→TV
    tweak.ask {chip | text} (10 s window, 3 slots)
  2. TV→ForgeFn
    forge.tweak {spec, requests, seats, mode}
  3. ForgeFn↺
    screen typed text (Guardrails, fails closed)
  4. ForgeFn→Claude
    tweak brief + chip preset hint
  5. Claude→ForgeFn
    patch_spec + playtest_spec in one reply
  6. ForgeFn→TVreply
    forge.tweaked {spec, summary, changes, source}
  7. TV↺
    "What changed" card, then replay

Bedrock first, Anthropic API as fallback

apps/backend/src/ai/bedrock.ts exposes one converse() for the roles designer (Sonnet 5.5) and fast (Haiku 5.5). Each role has an ordered chain of model ids and inference profiles (anthropic., in., global.).

Guardrails

Join, WebRTC, relay

Sequence
TVAPI GW + WsFnPhone
  1. TV→API GW + WsFn
    room.create {household}
  2. API GW + WsFn→TVreply
    room.created {code, tvToken}
  3. Phone→API GW + WsFn
    room.join {code, name, rejoinToken?}
  4. API GW + WsFn→Phonereply
    room.joined {playerId, token}
  5. API GW + WsFn→TVreply
    player.joined
  6. Phone→API GW + WsFn
    signal {to: tv, offer}
  7. API GW + WsFn→TVreply
    signal {from: playerId, offer}
  8. TV→API GW + WsFn
    signal {to: playerId, answer}
  9. API GW + WsFn→Phonereply
    signal {from: tv, answer}
  10. Phone⇄TVboth ways
    DataChannel "input" (unordered) + "state" (reliable)
  11. Notenot open within 4 s: relay through the WebSocket, chunked at 28 KB

Message catalogue

Authoritative definitions: packages/spec/src/protocol.ts. All frames are JSON with a string t.

DirectionMessages
TV/phone to backendroom.create, room.join, room.resume, signal, relay, ping, forge.pitch, forge.vote, forge.tweak, host.say, host.roast, judge.request, arcade.list, arcade.save, arcade.play, arcade.delete, arcade.played
Backend to TV/phoneroom.created, room.resumed, room.joined, player.joined, player.left, tv.left, tv.back, signal, relay, pong, error {code}, screen, forge.pitches, forge.progress, forge.ready, forge.art, forge.tweakProgress, forge.tweaked, host.audio, host.roasted, judge.result, arcade.items, arcade.game
Phone and TV peer (DataChannel or inside relay)input {frame}, screen {name, data}, ping, pong, ~chunk envelopes

Phone screens: join, avatar, storm, vote, warmup, play, results, tweak, wait, blocked.

host.audio still carries the caption text. Voice is off (voice: false), so no audio URL is sent and Polly is not called.

Rendering budget on the stick

Security and privacy

Edit this page on GitHubLast updated 23 October 2026