Smart Agent Teams

Configuration

Every environment variable read by the SAT API, the web build, the sat CLI and the runner, with defaults and production requirements.

SAT is configured entirely through environment variables. There is no config file for the API. This page lists every variable that the running code reads. Variables are read once at import time unless noted, so changing one requires restarting the API (on Cloud Run, gcloud run services update creates a new revision).

In production, set the API's variables on the service itself and take DATABASE_URL, JWT_SECRET_KEY and SMTP_PASSWORD from a secret store (for example Secret Manager), never as plain values. See Architecture for the deployment shape.

API

Read by the FastAPI app (apps/api, entry point main:app).

Core

VariableDefaultRequired in prodDescription
DATABASE_URLpostgresql://sat_user:sat_password@localhost:5432/sat_dbYes (secret)SQLAlchemy URL of the PostgreSQL database. Also used by Alembic. Read in database/connection.py.
JWT_SECRET_KEYdev-only-insecure-jwt-secret, only in development environmentsYes (secret)HS256 signing key for access and refresh tokens. The built-in key is used only when APP_ENV (or, if unset, ENVIRONMENT) is development, dev, local or test, ignoring case and surrounding spaces; unset counts as development. With any other value and no JWT_SECRET_KEY, the API refuses to start. Generate at least 48 random bytes, hex-encoded, for example openssl rand -hex 48.
APP_ENVunsetYesAny value other than development, dev, local or test (case-insensitive) turns on the JWT_SECRET_KEY requirement. Exactly production stops the mailer from logging email bodies when SMTP is not configured. Takes precedence over ENVIRONMENT for those two checks.
ENVIRONMENTdevelopmentYesReported by /health and /. When exactly production, unhandled-error responses omit the exception text. Used for the two checks above when APP_ENV is unset. Set to the same value as APP_ENV.
DEBUGfalseNo (keep false)true serves Swagger UI at /docs, ReDoc at /docs/redoc, the schema at /openapi.json and the route list at /api/debug/routes. start_api_local.py sets it to true.
PORT8080Set by Cloud RunPort uvicorn listens on. The Docker image sets PORT=8080 and runs uvicorn main:app --host 0.0.0.0 --port ${PORT}.
API_HOST0.0.0.0NoBind address, only when you run python main.py directly.
API_PORT8000NoPort only when you run python api/main_api.py directly. Not used by the Docker image or main.py.
DB_ECHOfalseNotrue logs every SQL statement.

Three checks, three comparisons

The JWT_SECRET_KEY check fails closed: only development, dev, local and test (any case) may use the public development key, so APP_ENV=prod or ENVIRONMENT=Production without a secret stops the API at startup with JWT_SECRET_KEY must be set. The other two checks compare literally: the mailer keeps email bodies out of the log only when APP_ENV (or ENVIRONMENT) is exactly production, and error responses drop the exception text only when ENVIRONMENT is exactly production. Use the exact value production (or staging) for both variables.

HTTP

VariableDefaultRequired in prodDescription
ALLOWED_ORIGINS*YesComma-separated CORS origins, for example https://sat.example.com,https://<site>.web.app,https://<site>.firebaseapp.com. List every origin the web app is served from. The browser calls the API's direct URL for live events, so the web origin must be listed. Also the fallback for WEB_APP_URL.
ALLOWED_HOSTSunset (no check)NoComma-separated Host header allow-list. Leave unset in Cloud Run: requests arrive as *.run.app, through Firebase Hosting, and on tagged revision URLs, and a fixed list breaks deploys.
GOOGLE_OAUTH_CLIENT_IDunsetNoThe OAuth client ID (…apps.googleusercontent.com) that turns on Sign in with Google. Unset, the feature is off: GET /api/auth/config returns google_client_id: null and the web app hides the button. It is public (an audience, not a secret); there is no client secret. Use a separate client per environment and per product. See Sign in with Google.
WEB_APP_URLfirst http(s) entry of ALLOWED_ORIGINS, else http://localhost:4200YesBase URL for links in emails: <WEB_APP_URL>/reset-password?token=..., for example https://sat.example.com.

Runs and events

VariableDefaultRequired in prodDescription
RUN_LEASE_SECONDS300NoA running run whose last heartbeat (or start) is older than this is failed with "lease expired" by the next claim, POST /runs/expire-stale or sat scheduler. Returned to runners as lease_seconds. Keep it well above the runner's 30-second heartbeat.
EVENTS_BACKENDmemoryNomemory: in-process bus, one instance only. redis: Redis pub/sub on channel sat:company:<company_id>:events, required before running more than one instance. Any other value means memory.
REDIS_URLredis://localhost:6379/0Only with EVENTS_BACKEND=redisRedis for the event bus. database/connection.py also builds a connection pool from it at import, but connects only when used, and only legacy routes use it.

Email

Used for password-reset links. When SMTP_HOST, SMTP_USER and SMTP_PASSWORD are not all set, no email is sent: outside production the message (including the link) is written to the API log; in production only a warning is logged.

VariableDefaultRequired in prodDescription
SMTP_HOSTunsetFor password resetSMTP server, for example smtp.gmail.com.
SMTP_PORT587NoSMTP port.
SMTP_SECUREfalseNotrue uses implicit TLS (port 465). Otherwise STARTTLS.
SMTP_USERunsetFor password resetSMTP login.
SMTP_PASSWORDunsetFor password resetSMTP password. Store it in your secret store, never as a plain env var.
MAIL_FROMSmart Agent Teams <SMTP_USER>NoFrom header.

To set it up on Cloud Run with Secret Manager (replace the placeholders with your project, service, region and the service's runtime service account):

printf '%s' '<app password>' | gcloud secrets create <smtp-secret> --project <project> --data-file -
gcloud secrets add-iam-policy-binding <smtp-secret> --project <project> \
  --member "serviceAccount:<runtime service account>" \
  --role roles/secretmanager.secretAccessor
gcloud run services update <service> --project <project> --region <region> \
  --update-secrets SMTP_PASSWORD=<smtp-secret>:latest \
  --update-env-vars 'SMTP_HOST=smtp.gmail.com,SMTP_PORT=587,SMTP_USER=<sending account>,MAIL_FROM=Smart Agent Teams <no-reply@example.com>'

The secret must be readable by the service's runtime service account.

Not read by the running API

apps/api/config.py defines a Settings class with variables such as SECRET_KEY, APP_NAME, LOG_LEVEL, API_BASE_URL, GCP_PROJECT_ID and MAX_CONCURRENT_AGENTS. No module that main:app loads imports it, so setting those variables has no effect. LOG_LEVEL is read only by start_api_local.py, to set uvicorn's log level locally.

Production values at a glance

VariableRecommended value
DATABASE_URLFrom your secret store
JWT_SECRET_KEYFrom your secret store
APP_ENVproduction (or staging)
ENVIRONMENTSame as APP_ENV
ALLOWED_ORIGINSEvery origin the web app is served from
WEB_APP_URLThe web app's public URL
PORT8080 (set by Cloud Run and the image)
EVENTS_BACKENDUnset (memory) with one instance; redis before you scale out
SMTPSet by hand; see Email

Web build

Vite compiles these into the browser bundle at build time. Everything here is public. Building with --mode production or --mode staging loads apps/web/.env.production or apps/web/.env.staging. Turborepo passes every VITE_* variable through to the build and includes it in the cache key.

VariableDefaultProduction valueDescription
VITE_API_URLempty (same origin)emptyBase URL for REST calls. Empty means the page's own origin, which Firebase Hosting rewrites to the API for /api/** and /health.
VITE_EVENTS_ORIGINempty (same origin)your Cloud Run service URLOrigin of the SSE stream /api/companies/<id>/events. Firebase Hosting buffers rewritten responses, so production points this at Cloud Run directly.

Local development server (bun run dev, Vite on port 4200):

VariableDefaultDescription
SAT_API_URLhttp://localhost:8080Target of the dev server's proxy for /api and /health. Read in apps/web/vite.config.ts.

sat CLI

Read by apps/cli. Flags win over environment variables, which win over the stored profile. See the CLI reference.

VariableDefaultDescription
SAT_PROFILEthe config's defaultProfile, else localProfile to use. Built in: local (http://localhost:8080), staging (https://sat-yarlis-staging.web.app), prod (https://app.yarlis.com, events at the Cloud Run URL).
SAT_API_URLthe profile's URLAPI base URL. Setting it also drops the profile's events URL.
SAT_EVENTS_URLthe profile's events URL, else the API URLOrigin for the SSE stream. Set it when the API URL is behind a proxy that buffers (Firebase Hosting).
SAT_COMPANYthe profile's stored companyCompany name, task prefix or id.
SAT_API_KEYunsetBearer API key for this process. Takes precedence over stored credentials and is never written to disk.
SAT_TOKENunsetBearer access token for this process, used when SAT_API_KEY is unset. Never stored.
SAT_CONFIG_DIR$XDG_CONFIG_HOME/sat, else ~/.config/satWhere config.json (and credentials.json for the file store) live. Files are written with mode 0600.
SAT_CREDENTIAL_STOREautoauto uses the macOS Keychain or Linux Secret Service when available, else a file. keychain behaves like auto. file forces credentials.json.
SAT_DEBUGunsetPrint stack traces on errors.
NO_COLORunsetDisable colours.
XDG_CONFIG_HOME~/.configBase for the default config directory.

Runner

Read by sat runner start and passed to agent processes.

VariableDefaultDescription
SAT_WORKDIR$XDG_DATA_HOME/sat/workspaces, else ~/.local/share/sat/workspacesRoot of run workspaces (repository clones, worktrees, agent folders). --workdir overrides it.
XDG_DATA_HOME~/.local/shareBase for the default workspace root.
SAT_CODEX_PRICE_INunsetDollars per million input tokens for Codex runs. Codex does not report cost, so unless both price variables are set its runs record cost_cents of 0 and do not count against budgets.
SAT_CODEX_PRICE_OUTunsetDollars per million output tokens for Codex runs.

The runner requires an API key (sat_live_...), from SAT_API_KEY or stored with sat apikey create <name> --store. It refuses to start on a password session.

Set by the runner for agent processes

Each agent process gets a clean sat environment so it can call the CLI as itself. These values override anything inherited.

VariableValue
PATHA sat shim directory, then the runner's PATH
SAT_API_KEYThe runner's API key
SAT_TOKEN, SAT_PROFILEEmpty
SAT_CONFIG_DIR<workdir>/.sat-agent-config (empty, mode 0700), so the agent cannot read stored credentials
SAT_CREDENTIAL_STOREfile
SAT_API_URL, SAT_EVENTS_URLThe runner's resolved URLs
SAT_RUN_IDThe run id. sat adds it to task, comment and subtask writes, which attributes them to the agent.
SAT_AGENT_ID, SAT_AGENT_NAMEThe agent
SAT_COMPANYThe company id
SAT_TASKThe task key, when the run has a task

On this page