Skip to content
AI Workforce OS
ES EN
Create account

Configuration reference

All AI Workforce OS configuration is read from environment variables. This page lists the variables of the backend (Go), the agent runtime (Python) and the frontend (Next.js), with the default value set in code. It is for anyone installing or operating their own instance.

11 min read

On this page
  1. How configuration is loaded
  2. Server and database
  3. Authentication, CORS and proxy
  4. Budget, cost and execution limits
  5. Rewards and coins
  6. Runtime: model providers and keys
  7. Connections, sandbox, storage and push
  8. Rate limits
  9. Frontend: build arguments
  10. Email verification and trial accounts
Back to top

How configuration is loaded#

The backend reads the environment at startup and validates the combination before it opens its port: if something is inconsistent, the process exits with an error that names the variable. The runtime reads its variables when it uses them. The frontend reads nothing at runtime: its values are fixed at build time.

  • Booleans: true, false, 1, 0 and the other values accepted by strconv.ParseBool. An invalid value aborts startup.
  • Integers and durations: a missing, non-numeric or non-positive value is replaced by the default. Durations use Go syntax (30s, 5m, 1h).
  • USD amounts: 0 is valid and turns the corresponding cap off; a negative or non-numeric value keeps the default. The exception is BUDGET_USD, which needs a value above zero.
  • Lists: comma-separated values; spaces are ignored.

Note

In the tables, Conditional means the variable is only required in combination with another one (for example JWT_SECRET with AUTH_ENABLED=true). No means there is a default or the feature stays off.

Server and database#

VariableDefaultRequiredMeaning
PORT8080NoBackend HTTP port.
DATABASE_URLemptyConditionalPostgres URL. Without it the backend keeps everything in memory (demos only). Required with AUTH_ENABLED=true.
MIGRATE_DATABASE_URLemptyNoOwner (DDL) connection used only to apply migrations. Empty: DATABASE_URL is used.
DB_APP_ROLEapp_userNoNon-owner role without BYPASSRLS that every connection switches to (SET ROLE); the basis of RLS isolation. Empty disables the switch.
REDIS_URLemptyNoRedis URL, with the password inside the URL. Empty: events, locks and rate limits live in process memory.
RUNTIME_URLhttp://localhost:8000NoInternal URL of the agent runtime.
RUNTIME_TOKENemptyNoShared secret between backend and runtime. Recommended in production: the backend logs a warning when it is empty.
READ_HEADER_TIMEOUT10sNoMaximum time to read the headers of a request.
READ_TIMEOUT30sNoMaximum time to read a whole request.
WRITE_TIMEOUT60sNoMaximum time per REST request. Not applied to the WebSocket.
IDLE_TIMEOUT120sNoIdle time of keep-alive connections.
MAX_BODY_BYTES1048576NoMaximum size of a request body (1 MiB).
APP_ENVemptyNoWith the value production, the backend warns when the vault KEK comes from the environment.
TRACE_RETENTION_DAYS30NoDays the agents' reasoning traces are kept; they are purged hourly.

Tip

Set RUNTIME_TOKEN to the same value in the backend and the runtime. Once it is set, the runtime requires Authorization: Bearer <token> on every route except /healthz.

Authentication, CORS and proxy#

With AUTH_ENABLED=false the backend runs in demo mode: one fixed organization and no sign-in. With AUTH_ENABLED=true it enables accounts, JWT, RBAC and per-organization isolation, and the org_id comes from the token.

With authentication on, startup fails if JWT_SECRET is shorter than 32 bytes, if DATABASE_URL is missing or if ALLOWED_ORIGINS contains *.

VariableDefaultRequiredMeaning
AUTH_ENABLEDfalseNoEnables accounts, JWT, RBAC and multi-tenancy. The production compose file sets it to true.
JWT_SECRETemptyConditionalAccess token signing key; at least 32 bytes; required with AUTH_ENABLED=true. Rotating it invalidates every session.
ALLOWED_ORIGINSemptyConditionalOrigins allowed for CORS and WebSocket, comma-separated and without a trailing slash. Empty without authentication means *; empty with authentication, same origin only.
AUTH_COOKIE_SECUREtrueNoSecure attribute of the refresh token cookie. Only turned off for local plain-HTTP development.
AUTH_COOKIE_NAMEwfai_refreshNoName of the refresh token cookie. Change it when several installations share a host.
TRUSTED_PROXIESemptyNoCIDRs or IPs of the proxies whose X-Forwarded-For is honored (used by rate limits). An invalid value aborts startup.
ENABLE_DEMO_RESETfalseNoRegisters POST /api/v1/demo/reset, which wipes execution data. Keep it false in production.
KILL_SWITCHemptyNofreeze or lockdown: a process-wide override no API call can lift. Re-read on SIGHUP.
UI_BASE_URLemptyNoPublic frontend URL: where the browser lands after the OAuth callback, and the base of email verification links.

Warning

With AUTH_ENABLED=false there is no sign-in: anyone who can reach the URL controls the demo organization. Do not expose that mode to the internet with real data or keys.

Budget, cost and execution limits#

These variables control spending and orchestrator concurrency. Context windows and quality checks have more internal settings with sensible defaults; this table covers the ones worth knowing.

VariableDefaultRequiredMeaning
BUDGET_USD50NoSpending cap in USD. The production compose file uses 25 when you do not set it.
REQUEST_BUDGET_CAP_USD0NoHard spending cap per request. 0 turns it off.
AGENT_BUDGET_USD0NoMonthly spending cap per agent. 0 turns it off.
COST_CONFIRM_THRESHOLD_USD1.0NoEstimated cost above which confirmation is requested before running.
BUDGET_PAUSE_TIMEOUT30mNoHow long a request paused by a cap waits for someone to decide.
PRICE_IN_PER_M / PRICE_OUT_PER_Minternal price tableNoForce the USD price per million input and output tokens. Set both; the backend and the runtime read them.
APPROVAL_ACTIONSsend_proposal,send_contractNoActions that always require human approval.
APPROVAL_TIMEOUT30mNoHow long an approval inside a request waits.
STANDING_APPROVAL_DAYS30NoLifetime of a standing per-project permission (at most 90). It also ends with the project.
MAX_PARALLEL / MAX_PARALLEL_PER_ORG4 / 8NoConcurrent tasks overall and runtime calls in flight per organization; the rest wait in a queue.
MAX_PLAN_DEPTH30NoMaximum depth of a plan.
TASK_TIMEOUT120sNoMaximum time of a task.
TASK_RETRIES / TASK_MAX_ATTEMPTS3 / 2NoRetries and maximum attempts per task.
TASK_RETRY_BACKOFF2sNoWait between task retries.
PROJECT_MAX_NODES / PROJECT_MAX_CHILDREN_PER_GROUP / PROJECT_MAX_ACTIVE20000 / 200 / 20NoMaximum project size and number of active projects per organization.
PLANNER_MAX_PHASES / PLANNER_MAX_TASKS_PER_PHASE / PLANNER_CONCURRENCY12 / 40 / 3NoBounds of the project planner.
DEP_CONTEXT_TOKEN_BUDGET8000NoContext tokens a task receives from the tasks it depends on.
EMAIL_HOLD_SECONDS60NoWindow to cancel an email before it leaves. Minimum 60 and maximum 300: out-of-range values are clamped.

Rewards and coins#

The coin system rewards completed work. REWARDS_RULES takes a JSON object that is applied on top of the default rules, so you only include the fields you want to change.

If the JSON has an invalid value, the backend logs it and uses the full default rules: it never applies a half-changed economy.

Default REWARDS_RULESJSON
{
  "enabled": true,
  "base": { "S": 5, "M": 10, "L": 20, "XL": 40 },
  "default_complexity": "M",
  "first_try_bonus_pct": 50,
  "rework_pct": 50,
  "project_base": 20,
  "project_per_task": 2,
  "project_max": 100,
  "min_project_tasks": 2
}
VariableDefaultRequiredMeaning
REWARDS_ENABLEDtrueNofalse, 0 or off turn coin earning off entirely.
REWARDS_RULESemptyNoJSON with rules: enabled, base (S, M, L, XL), default_complexity, first_try_bonus_pct, rework_pct, project_base, project_per_task, project_max, min_project_tasks.
REWARDS_WELCOME_COINS1000NoWelcome coins each organization receives once, the first time it opens the shop or the office. 0 turns it off.

Runtime: model providers and keys#

The runtime supports five providers: deepseek, anthropic, openai, kimi and custom (an OpenAI-compatible endpoint such as vLLM or Ollama). A provider counts as configured when its key (and, for custom, its base URL) is in the environment. With no provider configured, or with SIMULATION=true, the runtime uses the simulated engine: scripted answers, no keys and no cost.

On a transient error (timeout, rate limit, 5xx) it retries on the same provider and then moves to the next allowed one. ALLOWED_PROVIDERS is the operator ceiling: no data reaches an excluded provider, not even as a fallback. Each organization can narrow it further in its settings, never widen it, and can store its own provider key, which wins over the environment's.

VariableDefaultRequiredMeaning
SIMULATIONfalseNotrue forces the simulated engine even when keys are present.
DEEPSEEK_API_KEY / DEEPSEEK_MODELempty / deepseek/deepseek-chatConditionalDeepSeek key and model; DeepSeek is the platform default provider.
ANTHROPIC_API_KEY / ANTHROPIC_MODELempty / anthropic/claude-sonnet-5-5ConditionalAnthropic key and model.
OPENAI_API_KEY / OPENAI_MODEL / OPENAI_BASE_URLempty / openai/gpt-4.1 / emptyConditionalOpenAI key, model and optional base URL.
KIMI_API_KEY / KIMI_MODEL / KIMI_BASE_URLempty / openai/kimi-k2-0905-preview / https://api.moonshot.ai/v1ConditionalKimi (Moonshot) key, model and base URL.
CUSTOM_LLM_BASE_URLemptyConditionalBase URL of an OpenAI-compatible endpoint. Enables the custom provider.
CUSTOM_LLM_API_KEY / CUSTOM_LLM_MODELemptyNoOptional key (many local servers need none) and model name of the custom endpoint.
CUSTOM_PRICE_IN_PER_M / CUSTOM_PRICE_OUT_PER_M3 / 15NoUSD per million tokens of the custom endpoint. May be 0 for a local model.
MODELemptyNoLegacy variable: provider/model applies to that provider; an unprefixed name applies to the primary.
MODEL_PROVIDER_ORDERdeepseek,anthropic,openai,kimi,customNoGlobal fallback order. MODEL_PROVIDER_ORDER_<ROLE> changes it for an agent role or a call type (for example MODEL_PROVIDER_ORDER_PLAN).
ALLOWED_PROVIDERSemptyNoOperator ceiling: only these providers may receive data. Empty means all configured. Set it the same in the backend and the runtime.
PROVIDER_RETRIES1NoRetries on the same provider before moving to the next.
PROVIDER_RETRY_BACKOFF_S1.0NoWait between retries, in seconds.
PROVIDER_TIMEOUT_S120NoTimeout per provider call, in seconds.
SYNTH_PROMPT_TOKEN_BUDGET24000NoToken budget of the synthesis prompt in the runtime.

Warning

Provider keys are secrets that cost money when they leak. Pass them through the service environment, never in an image or the repository.

Connections, sandbox, storage and push#

Without CONNECTIONS_KEK no live credential can be stored: the vault fails closed and only simulated connections work. The KEK is base64 of 32 random bytes; keep it apart from the database, because without it the encrypted secrets cannot be recovered.

The code sandbox is optional and needs the sandbox-runner service, which is given access to the Docker socket. Read the security model page first.

Generate a KEKBash
openssl rand -base64 32
VariableDefaultRequiredMeaning
CONNECTIONS_KEKemptyNoKey-encryption key of the vault (base64 of 32 bytes). Needed by live connections and by each organization's own model keys.
CONNECTIONS_KEK_PREVIOUSemptyNoPrevious KEK, only while a rotation is in progress.
CUSTOM_API_ALLOW_PRIVATEfalseNoWhen true, generic API sources (custom_api) may reach private addresses, such as an ERP on your LAN. Only for self-hosted installs on a trusted network; it never changes the GET-only rule.
GOOGLE_OAUTH_CLIENT_ID / GOOGLE_OAUTH_CLIENT_SECRETemptyNoYour own Google OAuth app for connections.
LINKEDIN_OAUTH_CLIENT_ID / LINKEDIN_OAUTH_CLIENT_SECRETemptyNoYour own LinkedIn OAuth app.
OAUTH_REDIRECT_URLhttp://localhost:8080/api/v1/connections/oauth/callbackNoPublic URL of the OAuth callback; must match the one registered with the provider.
GITHUB_API_URLempty (api.github.com)NoGitHub API base, for example for GitHub Enterprise.
SANDBOX_URL / SANDBOX_TOKENemptyNoAddress and token of the sandbox-runner. Without both, agents cannot run code.
SANDBOX_IMAGE / SANDBOX_RUNTIME / SANDBOX_SECCOMP_PROFILEemptyNoRead by the sandbox-runner: execution image, container runtime (for example gVisor's runsc) and seccomp profile.
S3_ENDPOINT / S3_BUCKET / S3_REGION / S3_PREFIX / S3_USE_PATH_STYLEempty (S3_USE_PATH_STYLE: true)NoS3-compatible storage for Drive files. Without endpoint and bucket, Postgres is used, or memory when there is no database.
S3_ACCESS_KEY / S3_SECRET_KEYemptyNoS3 credentials.
DRIVE_MAX_FILE_MB / DRIVE_QUOTA_MB25 / 5120NoMaximum size per file and quota per organization, in MB.
VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECTemptyNoAll three together enable Web Push notifications; without them they stay off.
PUSH_ALLOWED_HOSTSemptyNoPush service hosts added to the default allow-list.

Rate limits#

Rate limits are built into the backend and have no tuning variables: for example, 10 sign-in attempts per 15 minutes and 120 commands per minute. The authentication limits are stored in Redis when REDIS_URL is set, and in process memory otherwise.

What you do configure is TRUSTED_PROXIES. Only the X-Forwarded-For header from those proxies is used to identify the client. Behind Caddy, list the proxy network: without it, every client shares the proxy's IP and the same counter.

Frontend: build arguments#

The frontend is compiled with standalone output. NEXT_PUBLIC_* variables are baked in at build time as Dockerfile arguments (ARG); changing them means rebuilding the image.

Leaving them empty gives the recommended single-domain behavior: the API at the relative path /api/v1 and a WebSocket URL derived from window.location.

Build for separate domainsBash
docker build \
  --build-arg NEXT_PUBLIC_API_URL=https://app.example.com/api/v1 \
  --build-arg NEXT_PUBLIC_WS_URL=wss://app.example.com/ws \
  -t aiworkforce-frontend ./frontend
Build argumentDefaultRequiredMeaning
NEXT_PUBLIC_API_URLemptyNoAbsolute API base URL, for example https://app.example.com/api/v1. Empty: relative path.
NEXT_PUBLIC_WS_URLemptyNoAbsolute WebSocket URL, for example wss://app.example.com/ws. Empty: derived from window.location.
NEXT_PUBLIC_MOCKemptyNoWith the value true, the frontend runs on mock data, without a backend.

Email verification and trial accounts#

Two optional sign-in features, both requiring AUTH_ENABLED=true. Email verification needs a working SMTP server: with EMAIL_VERIFICATION=true and no valid SMTP the backend refuses to start, so a half-configured server cannot lock anyone out.

Trial accounts let people use the app without signing up; each one gets its own organization and stops working on its own.

VariableDefaultMeaning
EMAIL_VERIFICATIONfalseNew accounts must open a one-time link sent to their address before they can sign in. Needs AUTH_ENABLED, UI_BASE_URL and SMTP_* (or EMAIL_DEV_LOG).
EMAIL_DEV_LOGfalseDevelopment only: verification links are written to the log instead of being sent.
SMTP_HOSTemptySMTP server. Required with EMAIL_VERIFICATION unless EMAIL_DEV_LOG is on.
SMTP_PORT465465 uses implicit TLS; 587 uses STARTTLS.
SMTP_TLStls on port 465; starttls otherwisetls, starttls or none (none only for a local relay).
SMTP_USER / SMTP_PASSemptySMTP server credentials; set both or neither.
SMTP_FROMemptySender, for example AI Workforce OS <no-reply@example.com>. Required for SMTP verification.
TRIAL_ENABLEDfalseTurns on POST /api/v1/auth/trial: throwaway accounts with their own organization.
TRIAL_TTL_HOURS24Hours a trial account works.
TRIAL_PER_IP_PER_DAY3Trial accounts one IP address may create per day.
TRIAL_GLOBAL_PER_DAY300Trial accounts created per day across all addresses.
TRIAL_EMAIL_DOMAINprueba.invalidDomain of the generated trial addresses (no mailbox behind it).