Skip to main content
Fleet Pi loads configuration from .env at the repo root, then .env.local (.env.local takes precedence). The dev server is responsible for loading these files into the server-side routes; values are accessed through process.env at runtime. Credentials saved from the in-app Configurations panel are written to .env.local. The canonical example lives in .env.example. This page is the authoritative reference for every variable Fleet Pi reads, grouped by concern.

LLM providers

Fleet Pi picks the default model by surface:
  • Local dev and anonymous chat default to Google Gemini (gemini-3.5-flash). Set GEMINI_API_KEY to use it, or pick another provider from the in-app config panel.
  • Deployed authenticated chat defaults to the Neon AI Gateway with qwen35-122b-a10b as the primary model and gpt-oss-120b also enabled. A user’s OpenAI-Chat-Completions (OCC) BYOK setting always takes precedence when configured.
Pi settings store the active provider and model, and you can change both from the in-app config panel. The same panel manages provider API keys and writes them to .env.local.

Amazon Bedrock

When using Bedrock, Fleet Pi uses the standard AWS credential chain — environment variables, profile, or IAM role. Fleet Pi defaults to Google Gemini (gemini-3.5-flash). The default provider and model are set in .pi/settings.json:
Change those fields to switch the default provider; set the matching API key in .env (or via the in-app config panel). Every provider supported by Pi is available — pick whichever credentials you already have.

Amazon Bedrock (opt-in)

When defaultProvider is amazon-bedrock, Fleet Pi uses the standard AWS credential chain: You can also provide AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY directly. Bedrock model IDs use region prefixes such as us.anthropic.claude-sonnet-4-6.

Pi runtime

Logging

The logger lives in apps/web/src/lib/logger.ts. It includes PII redaction and emits a requestId correlation ID for every chat request, which lines up with provider circuit-breaker events for incident review.

Authentication (Better Auth)

Auth is disabled until you set BETTER_AUTH_SECRET. When the secret is present, Better Auth is mounted at /api/auth/*. The auth store can be local SQLite (default) or Neon Postgres. The Google login button is hidden in the UI when either Google variable is missing. When FLEET_PI_AUTH_DATABASE_URL is set, Better Auth uses Neon instead of local SQLite — apply schema once per environment with pnpm --filter web auth:migrate.

Neon Managed Auth (optional)

Fleet Pi runs Better Auth by default. Set NEON_AUTH_BASE_URL (or the NEON_AUTH_URL value that the Vercel↔Neon integration injects) to proxy sign-in, session, and account routes to Neon Managed Auth instead. Leaving both URLs unset keeps the Better Auth + SQLite fallback, so local anonymous chat still works. Use Managed Auth when you want Neon to own user identity across the app and the dual-host chat runtime.
Neon Managed Auth currently allows open sign-up. Until Neon ships restricted signups, treat any production deployment as an invite-only closed beta — share the URL only with intended testers, and keep the Neon Data API disabled in both neon.ts (dataApi: false) and the Neon console. Fleet Pi enforces tenant isolation through the private fleet_pi_app role plus FORCE RLS on pi_* tables; granting Data API access to authenticated or anonymous roles bypasses that. pnpm verify-deployment-readiness fails when those grants are still present.

Neon AI Gateway (default authenticated chat)

On deployed environments, authenticated chat routes through the Neon AI Gateway as the platform OpenAI-Chat-Completions (OCC) backend. This gives every signed-in user a working model out of the box — no BYOK required — while a user who has saved their own OCC provider settings still takes precedence. Fleet Pi enables two Gateway models by default:
  • qwen35-122b-a10b — primary
  • gpt-oss-120b
Use the Gateway when you want authenticated users to have working chat immediately after login without asking each user to bring an API key. Anonymous and local dev surfaces still fall back to Google Gemini (gemini-3.5-flash) — the Gateway only activates when the user is signed in and the two env vars below are set. Both values are captured into process memory once at boot and then deleted from process.env so agent shell tools cannot read them with printenv. This means you should not rely on NEON_AI_GATEWAY_* being visible to your own code past startup.

URL shape

Fleet Pi enforces a single /v1 suffix on the Gateway base URL. All of these normalize to the same value:
The host must resolve to *.neon.tech. Any other host is rejected at boot and the Gateway is skipped rather than serving requests to an untrusted origin.

BYOK precedence

When a user saves an OpenAI-Chat-Completions provider in the config panel, their BYOK settings win over the platform Gateway. Legacy OCC records are only migrated to the platform Gateway shape when the Gateway is active and the user has not brought their own OCC credentials.

Named OpenAI-compatible instances

Each user can save multiple OpenAI-compatible Chat Completions endpoints side by side — for example one instance for OpenCode Zen and another for Nebius — instead of overwriting a single BYOK slot. Every named instance keeps its own display name, base URL, model ID, and API key, and each one appears in the model picker as a separate provider row. Named instances work on both deployed chat (signed-in users) and local anonymous chat. Use named instances when you want to:
  • Route different chats through different OpenAI-compatible backends without editing settings between turns.
  • Keep a per-vendor label in the config panel so it’s obvious which endpoint you’re about to use.
  • Add a new OpenAI-compatible provider without disturbing your existing default OCC configuration.
Add an instance from the in-app config panel under OpenAI Chat Completions → Add instance. Fleet Pi requires four fields per instance: Fleet Pi picks the storage backend for named instances based on the surface: The file store is written atomically (temp file + rename) with a per-process mutation lock, so concurrent creates get distinct slugs. A malformed or future-version store falls back to an empty list with a diagnostic instead of breaking chat session creation. The .fleet/ directory is gitignored, but treat .fleet/providers.json as sensitive — it contains plaintext API keys.
Named instances must use an https:// base URL by default. On local dev surfaces, OCC-family instances can also point at http://localhost (useful for pointing at an Ollama or LM Studio process). Deployed chat still requires https:// at both save time and runtime registration, so a legacy http:// value can never sneak through.
The default OCC slot (openai-chat-completions) still exists alongside named instances and continues to take precedence over the platform Neon AI Gateway. Named instances give you additional endpoints without replacing that default. Fleet Pi also validates each instance every time the runtime registers it. If the stored API key can’t be decrypted or the base URL fails the safety checks, the instance is skipped with a warning diagnostic and shows up in the Settings providers list as Not configured instead of a misleading healthy row — so the model picker never advertises an endpoint that would fail at request time.

Readiness gate

pnpm verify-deployment-readiness validates that NEON_AI_GATEWAY_BASE_URL is a well-formed allowed Gateway URL — not just present. Deploys fail closed when the URL is malformed or points off the *.neon.tech allowlist, so a broken Gateway variable cannot silently ship.

Dual-host chat runtime (optional)

Fleet Pi normally serves the chat streaming API from the same Vercel app that hosts settings, providers, and the workspace. Setting VITE_FLEET_PI_CHAT_RUNTIME_URL splits chat onto a separate host (typically a Neon Function) while settings, providers, and workspace stay on Vercel. The browser attaches a Neon Managed Auth bearer JWT to every chat request, and the runtime verifies it against NEON_AUTH_JWKS_URL and NEON_AUTH_ISSUER. Use this when you want chat to scale independently or when your Neon Function should own session object storage. Example Vercel environment for a dual-host deployment:

Sessions and workspace paths

Pi session files are persisted under .fleet/sessions/ inside the repo. The session manager rejects paths outside the repo-scoped directory via isUsableSessionFile, so a stale sessionFile in localStorage silently falls back to a fresh session — see runbooks for recovery. Canonical durable state lives under agent-workspace/. The workspace server reads canonical files directly and uses agent-workspace/indexes/ only as projection storage.

Chat session mirror (Neon Postgres)

Pi session JSONL files under .fleet/sessions/ are always the source of truth. When FLEET_PI_CHAT_DATABASE_URL is set, Fleet Pi additionally mirrors full Pi session entries, run events, tool executions, and file mutations into Neon Postgres tables prefixed with pi_. Use this when you want SQL search across conversations, cross-surface history, analytics, or long-term debugging. Mirror failures are caught and logged — they never break chat streaming. Use two separate roles in Neon: Apply migrations once per environment before starting the app:
See runbooks for the full table list and operational guidance.

Daytona-backed user sandboxes

Authenticated users can be assigned an isolated Daytona sandbox that runs Pi tool calls in a container instead of on the host. Each user gets one sandbox in their Daytona account via BYOK (bring your own key), keyed by their Better Auth userId. Sandbox routes require Better Auth — unauthenticated requests return 401.

How Daytona is enabled per user

Fleet Pi enables Daytona for a user only when:
  1. The user is authenticated through Better Auth.
  2. A Daytona API key is resolved for that user.
Fleet Pi resolves the Daytona API key from the user’s stored provider secrets first (Settings → Providers → Daytona). If none is found, it falls back to the DAYTONA_API_KEY environment variable only in local development. On Vercel, env DAYTONA_API_KEY alone does not enable Daytona — each logged-in user must save their own Daytona API key as the daytona provider secret. When Daytona is not enabled for the calling user, GET /api/sandbox/preview returns 503 and tool calls run against the host workspace (unless the request is expected to have a sandbox, in which case the request fails closed).

Environment variables

Persistence and mount paths

Each user’s sandbox has one persistent volume that survives sandbox restarts and archival: There is no full-repo clone in the sandbox and no sandbox-side Pi session store — Pi sessions stay on the host (or Neon mirror). The sandbox mounts only agent-workspace/. On first launch (empty volume), Fleet Pi sparse-seeds the volume from FLEET_PI_REPOSITORY_URL using a non-clobber copy. Do not delete the workspace volume unless you intend to reset that user’s workspace.

Legacy sandbox migration

Sandboxes provisioned before this release mounted the workspace at /home/daytona/fleet-pi. Fleet Pi now expects /home/daytona/agent-workspace. On the next warm-up, legacy sandboxes are recreated automatically — the durable fleet-pi-ws-* volume is preserved and remounted at the new path. No action is required.

Provider credentials in the sandbox

When Daytona is active, Fleet Pi tries to sync each configured LLM provider key into the user’s Daytona organization as a Secret named fleet_pi_<providerId>. The sandbox then sees only opaque placeholders (dtn_secret_*); Daytona substitutes the real value on egress to the provider’s allowlisted HTTPS host. Providers eligible for Secrets sync (known HTTPS API hosts): The following credentials are still injected as plaintext inside the sandbox (they cannot use Secrets-based egress substitution): GitHub Copilot OAuth tokens, Google Vertex ADC (GOOGLE_APPLICATION_CREDENTIALS), Bedrock signing keys, OLLAMA_BASE_URL, and OCC base URL / model ID.
Daytona sandbox credential sync covers only the reserved default OpenAI Chat Completions slot (openai-chat-completions). Additional named OpenAI-compatible instances live in the chat runtime’s encrypted store and are never injected into the sandbox — sandbox tool calls that need one of those endpoints must go through the chat runtime, not directly from the container.
When a Secrets-backed credential changes for an active sandbox, Fleet Pi recreates the sandbox (volume preserved) so the new Secret placeholder is mounted at create time. If the Daytona Secrets API is not available for the user’s org (for example, Access denied on the Secrets endpoint), Fleet Pi falls back to plaintext injection instead of failing sandbox provisioning. See the API reference for the sandbox preview and webhook contracts.

Vercel deployment (trust zones)

Fleet Pi hardens Vercel/Neon deployments with three trust zones — local, vercel-production, and vercel-preview — enforced at boot. Vercel builds call assertDeploymentReadyOnBoot() before Better Auth mounts, so missing secrets or misconfigured Preview environments fail fast instead of accepting cross-zone traffic. Local development is unaffected and stays anonymous. Set these variables in the Vercel project (in addition to the auth and chat mirror variables above): Preview deployments must point at a Neon branch that is distinct from production. The readiness check confirms the preview marker is present in both database URLs and the production marker is absent. Verify readiness locally before promoting:
The CI vercel-release-gate job runs build:vercel plus this check against production-shaped and preview-shaped env. See runbooks for the pre-promotion checklist and break-glass procedure.

Generated configuration files

Quickstart

Apply this configuration end to end.

Runbooks

Troubleshoot provider errors, sessions, and circuit-breaker state.
Last modified on August 9, 2026