Skip to main content
Convex owns durable app state, domain mutations, scheduled work, and some webhook processing. The web app depends on Convex for conversations, files, memory, projects, billing state, and related data. Treat each web deployment and its Convex deployment as one release unit. Push the development deployment from the dedicated staging worktree for staging QA, then push production from the clean main worktree after the matching web deployment is live:
Do not use convex:push:all in a worktree workflow. Never pass .env.local to a production Convex deploy; use the dedicated production script instead.

Development backend

For ordinary local web work against the development Convex backend, run Next.js without deploying Convex from a feature worktree:
npm run dev:with-convex pushes development Convex before starting Next.js. Use it only from the dedicated staging worktree, where the current revision is the shared staging release candidate.

Production backend

Use the production script only from the clean canonical main worktree after the matching main web deployment is live:
For the full worktree, staging QA, and promotion process, see Worktree Staging QA.

Import boundary

Convex code may import browser-safe shared modules from src/shared. Convex must not import src/server, because server-only modules can break Convex bundling.

Browser auth for reactive queries

Convex queries and mutations cannot call fetch(). WorkOS / Better Auth JWKS verification therefore cannot run inside watchRoomMessages or other browser useQuery handlers. Browser→Convex subscriptions must use short-lived HS256 tokens:
  1. Next BFF GET /api/auth/convex-token mints a token with INTERNAL_API_SECRET (mintBrowserConvexAccessToken).
  2. ConvexAuthProvider passes that token into Convex client queries.
  3. Convex requireAccessToken verifies HS256 with crypto.subtle only (verifyBrowserConvexAccessToken).
INTERNAL_API_SECRET must match between the Next app env and the Convex deployment. Do not send the raw WorkOS access token into reactive queries. Collaboration realtime is native Convex when appDataCapabilities.provider === 'convex':
  • watchRoomMessages owns room transcript updates.
  • watchConversationListVersion provides a metadata-free invalidation signal for the sidebar.
  • watchNotifications is the single app-level notification subscription.
Do not run /api/v1/conversations/events long-polls alongside those subscriptions. That endpoint is a Postgres-only fallback; polling it against a Convex repository multiplies one browser connection into repeated Convex queries and authorization calls.

Bounded maintenance and list queries

Maintenance jobs and user-facing lists must advance through indexed, bounded pages. runEmptyConversationCleanup persists its pagination cursor in maintenanceCursors, and files/files:listPage is the cursor-based file listing source. Keep legacy list functions bounded during web/Convex deployment overlap; never restore a growing-table .collect() for compatibility.

Chat-delta retirement migration

Removing conversationMessageDeltas requires a two-revision Convex rollout. From the dedicated staging worktree, deploy the phase-5 migration revision and run internal.chat.conversations.collapseLegacyConversationPersistence once. Its scheduled batches fold every remaining delta into its assistant message and mark generating messages without an active AgentRun as interrupted. Confirm the scheduled chain has finished before deploying the following phase-5 cleanup revision, which removes the table, queries, and crons. Never skip directly to the cleanup revision on a deployment that may still contain delta rows. Postgres migration 0060_retire_conversation_message_deltas performs the same collapse and table removal transactionally. Migration 0061_agent_run_metrics adds the runner-observation document used for Chat-versus-Work measurement. Schema 61 retains minimum compatible runtime 60 because its metrics column is nullable; runtimes older than 60 still query the removed delta table. Take a database backup and treat migration 0060 as an explicit rollback boundary.