Skip to main content
Zap separates planning (recipe validation, budget checks, step expansion) from execution (provider API calls, asset persistence, poll draining). The runtime glues Convex, Upstash, and provider adapters together into a durable, idempotent pipeline: each step can be retried safely, partial runs can resume, and mock runs share the same code path as live runs.

Architecture Overview

A live run follows this path from API request to final artifact:
The client polls Convex (real-time subscriptions or manual refresh) to observe step progress and retrieve output assets. The server-side poll drain loop runs independently of the originating HTTP request.

Components

Convex

Convex is the source of truth for all run state. Every table is defined in convex/schema.ts. Run status lifecycle:
Step status lifecycle:

Upstash Redis

Upstash provides two critical functions:
  1. Idempotency keys — each provider job submission writes a key to Redis before the API call. If the submission is retried (e.g. after a timeout), the existing requestId is returned instead of creating a duplicate job.
  2. Provider poll queues — after submission, a poll job is enqueued in Upstash. The drain endpoint consumes these jobs on a schedule, calls provider.poll(requestId), and updates Convex when the result is ready.
Required environment variables:

Provider Adapters

Every provider implements the ProviderAdapter interface from lib/provider-types.ts:
The GenRequest type carries all information needed for a provider API call:
The provider router selects an adapter by matching step.provider (or defaults.provider) against registered adapter IDs: Poll result shape:
Submit result shape:

Poll Drain Endpoint

POST /api/providers/poll/drain is invoked by the Upstash queue scheduler. It:
  1. Dequeues a batch of pending poll jobs from Upstash
  2. For each job, calls provider.poll(requestId, secrets)
  3. On done: writes the asset to @vercel/blob, records the asset in Convex, marks the step done
  4. On failed: records the error on the Convex step, marks the step failed
  5. On queued / running: re-enqueues the job with an exponential backoff delay

Blob Store

Generated assets are persisted to Vercel Blob via @vercel/blob. The asset URL is written to the assets table in Convex. Required environment variable:

BYOK Key Retrieval

Live runs can use creator-supplied provider keys (Bring Your Own Key). The key retrieval flow is:
  1. The client sends a wallet-authenticated Supabase bearer token with the run request
  2. The server verifies the JWT and the ZAP_SECRET_REVEAL_TOKEN environment variable
  3. Provider keys are fetched from Supabase and injected into the GenRequest.secrets field
  4. The provider adapter reads secrets during submit() and poll() calls
  5. Plaintext keys are never returned to the browser — they exist only in the server-side request context
Required environment variable (server-side only):

HyperFrames Stitching

When a recipe specifies stitch.engine: hyperframes, the runtime follows this flow:
  1. Detect stitch.engine: hyperframes in the planned step
  2. Generate a temporary HyperFrames project directory with a Zap visual identity (DESIGN.md is written automatically if not present in the recipe root)
  3. Validate the project by running the HyperFrames CLI checks in sequence:
  4. Render the composition to the output format and quality:
  5. Persist the rendered file to @vercel/blob and record the asset in Convex
  6. On failure: record the error message on the Convex step record and fall back to the first resolved stitch asset — the run is marked done (not failed) with an explanatory step error
HyperFrames is an optional runtime dependency. If npx hyperframes is not available in the execution environment, the auto engine falls back to local FFmpeg-based stitching automatically. Only engine: hyperframes (explicit) triggers the HyperFrames path; engine: auto prefers HyperFrames when available but never fails if it is absent.

Live vs Mock Runs

The mock provider adapter returns deterministic, zero-cost outputs on every call. It is used by default in new recipes (defaults.provider: mock) and for all demo runs on the web.
Mock creator demos are public and zero-spend — no wallet or API keys are required. Live runs that call real provider APIs require a wallet-authenticated Supabase bearer token and a funded provider account.

Environment Variable Reference