Deploy and register

Local development

Run two dev servers, one per terminal.

Platform — set in the platform's .env.local:

ZONE_<AGENT_SLUG>_URL=http://localhost:4000

(env-var naming depends on the platform's zone-registry conventions — your platform admin will tell you the exact key)

Zone.env.local in your zone repo:

ZONE_JWT_SECRET=<provided by platform admin>
AUDIENCE_ID=<provided by platform admin>
PLATFORM_API_BASE_URL=http://localhost:3000
PLATFORM_ORIGIN=http://localhost:3000
ZONE_ORG_SLUG=<your-local-org-slug>
ZONE_AGENT_SLUG=<your-local-agent-slug>

ZONE_ORG_SLUG + ZONE_AGENT_SLUG compose the zone's static basePath. They must match the (org, agent) pair the platform registry binds this zone to. Wrong values build cleanly but every request 404s.

Run:

bash
# terminal 1 (platform)
npm run dev

# terminal 2 (zone) — non-default port
npm run dev -- -p 4000

Visit http://localhost:3000/{orgSlug}/{agentSlug}/app. The URL bar stays on :3000; the page body is served by :4000; assets are proxied via the platform's rewrites().

Smoke tests

bash
curl -i http://localhost:4000/{orgSlug}/{agentSlug}/app
# → 401 (no zone token)

curl -i -H "x-zone-token: garbage" \
  http://localhost:4000/{orgSlug}/{agentSlug}/app
# → 401 (invalid signature)

Then, in a browser: submit a run → run row appears in the platform DB → run detail polls → terminal status.

Deploy to Vercel

The zone is a second Vercel project (or a separate folder configured as one). Same Git repo or a sibling repo — either works; the important thing is its own Vercel project with its own production URL.

1. Create the project

  1. Push the zone repo to GitHub.
  2. vercel link in the zone repo → create a new Vercel project.
  3. Set the deployment region to the same region as the platform project. Cross-region rewrites add 50–200 ms per request.

2. Environment variables

Set the same six env vars from .env.local in the Vercel project — with production values:

VariableValue
ZONE_ORG_SLUGmatches the (orgSlug, agentSlug) pair registered on the platform
ZONE_AGENT_SLUGmatches the (orgSlug, agentSlug) pair registered on the platform
ZONE_JWT_SECRETthe per-agent secret from the platform UI (rotate by regenerating in the platform and redeploying with the new value)
AUDIENCE_IDmatches the audience the platform mints with for this agent
PLATFORM_API_BASE_URLplatform's production URL, no trailing slash
PLATFORM_ORIGINplatform's production URL, no trailing slash

No trailing slashes on PLATFORM_API_BASE_URL / PLATFORM_ORIGIN. A stray / produces //assets/... in the proxy destination and triggers a silent 308 loop with a text/plain body — extremely confusing to debug.

3. Disable Vercel Skew Protection

Vercel → Settings → Advanced → Skew Protection → off on both the zone and the platform project.

With Skew Protection on, platform and zone ?dpl=<id> deployment identifiers collide during the asset proxy and every _next/* request returns ERR_TOO_MANY_REDIRECTS.

4. Deployment Protection

Vercel → Settings → Deployment Protection → Vercel Authentication: Only Preview Deployments (or off).

With "All Deployments" selected, proxied asset requests get redirected to Vercel's SSO page and loop. Password protection must also be off (or preview-only) on production.

The zone is already protected at the app layer — any request without a valid JWT gets a 401 from the zone's middleware. Vercel-level protection on production adds nothing and breaks the proxy.

5. Deploy order

  1. Deploy the zone first. Note its production URL.
  2. Ask the platform admin to register the zone's URL against the agent. The platform then knows how to proxy /app/* traffic.
  3. The platform's asset rewrite reads env vars at build time — when the platform admin adds the new URL, the platform must be redeployed without build cache (Deployments → … → Redeploy → uncheck "Use existing Build Cache") for the new rewrite to take effect.

6. Production smoke tests

  • GET https://platform.tld/{orgSlug}/{agentSlug}/app → zone page renders inside the platform shell.
  • Network tab: _next/* requests are served from the platform's origin (proxy), not the zone's.
  • Submit a run → run row appears → run detail polls → terminal status.
  • curl -i https://zone.tld/{orgSlug}/{agentSlug}/app → 401 (zone is protected).
  • Same runId appears in both platform logs and zone logs.

Checklist

  • next.config.ts has basePath, assetPrefix, and allowedOrigins.
  • middleware.ts calls createZoneMiddleware with secret and audience.
  • Pages and server actions read state via getSession().
  • Platform calls go through createZoneClient — no direct fetch to platform endpoints.
  • All six env vars are set in production (no trailing slashes).
  • Skew Protection is off on both projects.
  • Deployment Protection is off (or preview-only) on production.
  • The platform admin has registered the zone URL and JWT secret for the agent.
  • Platform redeployed without build cache after the URL env var was added.

You're done. Visit /{orgSlug}/{agentSlug}/app on the platform to see the zone live.