Files
2026-09-07 21:21:31 +10:00
..
2026-09-07 21:21:31 +10:00
2026-09-03 22:45:09 +10:00
2026-09-03 22:45:09 +10:00
2026-09-07 16:50:09 +10:00
2026-09-07 19:53:58 +10:00
2026-09-03 20:13:14 +10:00
2026-09-07 21:21:31 +10:00
2026-09-07 16:50:09 +10:00
2026-09-07 16:50:09 +10:00
2026-09-07 21:21:31 +10:00
2026-09-07 16:50:09 +10:00

Assistant

The first application built with @supabase/agent-runtime, a framework for agents on Supabase Workers and AI SDK 7. Studio embeds Assistant through its HTTP integration. The project is expressed in supabase/: config.toml, declarative schemas, and workers.

Framework and application

The framework owns agent execution, tool composition, configurable permission enforcement, lazy skills, MCP connections, persistence lifecycle callbacks, authenticated Workers routes, and AI SDK streaming. Assistant supplies the Supabase product tools and prompts, consent policy, model selection, OAuth, and the complete Postgres schema and storage implementation. Studio owns its integration adapter and UI. The legacy generate-v4 backend remains separate.

Start with these application definitions:

  • src/ai/agent.ts: agent instructions, context, skills, and request-scoped tool resources.
  • src/ai/tools/mcp-tools.ts: named MCP transport, credential binding, and Studio tool aliases.
  • src/db/agent-persistence.ts: framework callbacks for canonical run startup, completion, and tool execution.
  • src/db/postgres-session-store.ts: application-owned SQL, transactions, history, and run events.
  • src/db/session-store.ts: database driver, table configuration, and HTTP error mapping.
  • src/ai/skills.ts: the skill catalog and lazy load_knowledge content.
  • src/ai/tools/tool-policies.ts: execution permissions, approvals, and model-output projections.
  • src/http/app.ts: Supabase Worker routes authenticated with Assistant sessions.
  • src/platform/identity.ts: one-time sign-in adapter for Studio's existing session.
  • src/platform/policy.ts: project access and model entitlement verified through OAuth.
  • src/http/chat-route.ts: canonical history, consent, streaming, and durable turn settlement.

Paths above are relative to supabase/workers/api/. Applications can define their own permission model; Assistant's concrete consent levels remain in src/permissions.ts.

Layout

supabase/
  config.toml
  schemas/                     # pg-delta source of truth
    _cluster/extensions.sql
    public/tables/*.sql
    private/schema.sql
    private/functions/*.sql
  migrations/                  # generated by declarative sync
  workers/api/                 # Node fetch worker (the project)
    index.ts                   # export default { fetch }
    local.ts                   # Node HTTP adapter for `pnpm dev`
    src/                       # worker implementation

Local

pnpm --filter assistant test
pnpm --filter assistant typecheck
pnpm --filter assistant dev   # http://localhost:8787

Tools: MCP server + harness overrides

The assistant is an agent harness over the Supabase MCP server, like Codex or Claude Code would be. supabase/workers/api/src/ai/tools/index.ts composes the tool set as:

  1. MCP base — getMcpTools connects to the MCP server with the user's OAuth token, project_ref bound from the conversation and read_only=true, and discovers dynamic tools. An Assistant-owned capability and consent allowlist filters the result; MCP write tools cannot override harness approval checks.
  2. Harness overrides — project-tools.ts re-implements execute_sql and deploy_edge_function with needsApproval (Studio's approval UI) via the Management API, and cannot be replaced by remote capabilities.
  3. Harness additions — rename_chat, list_policies, incident and support tools that MCP does not offer. The agent's skill catalog supplies load_knowledge.

The framework composes these sources with explicit overrides, checks execution permissions, and projects sensitive results before model conversion. Assistant uses the same projections to redact historical output after consent changes.

The MCP runtime owns discovery, aliases, connection cleanup, cancellation, and typed authorization errors. The agent's permission map enforces consent for both local and remote tools. Assistant owns turn claims, approval decisions, execution deduplication, and ordered lifecycle events in its Postgres adapter. It uses the framework history validator and plugs that adapter into AgentPersistence callbacks. The framework awaits these callbacks and manages the run lifetime without prescribing a database schema. GET /v1/conversations/:id/events?after=0&limit=100 returns owner-scoped event replay; it does not contain raw inputs or tool results. Events start with new runs after applying the migration; outcomes are not reconstructed for older request claims. Studio continues to use the existing chat stream and conversation endpoints.

One platform for OAuth, Management API, and MCP

project_ref, org_slug, and the OAuth token only mean something on the platform that issued them. The OAuth app, MANAGEMENT_API_URL, and the MCP server must therefore all belong to the same platform Studio is pointed at (NEXT_PUBLIC_API_URL in apps/studio/.env.local). Mixing them produces MCP error -32600: You do not have permission to perform this action on every tool call, because the token is valid but the org it is scoped to does not own the project. The authenticated /oauth/complete endpoint verifies the organization before storing tokens. The callback only returns a code to the initiating Studio window; that window retains the PKCE verifier.

MCP_URL is derived from MANAGEMENT_API_URL (api.supabase.com → mcp.supabase.com/mcp, anything else → <origin>/mcp); set it only to override.

Studio talks to Assistant .env
Local platform (http://localhost:8080/platform) MANAGEMENT_API_URL=http://localhost:8080, PLATFORM_AUTH_URL matching Studio's NEXT_PUBLIC_GOTRUE_URL, OAuth app registered in a local org
Production (https://api.supabase.com/platform) Leave MANAGEMENT_API_URL and PLATFORM_AUTH_URL unset; register the OAuth app in a production org

The OAuth app needs Projects Read (projects:read) and Organizations Read (organizations:read) to verify project access and model entitlement, in addition to the scopes needed by its tools. After changing the OAuth app's scopes, reconnect existing grants if they do not include those scopes.

supabase status in this folder also lists MCP http://127.0.0.1:55321/mcp. That is the CLI's unauthenticated MCP endpoint for the assistant's own local database, not for the user's project — do not point MCP_URL at it.

Schema

Edit supabase/schemas/**, then generate a migration:

npx --yes supabase@beta db schema declarative sync -f <name>

Worker secrets

Worker secrets cannot use the SUPABASE_ prefix: supabase secrets set skips them ("Env name cannot start with SUPABASE_"), and the Workers runtime does not inject SUPABASE_URL or keys either — a deployed worker only sees the secrets you set. Without them every route, including /health, fails with MISSING_SUPABASE_URL from @supabase/server.

Set the assistant project's own connection details under ASSISTANT_* (env.ts reads them as fallbacks for the SUPABASE_* names the local .env uses):

Secret Value
ASSISTANT_SUPABASE_URL https://<assistant-ref>.supabase.co (.red on staging)
ASSISTANT_PUBLISHABLE_KEY the project's sb_publishable_… key
ASSISTANT_SECRET_KEY the project's sb_secret_… key
ASSISTANT_DB_URL Postgres connection URL for privileged worker transactions
ASSISTANT_JWKS_URL optional; derived from ASSISTANT_SUPABASE_URL when unset

Plus OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_REDIRECT_URI, and OPENAI_API_KEY. Set MANAGEMENT_API_URL for a platform other than production.

PLATFORM_AUTH_URL is only used by the Studio sign-in adapter. It defaults to https://alt.supabase.io/auth/v1; for local development or staging, set it to Studio's NEXT_PUBLIC_GOTRUE_URL. It must use HTTPS except on localhost. ASSISTANT_POLICY_URL is no longer read and can be removed from existing deployments.

ASSISTANT_DB_URL must be the Session pooler string (postgres.<ref>@aws-0-<region>.pooler.…:5432). The direct db.<ref>.… host only has an AAAA record and the worker VM resolves IPv4, so it fails with getaddrinfo ENOTFOUND. The transaction pooler would work today but breaks the moment a query needs session state.

Secrets are applied when an instance starts (workers launcher: applied config bundle (N vars)), and the pg pool caches its connection string. After changing a secret, run pnpm --filter assistant workers:push to restart. The worker logs its env var names (never values) at boot, so the first log line after a deploy shows exactly which secrets the runtime applied.

Workers

Workers are experimental. Always use the beta CLI:

npx --yes supabase@beta experimental workers --help
npx --yes supabase@beta experimental workers new api --runtime node --size 2gb
pnpm --filter assistant workers:push   # builds index.mjs then pushes

See the framework README for the current architecture. PLAN.md and REVIEW_PLAN.md record earlier design and review work.

The feature flag assistantSupabaseBackend selects this integration. With the flag false, undefined, incomplete configuration, or self-hosted Studio, the existing generate-v4 backend and organization permissions remain unchanged. The environment override NEXT_PUBLIC_ASSISTANT_BACKEND=true only works in local development.

Studio initializes the integration in this order:

  1. Reuse the signed-in Studio user's session to obtain an Assistant session through POST /auth/exchange. Assistant validates the session directly with platform Auth, including MFA requirements. This happens silently and requires no extra sign-in.
  2. Read GET /v1/me. If the organization has no OAuth connection, show a consent button that opens the authorization popup. The initiating window retains the PKCE verifier and completes the connection.
  3. After connecting, ask the user to choose their permission level for the project.
  4. Load the conversation and enable chat after project consent is saved.

Grants belong to an Assistant user and project, with four choices: no project data, schema, schema and logs, or schema, logs and query results. Missing or obsolete grants require a fresh selection. Assistant's project consent controls data sharing; it does not inherit Studio's organization opt-in, HIPAA/sensitivity gate, or rollout policy. Project access and advanced model entitlement are checked directly through the OAuth connection before project operations. Expired or revoked connections return the interface to the connection step.

Authenticated API requests carry only an Assistant access token. They do not need a Studio session or call a Studio endpoint. Other surfaces can use the same API with an Assistant session; a future Slack integration would supply its own trusted identity-linking adapter. The Studio feature flag controls its interface, while Assistant's API enforces authentication, OAuth access, and its own permissions.

Public conversation tables are owner-readable and worker-write-only. Mutations use revisions and canonical stored history. Approved SQL/deployment operations are claimed durably before execution; an uncertain attempt is never replayed automatically. The browser reloads after persistence conflicts and never falls back to the old backend to retry an operation.

Validation

pnpm --filter assistant test
pnpm --filter assistant typecheck
pnpm --filter assistant lint
pnpm --filter assistant build
# Use a disposable local database with the committed migrations applied:
ASSISTANT_TEST_DB_URL=postgresql://postgres:postgres@127.0.0.1:55322/postgres pnpm --filter assistant test:db

The database suite creates synthetic users and removes their rows. It covers RLS, worker ownership, revision conflicts, OAuth binding, and duplicate approved writes. CI starts its own disposable Supabase stack and checks declarative schema drift. Generate database types with supabase gen types typescript --local --schema public from this app and write the output to supabase/workers/api/src/db/database.types.ts.

Hosted Workers ingress, real OAuth popup completion, and deployment rollback still require verification against the deployed application.

Compatibility

Existing organization permissions belong to generate-v4; the new integration requires fresh per-user, per-project consent after OAuth authorization. Existing Assistant identities, OAuth connections, and project grants continue to work. This authorization change requires no new database migration; apply all committed migrations when setting up an Assistant database.

The worker contract covers SQL chat, approved SQL/deployment tools, MCP read tools, conversation history, branches, feedback, and support lifecycle metadata. Reports, notebooks, Braintrust tracing, and rating categorization remain outside that contract. Keep users who need those capabilities on the legacy backend during dogfood.

Separate product boundary

Assistant owns its per-user/project grants, consent version, permission choices, capability calculations, MCP allowlist, and data redaction in the worker's src/permissions.ts. No permission code is shared with Studio or generate-v4.

Studio's lib/assistant/ and data/ai-assistant/ are the integration adapter. Its local response schemas validate the v1 HTTP shape. Permission selections are opaque strings; choices, disabled states, consent version, and context-sharing capabilities come from Assistant. Changing those choices does not require changing Studio's legacy permission enum or organization opt-in model. Each application owns its wire compatibility fixtures, so server changes cannot silently update client expectations through a shared import.

The optional Studio sign-in adapter verifies platform identity once when establishing an Assistant session. Normal Worker requests verify that Assistant session directly. Project authorization uses the user's stored OAuth connection and public Management API endpoints, independently of which surface sends the request.

For Studio review, start with the feature switch and the isolated integration folders, then inspect the state/composer hooks that connect them to the existing interface. The legacy SQL endpoint, consent hook, and settings modal are unchanged.