Configuration reference
Every setting in hubzoid.yaml and every environment variable Hubzoid reads, with its type, default and effect, and how they load.
Every setting Hubzoid reads: the deployment file hubzoid.yaml, then environment variables grouped by area. Most Hubs set only the model in <hub>/.env. Everything else has a working default.
Where each setting lives
| File | What goes in it | Section |
|---|---|---|
hubzoid.yaml | The deployment: its Hubs, public address, sign-in, owner, brand, chat defaults, embedded apps and browser editing. Versioned with the Hub folders. | hubzoid.yaml |
<hub>/.env | One Hub's model, keys and process settings. Readable only by you, never committed. | Environment variables |
.env next to hubzoid.yaml | Deployment secrets: the deployment key, DATABASE_URL, sign-in providers, mail. | How settings load |
<hub>/restricted/.env | Secrets only restricted tools read. | How settings load |
| AWS Secrets Manager | Optional. Any layer above, from a secret. | Configuration secrets |
A Hub's own declared settings (settings.yaml, hub.settings.yaml) and files such as hitl.yaml and budgets.yaml are described in Hub files.
hubzoid.yaml
The deployment file. It sits in the deployment folder: the folder that holds the Hub folders, or the Hub folder for a deployment of one. hubzoid run with no arguments runs it.
version: 1
public_url: https://hub.example.com
sign_in: true
owner: sam@example.com
hubs:
- my-hub
- finance/finance-hub| Key | Type | Default | Effect |
|---|---|---|---|
version | 1 | required | File format version. |
hubs | list of folders | required | Hub folders, relative to this file. Each must contain AGENTS.md. The first also answers at /. |
public_url | URL | none | The address people open, starting with https:// (or http:// on this machine). Fills HUBZOID_PUBLIC_URL. |
sign_in | boolean | false | Require sign-in. Fills HUBZOID_AUTH. |
owner | none | The first owner's email. Fills HUBZOID_ADMIN_EMAIL. | |
in_app_editing | boolean | false | Beta. Let people change agent files from the browser. See browser editing. Fills HUBZOID_IN_APP_EDITING. |
brand | mapping | none | The deployment's brand for sign-in, chat, shared pages and Settings. brand.identity.name names the deployment. Check it with hubzoid branding validate. |
chat | mapping | product defaults | Chat defaults: appearance, locale, time zone and the model picker. See chat defaults. |
diagnostics | mapping | product defaults | Diagnostic limits: check_timeout_seconds, max_parallel_checks, bundle_ttl_minutes, allow_network_probes. |
apps | mapping | none | Apps that embed the chat. See apps: chat inside your app. |
An unknown key, a wrong type or a Hub folder without AGENTS.md stops the start, naming the file, key and line. The file holds policy that must never fall back to an open default. A key set both here and in its environment variable, with different values, also stops the start and names both.
Browser editing (Beta)
An agent's instructions, skills, connectors, channels, approval rules, usage limits and settings are files. By default people read them in Settings and change them in the files, or ask Claude Code or Codex. With browser editing off:
- Proposing and approving Hub changes in Settings or chat answers that it is off.
- The
rememberandpropose_hub_changetools answer that it is off and say where to change the file. - Saving connectors, channels, settings, approval rules and budgets in Settings is read-only, with a note on where to change them.
Turn it on with in_app_editing: true in hubzoid.yaml, or HUBZOID_IN_APP_EDITING=true in one Hub's .env. Files that come from an image (HUBZOID_DATA_DIR set) are never written from the browser. The CLI's apply commands and hubzoid hub-changes keep working either way. Workflow permissions and stored secrets live in the database and stay editable.
| Variable | Type | Default | Effect |
|---|---|---|---|
HUBZOID_IN_APP_EDITING | boolean | false | The same switch for one Hub. |
HUBZOID_HUB_CHANGES_PUSH | boolean | false | Push the Git commit an approved Hub change makes, as it is: never rebased or forced. Without it the commit stays on the server for someone to push. |
HUBZOID_GIT_COMMITTER | Name <email> | the checkout's Git identity, else Hubzoid | Committer of those commits. The approving person is the author. |
Chat defaults
The chat: section holds the keys of the chat configuration without version:. Settings → Organization shows them read-only. Every agent reads a change within a few seconds. hubzoid chat config schema prints every key with its default.
chat:
presentation:
appearance:
default_mode: dark
models:
allow_choice: true
default_ref: sonnet
catalog:
sonnet: {label: Claude Sonnet, model: sonnet}
haiku: {label: Claude Haiku, model: haiku}| Key | Effect |
|---|---|
presentation.locale | Default and allowed interface language. English is the only one. |
presentation.appearance | Default and allowed modes (light, dark, oled, system), densities and text sizes. |
presentation.preferences | Which of mode, density, text size and time zone people may change. |
presentation.timezone | Deployment time zone. Default UTC. |
models.catalog | Models people may pick in chat, each a label and the model id the agent's runtime accepts. Without a catalog there is no picker, and every reply uses the agent's own model. |
models.allow_choice | false hides the picker again. |
models.default_ref | The catalog entry picked to start with. |
The server refuses a model that is not in the catalog. A model runs on the agent's own runtime, so list models of that runtime: a Claude agent cannot answer with a Codex model. Ratings settings live under feedback. Without a chat: section, chat/config.yaml in the deployment folder is read.
Apps: chat inside your app
Each entry under apps: registers one app that embeds a Hubzoid agent's chat through the AG-UI endpoint /api/agui. The key is the app's client id. Make its secret and a ready entry with hubzoid embed secret.
apps:
erpnext:
name: Kestrel & Oak ERP
secret_ref: secret:deployment/app-erpnext
origins: [https://erp.example.com]
access:
agents: [my-hub]
tools: [app_lookup, read_knowledge]
token_to: [app_lookup]
max_lifetime: 3600| Key | Default | Effect |
|---|---|---|
name | the client id | Display name. |
enabled | true | false refuses the app's passes at once and stops replies it started. |
mode | pass | pass: the app's server signs a short pass with the client secret. jwt: Hubzoid checks the app's own JWTs. |
secret_ref | Where the client secret is, for example secret:deployment/app-erpnext or env:ERPNEXT_CLIENT_SECRET. | |
origins | Web addresses whose pages may call Hubzoid from a browser. | |
access | The most the app may allow: agents and tools. Tools are the agent's tool names, connector:<id>, mcp:<server> or "*". A pass can narrow this, never widen it. | |
token_to | Tools that receive the person's own app token. | |
token_header | Authorization: Bearer | Header a connector receives the token in. |
max_lifetime | 3600 | Longest a pass may last, in seconds. At most one day. |
issuer, audience, jwks_url, public_key | jwt mode: how the app's tokens are checked. audience defaults to hubzoid. | |
user_claim, name_claim, email_claim | jwt mode: which claims name the person. | |
scopes, all_users | jwt mode: map the token's scope values to access, or give every signed-in user the app's maximum. A token without a mapped scope gets no access. |
No Hubzoid account is created for people who come from an app. Their chats are kept under a private id made from the app and their user id. Without a hubzoid.yaml, put the same section in apps.yaml in the deployment folder. The SDK guide chat inside your app (opens in a new tab) has the server and page code.
How settings load
<hub>/.envis loaded whenever a command builds or serves the Hub. Its values win over variables already set in your shell. A deployment that should take its values from the process environment instead (a systemdEnvironmentFile, a container platform) ships no.env.<hub>/restricted/.envis loaded next, also overriding. It holds secrets that restricted tools read from the environment. The file tools refuse every path underrestricted/, so the model cannot read these values. TheclaudeCLI and the MCP servers it starts do not inherit them, nor Hubzoid's own service secrets.- Booleans are on when set to
1,trueoryes, in any case. Most also accepton. - Numbers that do not parse fall back to the default in almost every case.
- An empty line value such as
OPENAI_API_KEY=sets nothing, so a stored secret can fill it, andhubzoid doctorwarns about it. - AWS Secrets Manager is optional. A secret named by
AWS_SECRET_NAME,HUBZOID_HUB_SECRET_NAMEorHUBZOID_RESTRICTED_SECRET_NAMEis read at start and wins over the file of its layer. See configuration secrets. - With several Hubs, each bridge loads its own Hub's
.env, and a Hub's.envstays with that Hub. Settings for the whole deployment belong inhubzoid.yamland in the.envnext to it: the deployment key (HUBZOID_SECRET_KEY),DATABASE_URL, sign-in providers,ENABLE_SIGNUPand mail. That.envwins over the process environment.HUBZOID_PUBLIC_URL,HUBZOID_AUTH,HUBZOID_ADMIN_EMAILandHUBZOID_IN_APP_EDITINGfill a keyhubzoid.yamlleaves out. A Hub.envthat sets a differentHUBZOID_AUTHorHUBZOID_SECRET_KEYstops the start with an error. Sign-in settings (OAUTH_*,OPENID_*,GOOGLE_CLIENT_*,MICROSOFT_CLIENT_*,ENABLE_SIGNUP,ENABLE_LOGIN_FORM,ENABLE_OAUTH_SIGNUPandHUBZOID_PUBLIC_URL) are still taken from the Hubs'.envfiles when the deployment's own settings lack them, and the start lists them.
hubzoid init writes a starter .env with a random bridge key and commented examples for each provider. hubzoid doctor checks the result and lists each key's layer by name. See doctor.
Models and reasoning
| Variable | Type | Default | Effect |
|---|---|---|---|
MODEL | model id | model: in AGENTS.md, else claude-local | The Hub's model. claude-local, claude-local/sonnet, claude-local/opus and claude-local/haiku run on the Claude Agent SDK through the installed claude CLI (bare claude-local is Sonnet). codex-local and codex-local/<model-id> run on local Codex through the Codex CLI, version 0.163.0 or 0.147.0. Any other value is a LiteLLM id such as openrouter/anthropic/claude-haiku-4.5, openai/gpt-4o-mini, anthropic/claude-haiku-4-5 or azure/<deployment>, run on the OpenAI Agents SDK. |
OPENROUTER_API_KEY | secret | none | Key for openrouter/ models. |
OPENAI_API_KEY | secret | none | Key for openai/ models. |
ANTHROPIC_API_KEY | secret | none | Key for anthropic/ models. The claude CLI also accepts it for claude-local. |
CLAUDE_CODE_OAUTH_TOKEN | secret | none | Subscription token for claude-local on a server without an interactive claude login. Create it with claude setup-token. Usage draws on the Claude subscription. |
CLAUDE_CODE_OAUTH_TOKEN_SECONDARY | secret | none | Optional first backup for claude-local. When the credential in use is refused for a confirmed usage limit or a rejected login, Hubzoid tries the next one with the same model, Hub and permissions. Needs CLAUDE_CODE_OAUTH_TOKEN set, and is refused together with ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_BASE_URL or a Bedrock, Vertex or Foundry switch. |
CLAUDE_CODE_OAUTH_TOKEN_TERTIARY | secret | none | Optional second backup, tried after the secondary. |
CODEX_HOME | path | ~/.codex | Where Hubzoid reads the file-backed Codex login, auth.json, for codex-local. The Codex CLI's own variable. |
HUBZOID_CODEX_BIN | path | codex on PATH | The Codex CLI binary codex-local runs. Point it at a private copy of a supported version, for example ~/.hubzoid/codex/node_modules/.bin/codex, to keep your own Codex at another version. A path that is not a file, or an unsupported version, stops the agent with the install command. |
JEV_OPENROUTER_API_KEY | secret | none | Dedicated OpenRouter key for Jev decisions: hub.call_jev in workflows and the call_jev chat tool. There is no fallback to OPENROUTER_API_KEY, and the chat model never uses this key. The Call Jev capability shows "Jev key missing" in Settings until it is set. |
AZURE_API_KEY | secret | none | Key for azure/ models. |
AZURE_API_BASE | URL | none | Azure OpenAI resource endpoint, for example https://<resource>.openai.azure.com. Required for azure/ models. |
AZURE_API_VERSION | string | LiteLLM's default | Azure REST API version, for example 2024-10-21. |
REASONING_EFFORT | low, medium, high | unset | OpenAI and Azure reasoning models receive it as their reasoning effort. On Claude it sets an extended thinking budget of 4,000, 12,000 or 24,000 tokens. Other values are ignored. |
SHOW_THINKING | off, indicator, full | indicator | Claude backend only. indicator shows a thinking panel without the text, full streams the summarized reasoning, off shows nothing. true means full and false means off. |
SHOW_TOOLS | off, compact, full | compact | How tool activity appears in replies. compact is the chat app's own row per call and is hidden on Slack, WhatsApp and Telegram. full is an inline line on every surface. off shows nothing. true means compact, false means off, inline means full. |
HUBZOID_VISION | boolean | true | Send uploaded images to the model as images. Set false for a text-only or cost-sensitive Hub. |
HUBZOID_VISION_MAX_EDGE | integer | 1568 | Longest image edge, in pixels, before an image is sent. |
HUBZOID_VISION_MAX_IMAGES | integer | 4 | Images expanded per turn. Older image references stay as text notes. |
HUBZOID_CACHE_TTL | 1h, 5m | 1h | Prompt cache lifetime for Anthropic-family models routed through LiteLLM (anthropic/, openrouter/anthropic/, Bedrock Claude). |
HUBZOID_DISABLE_PROMPT_CACHE | boolean | false | Stop adding prompt cache markers to those requests. |
HUBZOID_TITLE_MODEL | model id | the Hub's model | Model that writes conversation titles in the Hubzoid web app. Each title is one call, recorded as background usage. |
HUBZOID_TEST_RUNTIME | boolean | false | Allow MODEL=hubzoid-test/<script>, a scripted runtime with no model, for automated tests. Without it that model id is refused. |
A scheduled task's model: frontmatter and hubzoid schedule run --model override the model for that run only. See agents and models.
Credential failover keeps the same model and tools on every attempt. A request switches credentials only before any content or tool activity has started, and tries at most three. A limited credential waits for its reported reset, or five minutes without one, and a rejected login stays off until its token is replaced. Hubs that share an operational database share these cooldowns. Several tokens may draw on the same quota, so backups do not guarantee availability, and switching on a real exhausted subscription has not been induced in release testing. Restart the bridges after changing a token. See Claude credential failover (opens in a new tab).
Web app and sign-in
These settings describe the chat UI and how people sign in. With several Hubs, set them once for the deployment, as described in how settings load.
| Variable | Type | Default | Effect |
|---|---|---|---|
HUBZOID_AUTH | boolean | false | Require sign-in. Off is local mode: every request that addresses this machine is the local owner, admin@localhost, and the public port stays on loopback. Turn it on before anyone else can reach the Hub. |
HUBZOID_PUBLIC_URL | URL | none, or the loopback address of a local hubzoid run | The address people open, for example https://hub.example.com. Links Hubzoid writes (one-time sign-in links, downloads, report and Settings links) use it, external sign-in callbacks are built on it, and browser changes must come from it or from HUBZOID_ALLOWED_ORIGINS. Required behind a proxy, on any name other than localhost, and for Google, Microsoft or OpenID Connect sign-in. |
HUBZOID_ALLOWED_ORIGINS | comma list | none | Further addresses people use, for example https://agents.internal.example.com,http://localhost:3080. Each may send changes from a browser, receives sign-in and connector callbacks on its own address, and counts as this machine in local mode. |
HUBZOID_ALLOW_UNAUTHENTICATED_NETWORK | boolean | false | With sign-in off, hubzoid run --host 0.0.0.0 (or any address that is not loopback) stops with exit code 2. true accepts the risk on a network you trust: anyone who can reach the port uses the Hub as its owner. |
HUBZOID_SECRET_KEY | secret | generated in secret.key | The deployment key: one or more Fernet keys separated by commas. The first encrypts and signs, every key decrypts, so prepend a new key to rotate. It encrypts personal connection tokens and connector secrets and signs identity assertions between Hubzoid processes. Unset, Hubzoid creates secret.key next to the manifest in the data folder of a deployment of several Hubs, or <hub>/.hubzoid/secret.key for a single Hub. Back it up apart from the data. |
HUBZOID_ADMIN_EMAIL | none | With the password, creates the first administrator when sign-in is on and no account exists yet, and gives it the owner's access. Remove both after the first start. | |
HUBZOID_ADMIN_PASSWORD | secret | none | That administrator's password, 8 to 1024 characters. |
HUBZOID_ADMIN_NAME | string | the email's local part | That administrator's display name. |
HUBZOID_LINK_HOURS | hours | 72 | How long a one-time sign-in link stays valid. A link works once. |
HUBZOID_SESSION_DAYS | days | 30 | Longest life of a session. Lowering it applies to existing sessions too. |
HUBZOID_SESSION_IDLE_DAYS | days | 7 | A session ends after this many days without use. |
HUBZOID_AUTH_MAX_FAILURES | integer | 10 | Failed sign-ins within 15 minutes, per client address and per email, before that address or email is locked for 15 minutes. Shared by every bridge. |
HUBZOID_PASSWORD_HASH_CONCURRENCY | integer | 4 | Password hashes one process computes at once. |
ENABLE_SIGNUP | boolean | false | Show Create an account on the sign-in page. A new password account waits for an administrator's approval. |
ENABLE_OAUTH_SIGNUP | boolean | false | Create an account at a person's first external sign-in. It waits for approval unless the provider verified the email and its domain is listed explicitly in OAUTH_ALLOWED_DOMAINS. |
ENABLE_LOGIN_FORM, ENABLE_PASSWORD_AUTH | boolean | true | Either set to false turns password sign-in off, so people use an external provider only. |
HUBZOID_MFA_REQUIRED | none, admins, all | admins | Who must use two-step sign-in until an administrator picks a rule in Settings → Organization → Sign-in (or hubzoid auth mfa policy). Someone who must use it sets it up right after their password: an authenticator app code, then ten one-time recovery codes. People who sign in through SAML use their provider's own policy. |
HUBZOID_SUPPORT_CONTACT | text | your administrator | Who people are told to ask for access, on My access and in sign-in help. |
HUBZOID_MAX_FILES_PER_MESSAGE | integer | 10 | Files a person may attach to one message in the web app. |
Sign-in providers
| Variable | Type | Default | Effect |
|---|---|---|---|
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET | string, secret | none | Turn on Continue with Google. Register <HUBZOID_PUBLIC_URL>/oauth/google/callback as the redirect URI. |
GOOGLE_OAUTH_SCOPE | string | openid email profile | Scopes asked of Google. |
MICROSOFT_CLIENT_ID, MICROSOFT_CLIENT_SECRET | string, secret | none | Turn on Microsoft (Entra ID) sign-in. The redirect URI is /oauth/microsoft/callback. A Microsoft email counts as verified only when the ID token carries the xms_edov claim. |
MICROSOFT_CLIENT_TENANT_ID | string | common | The Entra ID tenant. |
MICROSOFT_OAUTH_SCOPE | string | openid email profile | Scopes asked of Microsoft. |
OPENID_PROVIDER_URL | URL | none | Discovery document or issuer URL of one standard OpenID Connect provider. With OAUTH_CLIENT_ID it turns that provider on. The redirect URI is /oauth/oidc/callback. |
OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET | string, secret | none | The OpenID Connect client. Leave the secret empty for a public client, which uses PKCE only. |
OAUTH_PROVIDER_NAME | string | SSO | The button reads "Continue with" and this name. |
OAUTH_SCOPES | string | openid email profile | Scopes asked of the OpenID Connect provider. |
OAUTH_ALLOWED_DOMAINS | comma list | any | Email domains that may sign in with any external provider. Judged on a verified email only. Unset or * allows any domain. A listed Google Workspace domain must also be the account's Workspace domain. |
OAUTH_MERGE_ACCOUNTS_BY_EMAIL | boolean | false | Let an external sign-in with a verified email attach to the existing account with that email, such as one created in Settings. Google sign-in only accounts need it, and hubzoid doctor warns (auth.google_merge) when Google is configured without it. |
See authentication for setting up sign-in and the first administrator.
Server, ports and links
| Variable | Type | Default | Effect |
|---|---|---|---|
PORT | integer | 3080 | Public port: the web app, downloads and MCP. --port wins. |
BRIDGE_PORT | integer | the first free port from 8000 | Bridge port. The bridge always binds 127.0.0.1. --bridge-port wins. Unset, hubzoid run skips ports in use. A port you set must be free. |
HUBZOID_HOST | address | 127.0.0.1 | Interface the public port binds to. 0.0.0.0 listens on every interface and needs sign-in. --host wins. |
BRIDGE_API_KEYS | comma list | dev | Keys the bridge accepts as Authorization: Bearer on /v1 and /uploads. Hubzoid's own processes, such as the Slack adapter and the inbound receiver, send the first key. dev is public, so set a random key for any shared install. hubzoid init writes one. dev is never accepted on /artifacts. |
MODEL_LABEL | string | slug of the AGENTS.md name | Model id reported by /v1/models. Hubs that run together need different ones. |
HUBZOID_MAX_UPLOAD_BYTES | integer | 26214400 (25 MiB) | Per-file cap for attachments in chat requests and for /uploads. A larger file gets HTTP 413. |
HUBZOID_ARTIFACT_SECRET | secret | generated in .hubzoid/artifact_secret | Secret that signs download links. Set it only to share one secret across hosts serving the same Hub. Changing or deleting it revokes every issued link. |
HUBZOID_ARTIFACT_LINK_TTL | seconds | 7 days | How long a newly issued chat download link from write_artifact stays valid. 0 means links do not expire. In the web app, the signed-in owner of a conversation can always download its files. |
HUB_LOG_LEVEL | info, debug, warning | info | Log level of the bridge and edge. |
HUBZOID_INBOUND_PORT | integer | 8100 | Loopback port of the WhatsApp, Telegram and generic webhook receiver. With several Hubs, a Hub without one gets the next free port. |
HUBZOID_HUB_SLUG | string | slug of the Hub folder name | Path segment in /webhooks/<slug>/. Set it only when two Hubs that run together would otherwise get the same slug. |
HUBZOID_SESSION_ID | string | random | Fixes the session id used for the output/<session>/ folder. |
HUBZOID_DATA_DIR | path | the Hub folder | Where a Hub writes its state: .hubzoid/ (databases, keys, chats, artifacts), output/, logs/ and .inbound/. Set it to a durable volume such as /data when the Hub's own files are baked into an image from Git. The Hub folder is then only read, and Settings shows its files read-only. One Hub per data folder, run on its own. |
HUBZOID_TRUSTED_PROXIES | comma list | loopback, and private networks while unset | Proxies in front of the public port whose X-Forwarded-For is believed: addresses or networks, or * when the port is reachable only through your proxy. List a CDN such as Cloudflare, or every person counts as one of its few addresses. |
hubzoid run opens the web app in a browser only from a terminal on a loopback host, and on Linux only in a graphical session (DISPLAY or WAYLAND_DISPLAY). The standard BROWSER variable picks the browser. --no-open skips it.
Artifacts and email
These settings govern published artifacts, created with hub.publish_artifact or the markdown task tool publish_artifact, and workflow email, sent with hub.send_email or send_email. See the workflow API.
| Variable | Type | Default | Effect |
|---|---|---|---|
HUBZOID_ARTIFACT_MAX_BYTES | integer | 52428800 (50 MiB) | Largest file a workflow may publish as an artifact. |
HUBZOID_ARTIFACT_LINK_DAYS | integer | 7 | Default lifetime, in days, of an artifact's public link. Kept between 1 and 90. |
HUBZOID_ARTIFACT_ALLOW_ORIGINS | comma list | none | HTTPS origins that published HTML artifacts may load scripts, styles, fonts and images from, for example https://cdn.jsdelivr.net. Unset, HTML artifacts make no network requests. |
HUBZOID_SHARE_DOMAINS | comma list | none | Email domains for company links (audience="domain" and the viewer's Share). Anyone at a listed domain can open such a link after company sign-in, as a reader-only account with no agent access. Company links stay off unless Google, Microsoft or OpenID Connect sign-in is set up with ENABLE_OAUTH_SIGNUP=true and each share domain listed explicitly in OAUTH_ALLOWED_DOMAINS. |
HUBZOID_EMAIL_DELIVERY | smtp, preview | smtp | preview writes each email to <hub>/.hubzoid/outbox/<person>/ and reports that nothing was sent. |
HUBZOID_SMTP_HOST | host | none | SMTP server. Required with HUBZOID_SMTP_FROM for smtp delivery. |
HUBZOID_SMTP_PORT | integer | 587, or 465 with HUBZOID_SMTP_SSL | SMTP port. |
HUBZOID_SMTP_USERNAME | string | none | SMTP user name, when the server needs one. Credentials are sent only over TLS. |
HUBZOID_SMTP_PASSWORD | secret | none | SMTP password. |
HUBZOID_SMTP_FROM | none | Sender address. A single email address. | |
HUBZOID_SMTP_STARTTLS | boolean | true | Upgrade the connection with STARTTLS. Certificates and host names are verified. |
HUBZOID_SMTP_SSL | boolean | false | Use implicit TLS instead, usually on port 465. |
HUBZOID_SMTP_TIMEOUT | seconds | 30 | Time allowed for each SMTP operation. |
Email goes to the account the run acts as, or, from a team run, to the addresses of an email list declared in workflows/settings.yaml. Code and models cannot name any other recipient. A local or example account such as admin@localhost cannot receive email, so run the workflow as a real account or use preview. SMTP delivery over TLS is tested against stand-ins in release testing, not end to end against a real mail server. With several Hubs, these settings can be set once for the deployment, in the .env next to hubzoid.yaml, and overridden per Hub.
Databases and deployment state
| Variable | Type | Default | Effect |
|---|---|---|---|
DATABASE_URL | database URL | sqlite:///<hub>/.hubzoid/hub.db | Database for the Hub's own tables. A PostgreSQL URL here is also used for the operational store and the workflow engine unless the variables below say otherwise. postgresql:// URLs use psycopg, which Hubzoid installs. |
HUBZOID_OPERATIONAL_DB | database URL | see below | Shared store for accounts, sessions, access grants, identities, connections, audit records, usage and workflow state. |
HUBZOID_DBOS_DB | database URL | see below | System database of the durable workflow engine. SQLite is always per Hub. PostgreSQL can be shared by several bridges. |
HUBZOID_DEPLOYMENT | path | discovered from <hub>/.hubzoid/deployment.json | Explicit path to the manifest of a deployment of several Hubs. |
The operational store resolves in this order: the registered deployment manifest, then HUBZOID_OPERATIONAL_DB, then DATABASE_URL, then the Hub's own database. The workflow engine resolves as: the manifest entry for the Hub, then HUBZOID_DBOS_DB, then DATABASE_URL when it is not SQLite, then <hub>/.hubzoid/dbos.db. When a manifest exists, a different HUBZOID_OPERATIONAL_DB or HUBZOID_DBOS_DB stops startup with an error instead of splitting the data. Change the deployment's configuration, not a single bridge.
Backups
| Variable | Type | Default | Effect |
|---|---|---|---|
HUBZOID_BACKUPS | weekly, daily, off | weekly | Scheduled verified backups of the deployment. |
HUBZOID_BACKUP_KEEP | integer | 4 | Scheduled backups kept. Older ones that still verify are removed. |
HUBZOID_BACKUP_DIR | path | <data>/.hubzoid/backups | Where scheduled backups go. |
HUBZOID_PG_BIN | path | PATH | Folder holding pg_dump and pg_restore, when they are not on PATH. |
See backup and restore.
Chat surfaces
Slack
| Variable | Type | Default | Effect |
|---|---|---|---|
SLACK_BOT_TOKEN | secret | none | Bot user OAuth token (xoxb-). Required by the Slack adapter. |
SLACK_APP_TOKEN | secret | none | App-level token (xapp-) with the connections:write scope, for Socket Mode. Required by the Slack adapter. |
SLACK_IDENTITY_MAPPING | boolean | false | Resolve each sender's verified Slack email (needs the users:read.email scope) and act as the account with that email, matched without regard to case. An email without an active account is treated as nobody. Restricted tools also need the Slack surface in HUBZOID_RESTRICTED_SURFACES. |
WhatsApp and Telegram
| Variable | Type | Default | Effect |
|---|---|---|---|
WHATSAPP_VERIFY_TOKEN | secret | none | Token checked on Meta's webhook verification request. |
WHATSAPP_APP_SECRET | secret | none | App secret used to verify the X-Hub-Signature-256 header on each delivery. |
WHATSAPP_TOKEN | secret | none | Access token used to send replies. |
WHATSAPP_PHONE_NUMBER_ID | string | none | Sending phone number id. All four WhatsApp variables are required. |
TELEGRAM_BOT_TOKEN | secret | none | Bot token. |
TELEGRAM_WEBHOOK_SECRET | secret | none | Checked against the X-Telegram-Bot-Api-Secret-Token header. Both Telegram variables are required. |
TELEGRAM_STREAM | boolean | true | Edit the Telegram reply in place as it streams. |
INBOUND_STREAM_INTERVAL | seconds | 1.0 | Minimum time between edits of the same Telegram message. Values below 0.8 are raised to 0.8. |
INBOUND_HISTORY_MAX | integer | 40 | Messages of history kept per WhatsApp or Telegram conversation. |
INBOUND_HISTORY_TTL_DAYS | number | 0 | Drop a chat's history older than this many days when that chat saves a new turn. Old turns are still read for the first reply after a long gap. 0 keeps it. |
INBOUND_MSG_VERIFY_PROMPT | text | Please tap the button below to verify your number. | Fixed message asking a sender to verify. |
INBOUND_MSG_VERIFIED | text | You are verified. How can I help? | Fixed message after verification. |
INBOUND_MSG_NOT_REGISTERED | text | This number is not registered for access. | Fixed message for a number that no person in Settings and no Hub roster has. |
INBOUND_MSG_NOT_OWN_CONTACT | text | Please share your own number to verify. | Fixed message when a sender shares someone else's contact. |
INBOUND_MSG_PLEASE_VERIFY | text | Please verify first. Tap the button below to share your number. | Fixed message for an unverified sender. |
INBOUND_MSG_NO_RESPONSE | text | Sorry, I do not have a response for that. Please try again. | Sent when a reply would otherwise be empty. |
Generic webhook
| Variable | Type | Default | Effect |
|---|---|---|---|
WEBHOOK_INBOUND_SECRET | secret | none | Shared secret. The generic webhook receiver is off without it. |
WEBHOOK_INBOUND_NAME | string | webhook | Path segment of the endpoint, /webhooks/<hub>/<name>, and the inbox a markdown task names in on_webhook:. |
WEBHOOK_INBOUND_HMAC | boolean | false | Verify X-Signature-256 or X-Hub-Signature-256 as sha256= HMAC of the raw body instead of accepting the secret as a header. |
Webhooks that start code workflows are declared in workflows/settings.yaml and take their secrets from WEBHOOK_SECRET_<NAME>, listed under schedules and workflows. See WhatsApp and Telegram, Slack and webhooks.
Access and identity
| Variable | Type | Default | Effect |
|---|---|---|---|
HUBZOID_RESTRICTED_SURFACES | comma list | web,api,mcp,workflow,app | The complete list of surfaces that may reach restricted tools and personal connections. app is chat embedded in your own app. owui, the older chat, is also in the default. Setting it replaces the default, so include the defaults you still want, for example web,api,mcp,workflow,app,slack-dm. slack-dm covers one-to-one Slack conversations. Add whatsapp or telegram to reach restricted tools and personal connections from those surfaces. Never add slack-channel, where one reply answers a shared thread under one person's identity. |
HUBZOID_ACCESS_TOOLS | boolean | on | false removes the access tools (my_management_scope, who_has_access, explain_access, propose_access_change, propose_new_account) from this agent. Otherwise they are hidden until an organization administrator grants Manage access from chat (access_tools). A proposal applies only after the same manager confirms it in Settings. |
HUBZOID_MANAGEMENT_TOOLS | boolean | unset | Deprecated. true gives every access manager the access tools without the access_tools grant, and hubzoid doctor warns. false removes the tools, like HUBZOID_ACCESS_TOOLS=false. Grant Manage access from chat instead, then remove it. |
HUBZOID_CHANGE_REQUEST_TTL | seconds | 900 | How long a proposed change waits for confirmation in Settings. Kept between 60 and 86,400. |
HUBZOID_PORTAL_DEV | boolean | false | Development only. With HUBZOID_PORTAL_DEV_USER, Settings trusts that subject without a sign-in session. Never set it on a shared or public deployment. |
HUBZOID_PORTAL_DEV_USER | none | The subject trusted when HUBZOID_PORTAL_DEV is on. |
See identity and access and built-in tools.
Schedules and workflows
| Variable | Type | Default | Effect |
|---|---|---|---|
HUBZOID_SCHEDULES | boolean | false | Turn on code workflows (workflows/<name>/*.py) in a hubzoid run of one Hub: they load, their schedules fire, and people can start them from Settings or chat. Without it, a Hub that declares webhooks in workflows/settings.yaml still loads code workflows for webhook runs, but schedules do not fire. A hubzoid run of several Hubs turns it on for every Hub. Markdown tasks and scheduled evals do not need it. |
HUBZOID_DISABLE_SCHEDULE | boolean | false | Stop markdown tasks (schedule/*.md) and scheduled evals from firing. |
HUBZOID_WORKFLOW_USER | the owner recorded at setup, admin@localhost locally | The account team workflows and markdown tasks run as when they declare no run_as. A Hub's value (its secret, then its .env) wins over the deployment's. It must be an existing account that is not pending or blocked and holds Use this agent. | |
HUBZOID_WORKFLOW_TOOLS | boolean | on | false removes the workflow tools (list_workflows, workflow_runs, run_workflow, pause_workflow, resume_workflow, cancel_workflow_run) and the personal schedule tools from this agent. Otherwise they are hidden until someone is granted their capabilities in Settings. |
WEBHOOK_SECRET_<NAME> | secret | none | Secret of the workflow webhook <name> declared in workflows/settings.yaml, upper case with - written as _. A webhook without its secret refuses every delivery. |
HUBZOID_ALERT_URL | URL | none | Fallback alert destination: a webhook URL that receives workflow alerts when no other destination is set: not the workflow's alert_to= or on_failure=, the markdown task's alert_to:, nor alerts.to in workflows/settings.yaml. |
HUBZOID_ALERT_SECRET | secret | none | Signs webhook alerts. Each POST then carries X-Hubzoid-Timestamp and X-Hubzoid-Signature: sha256=<hex>, the HMAC-SHA256 of the timestamp, a . and the body. Slack and email alerts are not signed. |
HUBZOID_EVAL_JUDGE_MODEL | model id | the Hub's model | Model that grades eval cases. hubzoid eval run --judge-model wins. See evals. |
HUBZOID_EVAL_KEEP_RUNS | integer | 200 | Eval result files kept per Hub. 0 keeps every run. A negative value is an error. |
Personal schedules and Run for me runs act as the person who saved or started them, never as HUBZOID_WORKFLOW_USER. Other workflow limits, such as max_concurrent_workflows, agent_max_attempts, webhook declarations, email lists and alert rules, live in workflows/settings.yaml rather than the environment. See the workflow API and workflows and schedules.
A Hub's own workflow code can read more variables. A ready-to-use Hub lists them in its README and, when it declares them, in its settings.yaml, which hubzoid hub settings shows.
MCP and connectors
| Variable | Type | Default | Effect |
|---|---|---|---|
MCP_SERVER | boolean | on for a local hubzoid run and for an https HUBZOID_PUBLIC_URL, off otherwise | Serve the Hub's tools and knowledge over MCP at /mcp (at /b/<hub>/mcp with several Hubs). Clients connect with OAuth after the person signs in and consents. A value set in the Hub's .env wins. With several Hubs, set it in the Hub's .env with MCP_PUBLIC_URL. |
MCP_PUBLIC_URL | URL | <public address>/mcp when MCP_SERVER is on by default | The canonical public MCP endpoint, required with MCP_SERVER=true. It must use https (plain http only on loopback) and end in /mcp, or in /b/<slug>/mcp with several Hubs. |
HUBZOID_MCP_TIMEOUT | seconds | 30 | Time one MCP tool call may take, for every MCP server and connector. A server's own client_session_timeout_seconds in .mcp.json wins. On claude-local an explicit value becomes the CLI's MCP_TOOL_TIMEOUT, unless that is set. |
HUBZOID_CONNECT_JOURNEY | boolean | true | Give the agent the connect_account tool, which sends a person a personal link to connect an app from chat or WhatsApp. false removes it. |
HUBZOID_CONNECT_TTL | seconds | 600 | How long a connection link stays valid. Kept between 60 and 3,600. |
HUBZOID_CONNECTOR_PRIVATE_HOSTS | comma list | none | Private hosts a connector's sign-in may reach besides the server's own host, for example an internal identity provider. Cloud metadata addresses are always refused. |
Connectors themselves are files, not environment variables: connectors/.mcp.json for servers everyone shares, and connectors/<id>.yaml for remote servers people sign in to (see Hub files). Settings can save them only when browser editing is on. See add a connector and connect an assistant.
Tools and the shared browser
| Variable | Type | Default | Effect |
|---|---|---|---|
HTTP_ALLOWLIST | comma list | none | Hostnames http_get may request. Subdomains of a listed host are allowed. Only the requested URL is checked, not where it redirects. Unset allows any host. |
HUBZOID_DISABLE_HTTP_GET | boolean | false | Remove http_get from the tool registry. |
HUBZOID_DISABLE_WEB_SEARCH | boolean | false | Remove web_search from the tool registry. |
CONNECTIONS | comma list | none | Apps this Hub's own tools may connect per person through the optional Composio broker, for example odoo,slack. Unset turns these connections off. |
COMPOSIO_API_KEY | secret | none | Key for that broker. Required for CONNECTIONS. |
HUBZOID_BROWSER | boolean | false | Give the Hub one shared Playwright browser as MCP tools (browser_navigate, browser_click, browser_snapshot and the rest). |
HUBZOID_BROWSER_PORT | integer | 8931 | Port of the browser sidecar Hubzoid starts itself. |
HUBZOID_BROWSER_MCP_URL | URL | http://localhost:<port>/mcp | Connect to an already running Playwright MCP endpoint and start nothing. |
HUBZOID_BROWSER_CDP_URL | URL | none | Attach the sidecar to a browser pool (browserless) for hard limits. Use the ws://host:3000?token=... form so the token survives. |
HUBZOID_BROWSER_CHANNEL | string | chromium | Browser build for the sidecar. Keep chromium in containers. |
HUBZOID_BROWSER_MCP_VERSION | string | 0.0.81 | Pinned @playwright/mcp version for the sidecar Hubzoid starts. latest floats deliberately. |
HUBZOID_BROWSER_MAX_RSS_MB | integer | 0 | Without a pool, restart the browser when its memory passes this many MB. 0 turns the watchdog off. |
HUBZOID_BROWSER_CONCURRENT | integer | 2 | Parallel browser slots in the pool. Read by docker/browser-compose.yml. |
HUBZOID_BROWSER_QUEUED | integer | 5 | Requests that may wait before the pool rejects new ones. Read by the compose file. |
HUBZOID_BROWSER_MEMORY | size | 2g | Hard memory ceiling of the pool container. Read by the compose file. |
HUBZOID_BROWSER_TIMEOUT_MS | milliseconds | 60000 | Pool timeout that reaps stuck sessions. Read by the compose file. |
HUBZOID_BROWSER_TOKEN | secret | hubzoid | Token shared by the pool and the sidecar in the compose file. |
HUBZOID_BROWSER_MCP_IMAGE_TAG | string | v0.0.81 | Playwright MCP image tag in the compose file. |
HUBZOID_BROWSER_TIMEOUT | seconds | 60 | No effect. Hubzoid reads the value, but nothing uses it. Set the pool timeout with HUBZOID_BROWSER_TIMEOUT_MS. |
See tools and connectors and built-in tools.
Telemetry
| Variable | Type | Default | Effect |
|---|---|---|---|
HUBZOID_OTEL_ENDPOINT | URL | none | OTLP/HTTP base URL for traces, for example a Langfuse .../api/public/otel endpoint. Unset turns tracing off. |
HUBZOID_OTEL_NORMALIZE | boolean | false | claude-local only. The bridge receives the claude process's traces on a loopback route, renames token attributes to the gen_ai.usage.* names Langfuse maps, sets the person as user.id, and forwards them. No separate collector is needed. |
HUBZOID_OPENAI_TRACING | boolean | false | OpenAI Agents backend only. Export runs, including prompts and tool data, to OpenAI's trace dashboard. Independent of HUBZOID_OTEL_ENDPOINT. |
OTEL_EXPORTER_OTLP_HEADERS | k=v,k=v | none | Headers for the trace backend, such as Authorization=Basic .... Also read back for eval pushes to Langfuse. |
OTEL_EXPORTER_OTLP_PROTOCOL | string | http/protobuf | OTLP protocol for the claude process. |
OTEL_METRICS_EXPORTER | string | none | Metrics exporter for the claude process. Set it only if you run a metrics backend. |
OTEL_LOGS_EXPORTER | string | none | Logs exporter for the claude process. |
OTEL_RESOURCE_ATTRIBUTES | k=v,k=v | none | Resource attributes for traces. On the OpenAI Agents backend Hubzoid adds hubzoid.hub to yours. On claude-local the claude process gets hubzoid.user, hubzoid.hub, hubzoid.surface and user.id for each turn. |
LANGFUSE_PUBLIC_KEY | secret | none | With LANGFUSE_SECRET_KEY and HUBZOID_OTEL_ENDPOINT, eval runs are pushed to Langfuse as datasets with scores. |
LANGFUSE_SECRET_KEY | secret | none | See LANGFUSE_PUBLIC_KEY. |
See observability.
Several Hubs
| Variable | Type | Default | Effect |
|---|---|---|---|
HUBZOID_GATEWAY_BRANDING | Hub slug or path | see effect | The Hub whose branding/ folder brands the deployment's pages, or a folder that holds branding/. Without it, <data-dir>/branding/ is used when it holds files, then the first Hub's branding/. |
HUBZOID_GATEWAY_BRIDGE_TIMEOUT | seconds | 180 | How long the deployment waits for each bridge's first answer. Raise it when many Hubs start at once on a busy machine. |
Sign-in and the deployment key are set once for the deployment, as described in how settings load. See several Hubs.
Remote CLI
| Variable | Type | Default | Effect |
|---|---|---|---|
HUBZOID_SERVER | URL | none | The deployment hubzoid auth and hubzoid api talk to. --server wins. |
HUBZOID_API_KEY | secret | none | A personal API key (hzp_...) those commands sign in with instead of a saved session. |
Docker Compose
docker/docker-compose.yml in the SDK repository reads these from your shell.
| Variable | Default | Effect |
|---|---|---|
HUB_PATH | required | Absolute path of your Hub folder, mounted at /hub. The compose file stops with a message when it is not set. |
HUBZOID_PUBLISH | 3080 | The published port, for example 127.0.0.1:3080 to stay on this machine. |
HUBZOID_IMAGE | hubzoid:local | The image to run. |
HUBZOID_AUTH | true | Sign-in. The compose file turns it on by default. |
HUB_PATH=$PWD/my-hub docker compose -f docker/docker-compose.yml up -dSee Docker.
Configuration secrets
Each configuration layer can also read an AWS Secrets Manager secret that holds a flat JSON object of settings. Nothing here is needed when you use files alone. With no secret named, Hubzoid makes no AWS call. This path is tested against stand-ins in release testing, not end to end against AWS itself.
| Variable | Layer | Where to set it | Effect |
|---|---|---|---|
AWS_SECRET_NAME | Deployment | The deployment's environment or the .env next to hubzoid.yaml, or a single Hub's .env or environment | Name or ARN of the deployment secret. A deployment of several Hubs records it in deployment.json so bridges started on their own find it. With several Hubs, a Hub's .env naming it is ignored. |
AWS_REGION | All | Beside the secret name | Region for every secret read. AWS_DEFAULT_REGION is the fallback, and an ARN carries its own region. |
HUBZOID_HUB_SECRET_NAME | Hub | <hub>/.env only | A secret whose keys override <hub>/.env for this Hub. |
HUBZOID_RESTRICTED_SECRET_NAME | Restricted | <hub>/restricted/.env only | A secret whose keys override restricted/.env for this Hub's restricted tools. |
- Secrets are read at start with boto3's normal credential chain: an instance or task role,
AWS_PROFILE, or access keys in the environment. - Within a layer the secret wins over the file. A single Hub applies its deployment secret after its
.env. A Hub that runs with others takes only the bridge's share of the deployment secret, before its own.env. That share covers workflow identity, email, report link settings, database URLs, the public address,HUBZOID_SECRET_KEYand telemetry. - A secret may not set
AWS_*keys, secret names or process control keys such asPATH,LD_*orPYTHONPATH. Values are strings, numbers or booleans. - A secret that cannot be read stops the process at start with the secret name, the layer and the AWS error class, never a value.
- Values are read once. After a rotation, restart what reads the secret: the whole
hubzoid runfor the deployment secret, and the Hub, withhubzoid restart <hub>, for a Hub or restricted secret. hubzoid doctorreports each configured key's layer by name only, and whether each secret can be read.--skip-secret-fetchlists the secrets without calling AWS. See doctor.
No longer read
MCP_ACCESS_GROUP has no effect. Access to MCP is a Use this agent grant in Settings, and personal MCP connections are connectors added in Settings. hubzoid doctor warns about a leftover MCP_ACCESS_GROUP. Remove it from older .env files.
Set by Hubzoid
Hubzoid sets these for its own child processes. You do not set them by hand.
| Variable | Set by | Purpose |
|---|---|---|
HUBZOID_HUB_DIR | hubzoid run | Tells the bridge which Hub to serve. |
HUBZOID_GATEWAY | a hubzoid run deployment from hubzoid.yaml or of several Hubs | Marks a bridge as one of several Hubs run together, which also turns on code workflows. |
HUBZOID_EDGE_DEFAULT, HUBZOID_EDGE_DEFAULT_FALLBACKS, HUBZOID_EDGE_ROUTES | hubzoid run | The edge's default upstream, the bridges it tries next, and its table of forwarded path prefixes. |
HUBZOID_EDGE_PUBLIC_SCHEME | hubzoid run | https when the declared public URL is HTTPS, so the edge forwards X-Forwarded-Proto: https upstream. |
HUBZOID_EDGE_WORKFLOW_HUBS | hubzoid run | The Hubs whose workflow engines the edge watches for /healthz/workflows. |
HUBZOID_WEBHOOK_EVENTS | the markdown task runner | Newline-separated event files a webhook-triggered run: script owns for this run. |
HUBZOID_RUN_AS | the markdown task runner | The account a run: script's run acts as. For information only. It grants the script nothing. |
HUBZOID_DEPLOYMENT_SECRET_INHERITED | a hubzoid run of several Hubs | Tells a bridge that hubzoid run has already read the deployment secret for it. |
MCP_TOOL_TIMEOUT | the claude-local runtime | The claude CLI's MCP tool timeout, from an explicit HUBZOID_MCP_TIMEOUT. |

