Skip to main content
Overlay is a Next.js app backed by Convex, server-mediated provider adapters, and runtime-configurable auth, billing, storage, model, tool, and telemetry providers.

Source Of Truth

  • src/app/api/v1/** is the public backend surface for web, mobile, desktop, and extension clients.
  • src/server/app-api/v1/** holds the route orchestration behind those Next route wrappers.
  • src/server/** holds server-only auth, billing, storage, AI, tools, route services, and provider adapters.
  • src/shared/** holds isomorphic contracts and client-safe helpers.
  • src/contexts/** holds app-wide client providers (AuthContext, WorkspaceContext); src/hooks/** holds app-wide client hooks (useWorkspaceChanged). Both may import @/shared only.
  • src/features/** holds web feature containers and feature-local helpers. Cross-feature imports are banned; shared primitives belong in @/shared, @/contexts, or @/hooks (e.g. the workspace provider, routing helpers, and types that used to sit inside features/workspaces).
  • src/server/workflows/** holds Vercel Workflow SDK durable orchestration (automation-run, automation-schedule, personal-chat-work, workspace-agent-turn). Workflow bodies bundle for the workflow runtime — keep their imports to "use step" functions and @/ alias modules only.
  • convex/** owns durable app state, domain mutations, scheduled work, and Stripe webhook handling.
  • scripts/ is split by purpose: scripts/ci/ (checks, boundary rules, baselines consumers), scripts/qa/ (smoke tests, rehearsal/readiness harnesses), scripts/db/ (migrations, backfills, ops), scripts/lib/ (shared script helpers). scripts/dev-setup.sh and scripts/vercel-ignore-build.sh stay at root — the latter is pinned by the Vercel project ignore-build command.
  • packages/overlay-app-core/** holds shared cross-surface contracts and UI settings types.
  • packages/overlay-api-client/** holds typed transport wrappers for /api/v1/**.
  • packages/overlay-ui/** holds the shared presentation primitives (buttons, inputs, dialogs, and the list-page system: Tile/TileGrid/TileIcon/TileSkeleton/CreateTile for grid tiles, ListRow for list rows, and HeaderSearch for collapsible header search). List pages (Projects, Knowledge, Files, Agents, Extensions) must render their tiles, rows, and headers through these primitives instead of hand-rolled markup so spacing, radii, hover language, and dark-mode surfaces stay uniform.
  • packages/overlay-agent-bridge-protocol/** owns the vendor-neutral, versioned Zod wire protocol between Overlay and connected execution environments.
  • packages/overlay-agent-host/** is the portable outbound-only host executable. It owns local device keys, SQLite delivery state, filesystem grants, and fake/ACP adapters; it does not own workspace identity, transcripts, approvals, policy, or billing.
  • packages/overlay-sandbox-runtime/** is the provider-neutral managed execution boundary. It owns sandbox lifecycle, command/file/port/snapshot/network/usage contracts and Vercel/Daytona translations. Provider SDK types and raw handles do not cross into public routes, billing, or connected-agent persistence.
App-data portability is capability-driven: src/server/app-data/capabilities.ts and src/server/app-data/route-support.ts are the source of truth for what Postgres mode exposes. Unsupported routes must return the structured app_data_route_not_supported response before domain services or Convex are called. Workspace collaboration and connector mappings are currently Convex only; Postgres must not silently fall back to Convex for them. Connected-agent persistence is provider-neutral through ConnectedAgentRepository. Environment, binding, command, remote-session, approval, sandbox-lease, enrollment, proof-challenge, short-lived credential, and request-replay records have matching Convex and PostgreSQL schemas. Remote output advances a per-session contiguous event cursor transactionally; it projects into the existing conversation and AgentRun lifecycle rather than creating a second transcript. A connected-agent environment is a machine or runtime boundary, not a working-directory or single-agent boundary. Bindings choose an agent and adapter; each command chooses an explicitly granted working directory. The host can grant multiple roots or explicit OS-user-wide access. Protocol versioning, validation, sequencing, enrollment proof canonicalization, and signed HTTP transport remain in the bridge/host packages so neither Convex nor PostgreSQL becomes a public host API. ConnectedAgentControlPlaneService is the server-only policy boundary for enrollment and host traffic. Browser management routes use the normal authenticated BFF and active-workspace resolution. Host routes are explicit BFF exceptions because they use outbound machine auth: method-scoped opaque credentials plus Ed25519 request proofs and atomic nonces. Route handlers never select a provider directly; both modes use ConnectedAgentRepository from app data. ManagedAgentSandboxService provisions the private runtime selected by operators, starts the same Agent Host image used by every environment, and records the provider-neutral sandbox lease. The browser sees Overlay Cloud, an environment, and lease state, not a Vercel or Daytona object. Clients should not call model providers, billing providers, identity admin APIs, object storage, or Convex directly unless a route explicitly exists for that surface. src/app/_components/AppShellLayout.tsx is the reusable web shell boundary. Canonical /app/** routes, ordinary auth flows, and public full-page product destinations compose it from their route-group layouts. The shell accepts an explicit showcase mode so public routes do not depend on hidden rewrite query parameters. Protocol callbacks, integration popups, public share links, and visual fixtures remain standalone by contract.

Domain Decomposition

Workspace behavior stays behind the existing provider/repository contracts while large seams are split by responsibility. The WorkspaceSwitcher controller owns navigation and lifecycle state; WorkspaceSwitcherView owns the trigger, menu, and dialog presentation. Postgres workspace governance writes build their SQL in src/server/workspaces/workspace-sharing-policy-sql.ts, keeping policy patch semantics separate from repository orchestration without changing the contract. Personal and organization workspaces use the same collaboration capabilities: people, agents, direct messages, channels, teams, invitations, and resource grants all follow the same service and repository contracts. Personal differs only in lifecycle: it is the account’s permanent default workspace, cannot be archived through workspace lifecycle APIs, and its creating principal remains the canonical active owner. Permanent account-deletion erasure is a separate audited compliance path.

Personal Chat execution lifecycle

Interactive Personal Chat runs use the provider-neutral AgentRun contract in src/shared/agents/agent-run.ts. AgentRun.status is the only persisted authority for queued, running, approval-waiting, completed, failed, or cancelled execution; conversationMessages.status is a rendering projection. Convex and Postgres create the assistant placeholder and AgentRun together, and complete, fail, or cancel both records in one storage transaction. Chat mode continues to use the AI SDK ToolLoopAgent. It streams directly to the connected browser, while Next after() drains a tee of the stream for a persistent conversation. Overlay does not write token deltas for this path and does not use the Cloudflare chat relay. The final assistant content, parts, usage, routed model, and terminal AgentRun state are written once when the run ends. On reconnect, the client polls GET /api/v1/conversations/run and reloads the final message; a non-terminal run shows the disconnected generating state. ToolLoop runs tolerate browser disconnection but not a process crash. Their lease expires to a structured tool_loop_lease_expired failure through the Convex cron or Postgres maintenance worker. Stop first cancels AgentRun, then best-effort aborts the in-process AbortController; late completion cannot overwrite cancellation. Work mode uses @ai-sdk/workflow’s WorkflowAgent behind the same AgentRun contract. The request route performs the same authorization, context and memory construction, model policy, tool filtering, billing reservation, and user-message persistence as Chat mode, then starts workflows/personal-chat-work.ts. Model calls and tool executions are durable Workflow steps; the workflow ID is stored on AgentRun. Work mode exposes the workflow’s writable stream as a UI message SSE stream: the WorkflowAgent writes ModelCallStreamPart chunks to the run stream and the route pipes them through createModelCallToUIChunkTransform. The client renders tokens, tool calls, and approval requests in real time, then receives the final persisted message through the normal conversation data path. Approval-required tools transition AgentRun to waiting_for_approval with a durable hook token and tool-call summary. POST /api/v1/conversations/run/approval resumes the hook; the workflow returns the same run to running. Stop cancels both AgentRun and the Workflow run. Internal Overlay tool mutations receive agent-run:<runId>:tool:<toolCallId> as their idempotency key. Connector providers outside Overlay must still honor their own idempotency semantics; Workflow step replay alone cannot make an external API idempotent. Both runners write comparable observations onto the terminal AgentRun. Chat records first-token and completion latency from its direct stream; Work records first-token latency from the workflow stream, completion latency, and durable step observations. Provider token usage and estimated provider cost, tool outcomes and retries, cancellation timing, browser-disconnect outcomes, lease/process-loss observations, and stale-run detection feed GET /api/v1/conversations/run/metrics. The report keeps raw sample counts and denominators and never recommends a runner; product decisions must be made from the resulting evidence.

Auth

  • Browser auth uses the signed httpOnly overlay_session cookie.
  • Native/mobile/desktop auth uses supported bearer access tokens against the same /api/v1/* backend.
  • Internal server-to-server calls use short-lived service auth from src/server/auth/service-auth.ts.
  • Shared route authentication flows through the API boundary helpers in src/server/auth/app-api-auth.ts.
Every sensitive /api/v1/* route should reject requests that lack a valid browser session, mobile bearer token, or service auth token.

App Bootstrap

GET /api/v1/bootstrap is the canonical signed-in startup call. It returns user info, entitlements, UI settings, model catalogs, feature flags, navigation destinations, and defaults. New clients should start from this route and reuse existing API contracts before adding client-specific endpoints.

Billing

Stripe is the hosted billing provider for subscriptions, top-ups, auto top-up preferences, customer portal sessions, and webhook events. Private deployments can disable billing with runtime config. Convex is the durable entitlement/usage source of truth after webhook processing. Billing ownership is represented separately from user identity by an additive BillingAccount contract. An account has exactly one scope owner: a user for personal, or a workspace for workspace. markup_25_v1 explicitly preserves the current 25% markup policy. New personal accounts start with an empty budgeted balance so account creation never grants spend implicitly. The compatibility migration creates one personal account per user, attaches legacy billing and usage rows, and dual-writes user and account identifiers plus canonical subscription/balance shadows. User-keyed records remain the enforcement and rollback source until balance parity and exact Stripe-subscription verification pass; the migration never switches reads automatically. Migration balance parity sums only active reserved and reconcile_required rows through the compound billing-account/status index. Historical finalized and released reservations are deliberately excluded, so a high-volume account does not hit the active-reservation safety cap merely because it has more than 1,000 completed operations. BillingPayerResolver separates workspace authorization from payment selection. The workspace-wallet master flag and off -> internal -> selected -> general rollout select which organization workspaces have switched. Personal workspaces and non-selected organizations retain the compatibility path. Once selected, an organization call requires an active workspace account and has no personal fallback. Workspace reservations update the account-keyed balance and an optional member or programmatic fixed-period limit atomically; the original personal reservation method remains unchanged for rollback. The app bootstrap and subscription read use that same resolver. Active members of an eligible organization therefore receive the workspace account’s plan and budget for model gating, while mutating billing operations remain restricted to owners and admins. Workspace-scoped clients send the active workspace header explicitly; a member’s personal subscription is not treated as the organization’s entitlement or payment fallback. All enabled hosted-cost boundaries now pass the resource workspace plus a member or programmatic subject into the resolver. This includes shared chat and named agents, Postgres and Convex knowledge embeddings, browser/Daytona, hosted media and transcription, gateway search, and automation/API execution. Scheduled automations use automation:{id} and API keys use api-key:{id}. Vercel Workflow overhead is a separate 20-event estimate charged at the provider event rate plus markup_25_v1. Integrations, functions, and MCP execution are bounded to 12 tool steps per agent turn and 50,000 response characters; file ingestion is bounded to 20 requests per user per hour plus the plan storage cap. src/server/billing/billable-feature-coverage.ts is the launch inventory and its test fails when a category lacks metering or a concrete quota. Chat reservations price the full 12-step model-call envelope, including the per-step 8,192 output-token ceiling. A tool loop must not reserve only its first model call: later completed steps would otherwise exceed the reservation and be quarantined even when provider usage is valid. Vercel Sandbox is the default managed sandbox provider. Its reservation is based on worst-case active vCPU time, provisioned-memory wall time, outbound transfer, and sandbox creation, with an operator-configured safety buffer and pre-provider per-run cost ceiling. Settlement uses provider SDK counters after the session stops; incomplete counters fail into reconciliation instead of an estimated charge. The standalone sandbox route always denies outbound network access because a user-space polling guard cannot make a byte-exact provider-cost promise. It rejects oversized artifacts before provider transfer, and its reservation includes the maximum bounded artifact transfer. Networked agent sandboxes use separate metered flows. The default iad1 region and its configured unit rates must move together. Blank pricing or buffer variables fall back to conservative defaults rather than silently becoming zero. Workspace Stripe ownership is account-keyed, never admin-keyed. Owners and admins may initialize the account and open checkout, top-up, verification, and portal flows, but Stripe metadata and durable subscription state bind to billingAccountId plus workspaceId; userId is retained only as the initiating actor. Both the Next/Postgres webhook and hosted Convex webhook preserve the personal metadata path, deduplicate event IDs, and reject stale subscription state with providerEventCreatedAt. Usage reservations have an evidence-gated recovery path. Work that expires before a provider starts is released automatically. Work that may have reached a provider is quarantined as reconcile_required; it must be finalized or released with a durable provider or internal-operation evidence reference. The five-minute Convex cron and Postgres worker expose queue depth and oldest age but never guess an ambiguous charge. Finalized usage preserves retail cost and actual provider cost independently. UsageRepository.getBillingAccountOperationalReport is implemented by both Convex and PostgreSQL and reports retail credits, actual cost, realized margin, cost coverage, reconcile queue depth, and the count older than the 15-minute response SLA. The workspace billing UI exposes this only to owners and admins. Important code:
  • src/shared/billing/billing-pricing.ts
  • src/shared/billing/billing-account.ts
  • src/shared/billing/billing-account-migration.ts
  • src/server/billing/billing-runtime.ts
  • src/server/billing/stripe-billing.ts
  • src/server/billing/BillingCustomerService.ts
  • src/server/billing/BillingCheckoutService.ts
  • src/server/billing/BillingPayerResolver.ts
  • src/server/billing/billable-feature-coverage.ts
  • src/server/billing/automation-workflow-billing.ts
  • src/server/billing/WorkspaceBillingService.ts
  • src/server/billing/providers/stripe-billing-provider.ts
  • convex/billing/subscriptions.ts
  • convex/billing/accounts.ts
  • convex/billing/accountMigration.ts
  • convex/billing/accountSubscriptions.ts
  • convex/billing/spendLimits.ts
  • convex/platform/usage.ts
  • convex/billing/stripe.ts
  • convex/billing/stripeSync.ts
  • src/shared/billing/usage-reconciliation.ts
  • src/server/usage/UsageRepository.ts
  • scripts/qa/usage-reconciliation-audit.ts
  • scripts/db/personal-billing-account-backfill.ts
  • scripts/qa/stripe-personal-account-verification.ts

Storage

Uploaded files and generated assets are stored through server-mediated flows. Cloudflare R2 remains the managed object store, but it is not used for DNS, web hosting, request proxying, or chat streaming. R2 and S3-compatible object access must remain private and owner-scoped; users should only receive short-lived, validated access.

Chat Streaming

Interactive chat streams directly from the Vercel-hosted /api/v1/conversations/act route to the browser. Persistent conversations tee a server-side reader so generation, usage accounting, and message persistence continue after a browser disconnect. Reconnects recover from the authoritative persisted conversation state; there is no external stream relay or stream-auth endpoint.

Observability and Metrics

The measurement layer uses PostHog as the unified metrics sink, emitting overlay.metrics.* events from both server and client. All emission is fire-and-forget — a metrics failure must never break a customer-facing request or crash the UI.

Server-side metrics

  • src/server/observability/metrics.ts — typed emission contract for BFF requests, Postgres queries, Convex functions, model token breakdowns, AgentRun lifecycle, workflow events, uploads, and business rollups.
  • src/server/observability/business-rollup.ts — periodic aggregation of cost-per-active-user-hour, cost-per-chat-turn, and cost-per-automation.
  • src/app/api/v1/_utils/bff.tshandleBffRoute emits a overlay.metrics.bff_request event on every exit path (early returns + final return) with route, method, status, duration, auth type, workspace, payload size, and Retry-After.
  • src/server/database/postgres/client.ts — pool wraps each client’s query method to emit overlay.metrics.postgres_query with duration and rows returned.
  • src/server/conversations/ActContextService.tsemitTokenBreakdown estimates per-source token counts (history, memory, skills, tools, attachments, system) and emits overlay.metrics.model_tokens.

Client-side metrics

  • src/shared/observability/client-metrics.ts — isomorphic event bus using window.dispatchEvent(CustomEvent). No PostHog import; the ObservabilityClient component listens and forwards to PostHog.
  • src/shared/observability/duplicate-tracker.ts — windowed duplicate request detection (1s, 5s, 15s windows) emitting overlay.metrics.duplicate_request.
  • src/components/providers/ObservabilityClient.tsx — forwards client metrics events to PostHog via addMetricsListener.
  • src/shared/chat/chat-list-cache.ts — emits cache hit/stale/miss events.
  • src/features/chat/components/chat/chatTransport.ts — emits chat-open latency, transcript bytes, and message count.
  • src/features/chat/components/chat/useAgentRunLifecycle.ts — emits AgentRun recovery metrics on terminal transitions.
  • src/contexts/AuthContext.tsx — emits session refresh metrics with trigger source (interval, focus, visibility, manual).

Convex function metrics

  • convex/lib/metrics.tswithMetrics wrapper for mutation handlers, records function name, duration, and error status to the functionMetrics table.
  • convex/platform/metrics.ts — cleanup cron (7-day TTL) and aggregation query for export to PostHog.
  • convex/schema.tsfunctionMetrics table with indexes by function name and timestamp.
  • convex/platform/rateLimits.ts — instrumented with withMetrics as the highest-frequency Convex mutation.

Privacy

Metrics events contain only: route names, opaque IDs, sizes, counts, durations, auth type, workspace ID, and categorized metadata. No conversation content, message bodies, authorization tokens, private workspace JSON, or raw user data is sent to analytics.

Convex

Development commonly uses a separate dev deployment from production. Follow Worktree Staging QA and Convex Workflow for the deployment lane that matches the current revision. Deploy Convex only from the matching release worktree:
Never run a Convex deploy from a feature worktree, never use convex:push:all, and do not pass .env.local to production Convex deploy commands; that can point deploys at the dev slug.