Overview

A zone app is a separate Next.js application that provides a custom UI for a single agent. Zones are useful when the agent needs richer interaction than the platform's built-in input/output pages can provide — interactive review screens, complex multi-step inputs, custom dashboards.

How it works

  1. You build and deploy a Next.js app — your zone.
  2. A platform admin registers the zone's URL and a shared JWT secret against an agent.
  3. When a user navigates to /{orgSlug}/{agentSlug}/app/* on the platform, the platform mints a per-request JWT signed with the agent's zone_jwt_secret and rewrites the request to your zone.
  4. Your zone verifies the JWT (via createZoneMiddleware), reads the claims (via getSession), and calls platform endpoints on the user's behalf (via createZoneClient).

From the user's perspective, the zone feels like a native part of the platform — same URL, same auth, same navigation.

Request flow

Browser
  │  GET https://agents.collectivexp.ai/{orgSlug}/{agentSlug}/app[/...]
  ▼
Platform middleware
  • reads the platform session → { userId, orgId, agentId, role }
  • mints a per-request HS256 JWT with the agent's zone_jwt_secret
  • rewrites to the zone, forwarding the JWT as `x-zone-token`
  ▼
Zone middleware (createZoneMiddleware)
  • verifies `x-zone-token` against ZONE_JWT_SECRET
  • attaches decoded claims on `x-zone-claims`
  ▼
Zone page or server component
  • reads { claims, token } via getSession()
  ▼
Zone server action
  • forwards `token` as `Authorization: Bearer` to a platform zone endpoint
  ▼
Platform `/api/zones/agents/{agentId}/runs[…]`
  • re-verifies the token, cross-checks agentId in the URL against the claim,
    then executes the requested operation

The zone owns the entire /app/* subtree — /app, /app/runs, /app/settings, and any other page routes it defines. The platform reserves only the /app/* prefix.

API route handlers are the exception: a zone does not use them. A client-side fetch('/api/…') resolves against the platform's origin rather than the zone's, so the request never arrives. Client-to-server calls go through server actions — see Platform client.

Static assets are served on the same origin: the browser fetches /assets/{orgSlug}/{agentSlug}/_next/* from the platform, which proxies to the zone. Same-origin asset delivery is load-bearing — see Deploy for why.

Deep-linking

Anything that needs to link to a specific run from outside the zone (HITL emails, Slack notifications, dashboards) uses:

/{orgSlug}/{agentSlug}/app?run=<runId>

Honour this query param in your landing page — the platform's HITL notification system routes zone-backed agents to /app?run=<runId> automatically, and dropping the convention breaks those links.

For human-in-the-loop agents, the landing page can do more than display the waiting run — it can resume it. Render your review UI for a waiting run and call cxpa.runs.completeWaitpoint(runId, decision) (platform client) to complete the waitpoint and let the task continue.

What lives in the zone vs. the platform

In the zoneIn the platform
Bespoke UI for this agentOrg / user / role management
Domain-specific workflowsRun history and observability
Run triggering via createZoneClientRun execution via the runtime
Custom layouts and componentsAuth, RBAC, billing, settings

The zone never holds long-lived platform credentials. Every request comes with a fresh JWT scoped to the agent and the user.

Prerequisites

  • Node 20+
  • A registered agent on the platform (a platform admin will share zone_jwt_secret and audience).
  • Familiarity with Next.js App Router.

Walkthrough

The remaining pages in this section walk through a complete zone build:

  1. Scaffold — Next.js project setup, basePath, assetPrefix, allowedOrigins.
  2. Auth — verifying the zone JWT.
  3. Session — reading claims in server components.
  4. Platform client — triggering runs, resolving credentials.
  5. Deploy — registering the zone and going live.

This section is the mechanics: auth plumbing, configuration, and the SDK surface. It deliberately says nothing about your own data — where a zone's domain records live, how to render a list when the platform has no list-runs API, and which of org, user and role scoping the platform does not do for you. That is Zone data and tenancy, and any zone with a database of its own should read it: the isolation guarantees stop at the deployment boundary, and everything finer is yours to write.

Two things readers commonly ask before starting, both answered outside this section:

  • A zone does not replace scheduling. One agent can have a zone app, schedules and external triggers at the same time — the surfaces are independent, and an agent that runs on a schedule while its zone offers a manual "run now" is an ordinary shape. See Create the agent.
  • The platform does not enforce roles inside your zone. Every member of the bound organization reaches every page with a valid token; claims.role tells you who they are and gates nothing. If an action should be restricted, check it yourself in the server action — see Session.