horus-os

Environment variables

Complete reference for every environment variable horus-os reads, what each controls, and which ones are secrets that must stay server-side.

Overview#

horus-os reads configuration from two places: the config.toml file in your data directory and process environment variables. Environment variables hold everything that should never be written to a file: API keys, tokens, passwords, and runtime gates. The general rule in this repo is that secrets live in the environment, while durable settings live in config.toml.

This page lists every variable horus-os reads, what it does, and whether it is a secret. A variable marked Secret: yes grants access to a provider account, mailbox, or remote service. Keep those values out of committed files, shell history, and any browser-facing surface. See Security for the threat model behind these rules.

Important

Variables prefixed with NEXT_PUBLIC_ are the only ones that are safe to expose to a browser. Every other variable on this page is read by the Python backend and must stay server-side. Never put a provider key, token, or password behind a NEXT_PUBLIC_ name.

Core runtime#

VariableSecretPurpose
HORUS_OS_DATA_DIRNoOverrides the default data directory. When set, horus-os stores config.toml, horus.sqlite, the vault (notes/), skills/, models/, and vectors.sqlite here instead of the platform default. The path is expanded (~ is resolved).
HORUS_OS_PRICING_PATHNoPath to a custom pricing.json for cost accounting. Overrides the [pricing] path key in config.toml and the bundled pricing table. The env var wins over the TOML value.
HORUS_OS_DISABLE_SCHEDULERNoWhen set to the exact string true, the scheduler does not start. start() becomes a silent no-op and the runtime boots cleanly with a passing health check. Use this for deployments that must never run scheduled tasks.
HORUS_TZNoIANA timezone name (for example America/New_York) used to resolve cron schedules. When unset, horus-os falls back to the system local timezone via zoneinfo.
HORUS_OS_DISABLE_PLUGINSNoWhen set to true, all plugin discovery and loading is skipped globally. horus-os serve --disable-all-plugins sets this internally; you can also set it yourself to run with plugins off.
HORUS_OS_PLUGIN_DIRNoOverrides the directory horus-os scans for installed plugins.
HORUS_OS_DEMONoWhen set to 1, the server runs in demo mode: integration write endpoints return 403 before performing any write. Used to host a read-only public dashboard.
Note

Installing a plugin outside a virtualenv is not gated by an environment variable. By default horus-os plugins install refuses to run against a system (non-virtualenv) Python interpreter. Pass the --allow-system-python flag to override that check; there is no equivalent env var.

The default data directory (when HORUS_OS_DATA_DIR is unset) is platform specific:

  • macOS: ~/Library/Application Support/horus-os
  • Linux: $XDG_DATA_HOME/horus-os, or ~/.local/share/horus-os
  • Windows: %APPDATA%\horus-os

Provider keys#

These keys authenticate horus-os to the model providers. They are secrets. The CLI and the dashboard read them straight from the environment and never persist them.

VariableSecretPurpose
ANTHROPIC_API_KEYYesAPI key for the Anthropic provider. Required when default = "anthropic" (the default).
GEMINI_API_KEYYesAPI key for the Google Gemini provider. Required when the Gemini provider is selected.
GOOGLE_API_KEYYesFallback for the Gemini provider. horus-os reads GEMINI_API_KEY first, then GOOGLE_API_KEY.
HORUS_OS_LOCAL_API_KEYYesAPI key sent to a local OpenAI-compatible model server. Defaults to the literal horus-local when unset, which is fine for servers that ignore the key.
HORUS_OS_LOCAL_BASE_URLNoOverrides the base URL of the local OpenAI-compatible provider (for example an Ollama endpoint). Takes precedence over the [local] base_url value in config.toml.
Tip

Set provider keys in your shell profile or a process manager environment, not in config.toml. If a key is missing, horus-os run tells you exactly which variable to set for the selected provider before it makes any model call.

Web search and research#

VariableSecretPurpose
HORUS_OS_WEB_SEARCH_KEYYesAPI key for the configured web-search provider (brave or tavily). Read at tool-registration time and never persisted to config.toml. Not required for a self-hosted SearXNG instance.

The web-search provider and base URL themselves are stored in config.toml under [tools.web_search], not in the environment. See Web access and Autonomous research.

Shell tool#

The shell tool is gated by a runtime environment variable so toggling it never requires rewriting config.toml.

VariableSecretPurpose
HORUS_OS_SHELL_ENABLEDNoThe runtime gate for the shell_exec tool. The tool is registered only when this equals the exact string true AND [shell] enabled is true in config.toml. Both conditions must hold.
Caution

Enabling the shell tool lets agents execute commands on the host. Treat HORUS_OS_SHELL_ENABLED=true as a deliberate, scoped decision. The remaining shell limits (timeout, output cap, working directory, type, confirmation) are configured in the [shell] table of config.toml.

Webhooks and remote triggers#

VariableSecretPurpose
HORUS_OS_WEBHOOK_SECRETYesHMAC signing secret for the webhook adapter. The adapter refuses to run when this is unset. Inbound requests must carry an X-Horus-Signature: sha256=<hex> header where the digest is HMAC-SHA256 of the raw body keyed with this secret.

See Remote access for how remote triggers are authenticated.

Discord#

VariableSecretPurpose
HORUS_OS_DISCORD_TOKENYesDiscord bot token. Required to start the Discord adapter.
HORUS_OS_DISCORD_GUILD_IDNoThe Discord server (guild) ID the bot operates in.
HORUS_OS_DISCORD_ADMIN_ROLE_IDNoRole ID whose members are treated as admins.
HORUS_OS_DISCORD_CATEGORYNoCategory name under which managed channels are created.
HORUS_OS_DISCORD_AGENT_PROFILENoAgent profile name the Discord adapter loads.

See Discord.

Slack#

VariableSecretPurpose
HORUS_OS_SLACK_BOT_TOKENYesSlack bot token. Required to start the Slack adapter.
HORUS_OS_SLACK_SIGNING_SECRETYesSlack request-signing secret used to verify inbound events. Required.
HORUS_OS_SLACK_AGENT_PROFILENoAgent profile name the Slack adapter loads.

See Slack.

Email#

VariableSecretPurpose
HORUS_OS_EMAIL_IMAP_HOSTNoIMAP server hostname. Required to start the email adapter.
HORUS_OS_EMAIL_IMAP_USERNoIMAP login username.
HORUS_OS_EMAIL_IMAP_PASSWORDYesIMAP password or app password.
HORUS_OS_EMAIL_IMAP_PORTNoIMAP SSL port. Defaults to 993.
HORUS_OS_EMAIL_SMTP_HOSTNoSMTP server hostname for sending.
HORUS_OS_EMAIL_SMTP_USERNoSMTP login username. Defaults to the IMAP user.
HORUS_OS_EMAIL_SMTP_PASSWORDYesSMTP password. Defaults to the IMAP password.
HORUS_OS_EMAIL_SMTP_PORTNoSMTP SSL port. Defaults to 465.
HORUS_OS_EMAIL_POLL_INTERVALNoSeconds between mailbox polls. Defaults to 60.
HORUS_OS_EMAIL_AGENT_PROFILENoAgent profile name the email adapter loads.

See Email.

Calendar#

VariableSecretPurpose
HORUS_OS_CALENDAR_OAUTH_CLIENT_PATHNoPath to the Google OAuth client JSON file. Required to start the calendar adapter.
HORUS_OS_CALENDAR_WRITE_ALLOWEDNoWhen set to the exact string true, the create-event tool is registered. Unset or any other value keeps the calendar read-only. The handler re-checks this at call time, so flipping it off mid-run denies writes.

See Calendar.

Voice#

The voice adapter places outbound calls through Twilio. The call tools register only when all three Twilio variables are set, and calling stays disarmed until you explicitly opt in with the ask-first gate.

VariableSecretPurpose
HORUS_OS_TWILIO_ACCOUNT_SIDNoTwilio account SID. One of the three variables required for the voice tools to register.
HORUS_OS_TWILIO_AUTH_TOKENYesTwilio auth token. One of the three variables required for the voice tools to register.
HORUS_OS_TWILIO_FROM_NUMBERNoThe caller-id phone number, in E.164 form. One of the three variables required for the voice tools to register.
HORUS_OS_VOICE_PUBLIC_BASE_URLNoPublic base URL Twilio can reach to fetch call instructions and open the media stream (a tunnel works in local development). Required to place a call.
HORUS_OS_VOICE_CALLS_ALLOWEDNoThe ask-first arming gate for outbound calling. A call is placed only when this equals the exact string true, and the handler re-checks it at call time. Unset or any other value keeps calling disarmed, so an agent can never dial out unless you have armed it.
HORUS_OS_VOICE_NOTIFY_WEBHOOKNoOptional URL that receives a JSON notification when a call completes.

See Voice.

GitHub#

VariableSecretPurpose
GITHUB_TOKENYesGitHub personal access token. Optional for public, read-only access, but raises the rate limit and is required to read private repositories. Read server-side only.

See GitHub.

Supabase (server-side backend)#

These variables configure the Supabase adapter and horus-os doctor --supabase. They are read only by the Python backend and must never reach the browser. The adapter stays a silent no-op until both are present.

VariableSecretPurpose
SUPABASE_URLNoYour Supabase project URL the backend connects to.
SUPABASE_SERVICE_KEYYesThe Supabase service role key. It bypasses row-level security, so keep it server-side only and never expose it through a NEXT_PUBLIC_ name.

The browser-facing Supabase keys (NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY) are separate and documented under Dashboard. See Supabase.

Vercel and Tailscale#

VariableSecretPurpose
HORUS_OS_VERCEL_TOKENYesVercel API token. The server uses it to call the Vercel REST API; the token value is never returned to clients.
HORUS_OS_VERCEL_PROJECT_IDNoVercel project ID the deployment status calls target.
HORUS_OS_TAILSCALE_API_KEYYesTailscale API key used for the Tailscale integration.

See Deploy to Vercel and Remote access.

OpenTelemetry#

The OTel adapter reads standard OpenTelemetry SDK variables directly, plus one horus-os-specific gate.

VariableSecretPurpose
OTEL_EXPORTER_OTLP_ENDPOINTNoOTLP collector endpoint the span exporter sends to. Read by the OpenTelemetry SDK.
OTEL_EXPORTER_OTLP_HEADERSSometimesHeaders for the OTLP exporter. May carry an auth token for a hosted backend; treat as a secret in that case. Read by the OpenTelemetry SDK.
HORUS_OS_OTEL_CAPTURE_CONTENTNoWhen set to true, opts into capturing message content on spans. Off by default so prompt and response bodies are not exported.

See OpenTelemetry and Observability.

Dashboard (NEXT_PUBLIC_*)#

These are read by the Next.js dashboard at build time and are baked into the browser bundle. They are public by design. Never put a secret behind a NEXT_PUBLIC_ name.

VariableSecretPurpose
NEXT_PUBLIC_API_BASENoBackend origin the dashboard fetches /api from. Leave unset for same-origin. It is an origin, not a secret.
NEXT_PUBLIC_SUPABASE_URLNoYour Supabase project URL for the anon read path. The browser counterpart of the server-side SUPABASE_URL.
NEXT_PUBLIC_SUPABASE_ANON_KEYNoThe Supabase anon key for browser reads. Safe to publish; row-level security is enforced. Never put the service key (SUPABASE_SERVICE_KEY) here.
NEXT_PUBLIC_HORUS_DEMONoWhen 1, the dashboard renders the public marketing view at /.
Warning

Only the Supabase anon key, never the service key, may be exposed through a NEXT_PUBLIC_ variable. Anyone who can load the dashboard can read these values. See Supabase for the row-level-security details.

Secrets at a glance#

Treat every variable below as a credential. Keep it out of committed files, shell history, screenshots, and any browser-facing surface:

  • ANTHROPIC_API_KEY, GEMINI_API_KEY, GOOGLE_API_KEY, HORUS_OS_LOCAL_API_KEY
  • HORUS_OS_WEB_SEARCH_KEY
  • HORUS_OS_WEBHOOK_SECRET
  • HORUS_OS_DISCORD_TOKEN
  • HORUS_OS_SLACK_BOT_TOKEN, HORUS_OS_SLACK_SIGNING_SECRET
  • HORUS_OS_EMAIL_IMAP_PASSWORD, HORUS_OS_EMAIL_SMTP_PASSWORD
  • HORUS_OS_TWILIO_AUTH_TOKEN
  • GITHUB_TOKEN
  • SUPABASE_SERVICE_KEY
  • HORUS_OS_VERCEL_TOKEN, HORUS_OS_TAILSCALE_API_KEY
  • OTEL_EXPORTER_OTLP_HEADERS (when it carries an auth token)

See also#