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 has no hard-coded default model. When a new chat session arrives without a model, the server picks one:
  1. The defaultProvider / defaultModel from Pi settings, when that provider has credentials.
  2. Otherwise, the first model with credentials that Fleet Pi registered itself (the OpenAI-Chat-Completions slot or a custom provider) and that you enabled.
  3. Otherwise, the first enabled model with credentials, then the first model with credentials.
If no provider has credentials, the turn fails with No chat model is available: add an LLM provider in Settings > Providers (or set its env vars) and try again. Fleet Pi never falls back to Pi’s built-in per-provider defaults. The composer also holds your first message until the model picker has loaded, so a turn is never sent without a model. On deployed authenticated chat, the Neon AI Gateway backs the OpenAI-Chat-Completions (OCC) slot, with qwen35-122b-a10b as the primary model and gpt-oss-120b also enabled. A user’s own 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. Locally, the same panel manages provider API keys and writes them to .env.local. Models you explicitly enable for a built-in provider appear in the model picker, and their saved BYOK credentials are used on hosted turns. OPENAI_API_KEY is always sent to https://api.openai.com/v1. Fleet Pi does not read OPENAI_BASE_URL or OPENAI_MODEL. For a gateway or any other OpenAI-compatible endpoint, use the openai-chat-completions provider instead: Set all three together. These variables apply to local runs only. Deployed chat never uses the env API key: each signed-in user adds the provider in Settings > Providers, and without it the Neon AI Gateway backs this slot. Locally, signed-in accounts backed by the chat database (FLEET_PI_CHAT_DATABASE_URL) prefer values saved in Settings and fall back to these variables.
Fleet Pi checks the credential before each turn. When the key is a Neon AI Gateway token (nt_live_…) and the model’s endpoint is not a Neon AI Gateway host, the turn fails with a message naming the provider. Fleet Pi does not send the request, so the platform token never reaches a third-party endpoint. Use the Gateway through the openai-chat-completions provider, or save a key for the other provider in Settings > Providers.

Amazon Bedrock

When using Bedrock, Fleet Pi uses the standard AWS credential chain — environment variables, profile, or IAM role. To pin a default provider and model, set defaultProvider and defaultModel in .pi/settings.json or from the in-app config panel. The committed .pi/settings.json sets neither, so Fleet Pi resolves the model as described above:
Set the matching API key in .env (or via the in-app config panel). Every provider supported by Pi is available, so 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

Agent loop settings

Change these from Settings > Agent loop. They are saved as Pi settings through GET / PATCH /api/chat/settings. On hosted chat, saved values apply on the next Durable Object turn. Hosted chat registers extensions, code mode, and MCP servers only when you turn them on.

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 (Fleet Pi also accepts NEON_AUTH_URL as a fallback) 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.
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.

Require chat authentication (optional)

Fleet Pi serves the chat streaming API from the same Cloudflare Worker that hosts settings, providers, and the workspace. On Cloudflare, new hosted Agent sessions stream from the signed-in user’s Pi Durable Object. Chat routes require authentication on Cloudflare and whenever Neon Managed Auth is configured.

Sessions and workspace paths

Locally, Pi session files are persisted under .fleet/sessions/ inside the repo. On Cloudflare, hosted Agent transcripts live in the per-user Pi Durable Object instead (see chat session mirror). 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)

Locally, Pi session JSONL files under .fleet/sessions/ are the source of truth. On Cloudflare, each signed-in user’s hosted Agent transcript lives in that user’s Pi Durable Object (SQLite storage, bound as USER_PI_DURABLE). Neon keeps auth, settings, and an optional projection of the conversation. 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. The Cloudflare readiness check requires FLEET_PI_CHAT_DATABASE_URL. In local development, mirror failures are caught and logged and never break chat streaming. On Cloudflare, mirror errors fail closed. Use two separate roles in Neon: The runtime role name defaults to fleet_pi_app. Override it with FLEET_PI_APP_ROLE. 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 Cloudflare (FLEET_PI_DEPLOYMENT=cloudflare), 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, the per-user Durable Object, or the Neon mirror. The sandbox mounts only agent-workspace/. Fleet Pi seeds each volume from a neutral starter template (templates/agent-workspace/ in FLEET_PI_REPOSITORY_URL): system policy files, empty memory stubs and three starter Pi skills. It does not copy Fleet Pi’s own agent workspace.
  • The volume records the applied template version in .fleet/template-version. Prepare skips the clone when the volume is at that version or newer, and re-seeds when a newer build ships a newer template version.
  • Fleet Pi copies missing files and never overwrites a file. When a template file changed and your copy differs, Fleet Pi writes the new copy to .fleet/template-updates/<version>/<path> for you to review.
  • On volumes seeded before template versions existed, files byte-identical to something the old seed copied move once to .fleet-pi-quarantine/legacy-seed/. Files you edited stay in place.
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.

Cloudflare Workers deployment (trust zones)

Fleet Pi deploys the web app to Cloudflare Workers. Cloudflare is the only supported deploy host. The Worker config lives in apps/web/wrangler.jsonc:
  • Worker name fleet-pi-web with the nodejs_compat compatibility flag and observability enabled.
  • A SQLite-backed Durable Object class UserPiDurable, bound as USER_PI_DURABLE. Each signed-in user gets one object that stores their hosted Agent transcript.
  • Rate limit bindings FLEET_PI_SIGN_IN (10 requests per 60 seconds) and FLEET_PI_CHAT_START (30 requests per 60 seconds).
  • The non-secret var FLEET_PI_DEPLOYMENT=cloudflare, which switches Fleet Pi to its hosted, fail-closed, Postgres-backed code paths. Worker Previews also set FLEET_PI_DEPLOYMENT_TRUST_ZONE=preview.
Set secrets such as database URLs, auth secrets, and provider keys with wrangler secret, not in wrangler.jsonc. Build and deploy from apps/web:
Fleet Pi enforces three trust zones at boot: local, cloudflare-production, and cloudflare-preview. On the Worker, assertDeploymentReadyOnBoot() runs the readiness check before Better Auth mounts, so missing secrets or misconfigured previews fail fast instead of accepting cross-zone traffic. Local development is unaffected and stays anonymous. Set these variables on the Worker (in addition to the auth and chat mirror variables above): Preview Workers must point at a Neon preview branch that is distinct from production. Previews do not inherit production secrets. The readiness check confirms the preview marker is present in both database URLs and the production marker is absent. Verify readiness locally before deploying:
See runbooks for the pre-deploy 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 November 10, 2026