Skip to main content

Backend configuration reference

The backend is configured with dynaconf: a stack of YAML files plus environment variables, merged in a fixed order. This page is the full field reference. The Docker Compose and Kubernetes guides only cover the handful of keys you must set to get running and link back here for everything else.

How configuration is layered​

The active environment is chosen by ENV_FOR_DYNACONF (the deployment images set it to production). For that environment, dynaconf loads and deep-merges the following, in order — later sources win:

OrderSourceCommitted?What goes here
1config.yaml (default: block)yesBase defaults for every key. Don't edit.
2config.production.yaml (production: block)yesProduction-specific defaults (e.g. cookie_secure: true). Don't edit.
3config.production.local.yamlno (gitignored)Your non-secret overrides — URLs, mode, tuning.
4config.production.secrets.yamlno (gitignored)Your secrets — passwords, API keys, JWT/CSRF/vault material.
5Environment variables (CUBEPLEX_…)—Highest precedence; override any key.

You only ever author layers 3–5. The split between local and secrets is purely organizational (safe-to-see vs sensitive) — dynaconf merges them the same way.

Env-section wrapper

The two operator files are keyed by the environment name, and set dynaconf_merge: true so their values merge onto (rather than replace) the defaults:

dynaconf_merge: true
production:
api:
public_url: "https://cubeplex.example.com"
auth:
cookie_secure: true

How each deployment mode surfaces these​

  • Docker Compose mounts config.production.local.yaml and config.production.secrets.yaml straight into the backend container. You edit the files directly (see the Compose guide).
  • Kubernetes renders them for you: backend.configOverrides in values.local.yaml becomes the local (ConfigMap) file, and backend.secrets becomes the secrets (Secret) file. You never write the YAML by hand — see the Kubernetes guide.

Environment variables​

Any key is overridable by an environment variable: prefix CUBEPLEX_, and join nesting levels with a double underscore __.

Config keyEnvironment variable
auth.jwt_secretCUBEPLEX_AUTH__JWT_SECRET
auth.csrf_secretCUBEPLEX_AUTH__CSRF_SECRET
redis.urlCUBEPLEX_REDIS__URL
sandbox.domainCUBEPLEX_SANDBOX__DOMAIN
parsers.docling_serve.base_urlCUBEPLEX_PARSERS__DOCLING_SERVE__BASE_URL
social_login.google.client_idCUBEPLEX_SOCIAL_LOGIN__GOOGLE__CLIENT_ID

Env vars win over every file, which makes them the right place for secrets you'd rather not write to disk.

Required in production​

The install fails fast if these are empty. The backend also refuses to boot when an auth signing secret is a known placeholder or has fewer than 32 characters:

KeyPurpose
auth.jwt_secretSigns session JWTs. openssl rand -hex 32; placeholders and values under 32 characters are rejected.
auth.csrf_secretCSRF double-submit cookie. openssl rand -hex 32; placeholders and values under 32 characters are rejected.
auth.vault_keyFernet key encrypting the MCP / credentials vault.
database.passwordPostgres password (match your infra).
redis.urlIncludes the Redis password.
objectstore.access_key / access_secretS3 / rustfs credentials.
llm.providers.*At least one working provider — see LLM provider configuration.

sandbox.{domain,image,api_key} are additionally required if you enable the sandbox (agent tool execution).


Deployment & API​

deployment:
mode: single_tenant # single_tenant | multi_tenant
api:
host: "0.0.0.0"
port: 8000
public_url: "https://cubeplex.example.com"
transport_shutdown_timeout_seconds: 10
public_base_url: "https://cubeplex.example.com"
frontend_base_url: "https://cubeplex.example.com"
KeyDefaultNotes
deployment.modesingle_tenantsingle_tenant auto-creates one org on first registration (OSS). multi_tenant mints a per-user org (cloud).
api.host / api.port0.0.0.0 / 8000Bind address inside the container.
api.public_url""The URL clients reach the backend at. Behind a reverse proxy, use the proxy's URL.
api.transport_shutdown_timeout_seconds10Maximum wait for HTTP requests and SSE streams to close before agent draining begins.
public_base_urlhttp://localhost:8000Used to mint absolute URLs (OAuth redirects, etc.).
frontend_base_urlhttp://localhost:3000Where the backend redirects browsers.

Auth & sessions​

auth:
jwt_secret: "…" # required
csrf_secret: "…" # required
vault_key: "…" # required (Fernet key)
cookie_secure: true # MUST be false on plain HTTP
jwt_lifetime_seconds: 2592000
cookie_samesite: "lax"
password_policy: "high" # high | low
rate_limit:
login_per_minute: 5
register_per_minute: 3
email_verification:
enabled: "auto" # auto | true | false (auto = on iff email.backend == smtp)
code_length: 6
code_ttl_seconds: 600
max_attempts: 5
KeyDefaultNotes
auth.cookie_securetrue (prod)Set false on plain HTTP, or browsers silently drop the auth cookie.
auth.jwt_lifetime_seconds2592000Session lifetime (30 days).
auth.cookie_name / csrf_cookie_namecubeplex_auth / cubeplex_csrfCookie names.
auth.password_policyhighhigh enforces stronger passwords; low relaxes it.
auth.rate_limit.*5 / 3 per minLogin / register throttles.
auth.email_verification.enabledautoOTP email verification; auto turns on only when SMTP email is configured.

LLM providers​

The full field reference — providers, presets, default_model / fallback_models — lives in LLM provider configuration. Two extras defined at the config layer:

llm:
model_presets:
tiers:
lite: { enabled: true, primary: "provider/model-id", fallbacks: [] }
pro: { enabled: true, primary: "provider/model-id", fallbacks: ["provider/backup"] }
default_preset: pro

model_presets seeds the tiered lite/flash/pro/max presets into the system org's settings (what users pick from in the model selector); each tier is a primary model ref plus ordered fallbacks. default_preset is the tier applied when none is chosen.

Database, Redis & object store​

database:
host: "postgres" # Docker/K8s service name
port: 5432
user: "cubeplex"
name: "cubeplex"
password: "…" # required
pool_size: 10
max_overflow: 20
redis:
url: "redis://:<password>@redis:6379/0" # required
key_prefix: "cubeplex"
objectstore:
provider: "s3" # s3 | oss
endpoint: "rustfs:9000"
bucket: "cubeplex"
region: "us-east-2"
access_key: "…" # required
access_secret: "…" # required

With the bundled infra, database.host, redis.url, and objectstore.endpoint point at the in-cluster service names — leave them alone unless you renamed the services or use external backends. Postgres must be the pgroonga + pgvector image (conversation search runs CREATE EXTENSION); the bundled charts already use it.

Sandbox​

Controls agent tool execution. See the sandbox guide for the user-facing behaviour and each deployment guide for wiring it up.

sandbox:
enabled: true
domain: "…" # OpenSandbox API host:port (no scheme)
image: "ghcr.io/cubeplexai/cubeplex-sandbox:v0.9.3"
api_key: "…"
command_default_timeout_seconds: 3600 # default for the new managed-task reservation layer
use_server_proxy: false # true when the backend can't reach sandbox pods/ports directly
secure_access: false # required for docker-runtime OpenSandbox
ttl: 1800 # idle seconds before cleanup
ready_timeout: 300 # wait for a sandbox to become ready (covers a cold pull)
run_user: "cubeplex" # agent shell identity inside the container
run_uid: 1000 # OpenSandbox RunCommandOpts.uid (null = root)
run_gid: 1000
resource:
cpu: "2"
memory: "4Gi"
KeyDefaultNotes
sandbox.enabledtrueRequired. The backend refuses to start if it is not true.
sandbox.use_server_proxytrueSet false for direct pod access; true for Docker-bridge / isolated networks.
sandbox.secure_accessfalseEnables Kubernetes ingress-gateway signed URLs when true. Must be false on docker-runtime OpenSandbox.
sandbox.ttl1800Idle sandbox is reaped after 30 min.
sandbox.command_default_timeout_seconds3600Positive integer seconds, at most 2147483647 (the technical 32-bit seconds limit). The backend rejects out-of-range values, non-integers, booleans, and null at startup. Environment override: CUBEPLEX_SANDBOX__COMMAND_DEFAULT_TIMEOUT_SECONDS.
sandbox.run_user / run_uid / run_gidcubeplex / 1000 / 1000Agent commands and uploaded files run as this user. Match the sandbox image. Set run_uid to null to keep the previous root default. Browser stack still starts as root.
sandbox.resource.cpu / memory2 / 4GiPer-sandbox limits.

The background-task reservation layer uses command_default_timeout_seconds when an execute task omits timeout_seconds. An explicit positive timeout takes precedence and may exceed one hour, up to the same technical limit. Oversized values are rejected, not truncated or treated as unlimited. The absolute deadline is stored once; configuration changes do not extend existing reservations. Disabling completion notifications does not remove the deadline. Monitor deadlines, persistent monitors, HTTP timeouts, sandbox TTL, and the foreground waiting budget are separate settings.

This is the storage/reservation foundation of the lifecycle rollout. The existing agent execute path and coordinator have not switched to it yet; adding this setting does not by itself change their behavior or enable conversation-owned background execution. Existing command records are not assigned a guessed deadline.

Streaming​

streaming:
run_event_ttl_seconds: 43200 # 12h — how long a run's events are replayable
run_stream_block_ms: 5000 # SSE heartbeat cadence; must be < redis socket timeout
run_stream_max_events: 1000000 # DoS safety cap (trimming = silent replay loss)

run_event_ttl_seconds doubles as the upper bound on how long an in-flight run can stay active — raise it for very long agent runs.

Conversation context compaction​

compaction:
enabled: true
threshold_ratio: 0.7 # compact at context_window * ratio
keep_tail_tokens: 8000 # recent tokens kept verbatim
max_summary_tokens: null # null = cubeloop dynamic budget
fallback_context_window: 128000

Hybrid lexical + vector search over past conversations.

search:
enabled: true
lexical:
backend: "pgroonga" # pgroonga | pg_bigm
embedding:
enabled: false # lexical-only until you turn this on
base_url: "https://api.openai.com/v1"
api_key: "" # via CUBEPLEX_SEARCH__EMBEDDING__API_KEY
model: "text-embedding-3-small"
vector_dim: 1024

Lexical search works out of the box. Vector search stays off until you set embedding.enabled: true and supply an OpenAI-compatible /v1/embeddings endpoint. vector_dim is frozen at migration time — changing it later needs a table rebuild.

File parsing (docling)​

parsers:
docling_serve:
base_url: "http://docling-serve-cpu:5001"
api_key: ""
timeout_sync_seconds: 30
async_threshold_mb: 3

The read tool converts PDF / office documents to markdown via a docling-serve instance. Optional — see each guide's docling section for deploying one.

Attachments​

attachments:
max_file_bytes: 52428800 # 50 MiB per file
max_per_message: 10
max_per_conversation_bytes: 524288000 # 500 MiB
allowed_mime_types: [ image/png, application/pdf, … ]

Governs uploads. allowed_mime_types is an allow-list (images, PDF, office docs, text, archives by default); thumbnail / view_images control how images are down-scaled for the model.

Email & social login​

email:
backend: "log" # log | smtp
from_address: "noreply@cubeplex.local"
smtp_host: "…"
smtp_port: 587
smtp_user: "…" # via env / secrets
smtp_password: "…"
social_login:
google:
enabled: false
client_id: "…" # via env / secrets
client_secret: "…"

email.backend: log just prints emails to stdout (dev). Set smtp and fill credentials (via env or the secrets file) to send verification / password-reset mail for real. Google login is off until you enable it and supply OAuth credentials.

Memory​

memory:
short_term_enabled: true
long_term_enabled: false

Conversation memory. Short-term (in-conversation working memory) is on by default; long-term (cross-conversation recall) is off until you turn it on.

MCP tools​

mcp:
outbound_policy: "public_and_allowlist"
allowed_schemes: ["https"]
allowed_hosts: []
allowed_cidrs: []
progressive_disclosure:
enabled: "auto" # auto | on | off
threshold_pct: 10.0 # collapse when deferrable schemas ≥ this % of context
min_servers: 2
icons:
allow_remote: true # UI may render remote https icons
fetch_remote: true # discovery may outbound-fetch icons → data: cache
fetch_timeout_ms: 2500
max_bytes: 262144 # 256 KiB per icon
KeyDefaultNotes
mcp.progressive_disclosure.enabledautoCollapses deferrable tool schemas when they crowd the context; auto decides per the threshold below.
mcp.progressive_disclosure.threshold_pct10.0Collapse once deferrable schemas exceed this share of the context window.
mcp.icons.fetch_remotetrueSet both icon flags false on air-gapped deploys; catalog brand icons still render from bundled assets.
mcp.outbound_policypublic_and_allowlistControls backend requests made for MCP connectors. public_and_allowlist permits public targets and targets in the explicit allowlists; public_only, allowlist_only, disabled, and unrestricted are also available.
mcp.allowed_schemes[https]Allowed URL schemes for MCP connector requests. Keep the default unless a trusted deployment needs plain HTTP.
mcp.allowed_hosts / mcp.allowed_cidrs[]Explicit internal MCP destinations permitted by public_and_allowlist or allowlist_only. allowed_cidrs accepts IPv4 and IPv6 CIDR notation, such as 10.20.0.0/16, 10.20.1.4/32, or fd00:1234::/64; use allowed_hosts for DNS names.

Connectors themselves are managed in the DB-backed catalog, not here.

Skills​

skills:
cache_root: "skills_cache" # local extraction cache
preinstalled_dir: "skills/preinstalled"
registry:
skills_sh:
github_token: "" # optional — raises the GitHub API rate limit 60 → 5000/h

preinstalled_dir is seeded into the global skills catalog on boot. Set registry.skills_sh.github_token (via env / secrets) if skill discovery hits GitHub rate limits.

Image generation​

image_generation:
enabled: false
api: "openai-images"
model: "gpt-image-2"
api_key: null # via CUBEPLEX_IMAGE_GENERATION__API_KEY
base_url: null

Powers the generate_image tool (sandbox-gated). Off until you enable it and supply an api_key.

Tracing​

tracing:
enabled: false
directory: "./cubeloop-traces"
record_content: false # true captures full prompts/responses/tool I/O (larger, sensitive)
jsonl:
enabled: true # local-dev JSONL; Helm/Compose set false
otlp:
endpoint: null # e.g. http://localhost:4318/v1/traces to ship spans
headers: null
tempo:
query_endpoint: null # enables the admin trace viewer when set

Writes per-run cubeloop spans to disk when JSONL is on, and optionally ships them to an OTLP collector (Grafana Tempo, etc.). Helm and Compose fill otlp.endpoint and tempo.query_endpoint when bundled Tempo is on, and set jsonl.enabled: false so the backend writable layer is not filled. query_endpoint null still means admin traces 503. record_content: true is powerful for debugging but captures potentially sensitive prompt/tool data.

Logging​

logging:
third_party_level: "WARNING" # caps noisy botocore/httpcore/… loggers
verbose_modules: [] # re-enable DEBUG for specific logger names
access_log: true # one line per HTTP request

Set access_log: false behind a proxy that already logs requests. Add partial logger names to verbose_modules to selectively re-enable DEBUG.

During shutdown, HTTP requests and SSE streams that exceed api.transport_shutdown_timeout_seconds are cancelled by Uvicorn. Its ASGI exception report is logged at INFO without a traceback only when the exception is CancelledError with the exact reason Task cancelled, timeout graceful shutdown exceeded. Other cancellations and application exceptions keep their original level and traceback. Uvicorn's timeout summary (Cancel N running task(s), timeout graceful shutdown exceeded) remains ERROR so forced transport shutdown stays visible. This changes logging only; cancellation still propagates and agent run drain is unchanged.

Lifecycle​

lifecycle:
graceful_drain_timeout_seconds: 3600 # max wait for in-flight runs on shutdown
stale_run_threshold_seconds: 180

graceful_drain_timeout_seconds bounds how long the backend waits for active agent runs to finish before shutting down — align it with your longest expected run. The orchestrator's termination grace period must also include the shorter HTTP transport timeout and final resource cleanup.

Egress secret-injection listener​

egress_exchange:
auth:
mode: mtls # mtls (production) | dev (shared secret, dev/test only)
listener:
enabled: false # the egress bundle turns this on
port: 8443
certfile: ""
keyfile: ""
ca_certs: ""

The backend side of the egress secret-injection feature. Left off unless you deploy the egress bundle, which sets the listener and its mTLS material for you.

Next steps​