Skip to content

Server Configuration (Hub & Runtime Broker)

This document describes the configuration for the Scion Hub (State Server) and the Scion Runtime Broker.

Server configuration is defined in the server section of your settings.yaml file.

  • Primary: ~/.scion/settings.yaml (Global settings)
  • Legacy: ~/.scion/server.yaml (Deprecated, but supported as fallback)
schema_version: "1"
server:
env: prod
log_level: info
hub:
port: 9810
host: "0.0.0.0"
public_url: "https://hub.scion.dev"
broker:
enabled: true
port: 9800
broker_id: "generated-uuid"
database:
driver: sqlite
url: "hub.db"
auth:
dev_mode: false

Controls the central Hub API server.

FieldTypeDefaultDescription
portint9810HTTP port to listen on (standalone mode). In combined mode (--enable-web), the Hub API is served on the web port instead and this setting is ignored.
hoststring"0.0.0.0"Network interface to bind to.
public_urlstringThe externally accessible URL of the Hub (used for callbacks).
gcp_project_idstringGCP project ID used for minting GCP Service Accounts. Auto-detected if running on GCE/Cloud Run.
read_timeoutduration"30s"HTTP read timeout.
write_timeoutduration"60s"HTTP write timeout.
admin_emailslist[]List of emails granted super-admin access.
soft_delete_retentiondurationDuration to retain soft-deleted agents (e.g., "72h").
soft_delete_retain_filesboolfalsePreserve workspace files during the soft-delete period.
corsobjectCORS configuration (see below).
FieldTypeDefaultDescription
enabledbooltrueEnable CORS.
allowed_originslist["*"]Allowed origins.

Controls the Runtime Broker service.

FieldTypeDefaultDescription
enabledboolfalseWhether to start the broker service.
portint9800HTTP port to listen on.
broker_idstringUnique UUID for this broker.
broker_namestringHuman-readable name.
broker_nicknamestringShort display name.
hub_endpointstringThe Hub URL this broker connects to.
container_hub_endpointstringOverrides hub_endpoint when injecting the Hub URL into agent containers. Use when containers cannot reach the Hub at the broker’s address (e.g. http://host.containers.internal:8080 for local development).
broker_tokenstringAuthentication token for the Hub.
auto_provideboolfalseAutomatically add as provider for new projects.

Persistence settings for the Hub.

FieldTypeDefaultDescription
driverstring"sqlite"Database driver: sqlite or postgres.
urlstring"hub.db"Connection string or file path.
FieldTypeDefaultDescription
dev_modeboolfalseEnable insecure development authentication.
dev_tokenstringStatic token for dev mode.
authorized_domainslist[]Limit access to specific email domains.

OAuth provider credentials.

server:
oauth:
web:
google: { client_id: "...", client_secret: "..." }
github: { client_id: "...", client_secret: "..." }
cli:
google: { client_id: "...", client_secret: "..." }

Backend for storing templates and artifacts.

FieldTypeDefaultDescription
providerstring"local"Storage provider: local or gcs.
bucketstringGCS bucket name.
local_pathstringLocal path for storage.

Backend for managing encrypted secrets. The local backend is read-only and rejects secret write operations. Configure gcpsm to enable full secret management.

FieldTypeDefaultDescription
backendstring"local"Secrets backend: local or gcpsm. The local backend rejects writes; use gcpsm for production.
gcp_project_idstringGCP Project ID for Secret Manager. Required when backend is gcpsm.
gcp_credentialsstringPath to GCP service account JSON or the JSON content itself. Optional if using Application Default Credentials.

All server settings can be overridden via environment variables using the SCION_SERVER_ prefix and snake_case naming.

Examples:

  • server.hub.port -> SCION_SERVER_HUB_PORT
  • server.hub.gcp_project_id -> SCION_SERVER_HUB_GCPPROJECTID
  • server.broker.enabled -> SCION_SERVER_BROKER_ENABLED
  • server.broker.container_hub_endpoint -> SCION_SERVER_BROKER_CONTAINERHUBENDPOINT
  • server.database.url -> SCION_SERVER_DATABASE_URL
  • server.auth.dev_mode -> SCION_SERVER_AUTH_DEVMODE
  • server.secrets.backend -> SCION_SERVER_SECRETS_BACKEND
  • server.secrets.gcp_project_id -> SCION_SERVER_SECRETS_GCPPROJECTID
  • server.secrets.gcp_credentials -> SCION_SERVER_SECRETS_GCPCREDENTIALS

These environment variables control server-side logging behavior. They are not part of the settings.yaml structure.

VariableDescriptionDefault
SCION_LOG_GCPEnable GCP Cloud Logging JSON format on stdoutfalse
SCION_LOG_LEVELLog level: debug, info, warn, errorinfo
SCION_CLOUD_LOGGINGSend logs directly to Cloud Logging via client libraryfalse
SCION_CLOUD_LOGGING_LOG_IDLog name in Cloud Logging for application logsscion
SCION_GCP_PROJECT_IDGCP project ID for Cloud Logging (priority 1)auto-detect
GOOGLE_CLOUD_PROJECTGCP project ID for Cloud Logging (priority 2)-
SCION_SERVER_REQUEST_LOG_PATHWrite HTTP request logs to a file at this path. Each line is a JSON object in HttpRequest format. When not set, request logs follow the default routing (stdout in background mode, suppressed in foreground mode, Cloud Logging when enabled).(disabled)

See the Local Development Logging guide for details on log formats, request log fields, and Cloud Logging integration.

When server.hub.public_url is not explicitly set, the Hub endpoint injected into agents is resolved in this order:

  1. SCION_SERVER_HUB_PUBLIC_URL or server.hub.public_url — explicit Hub public URL.
  2. Project-level hub.endpoint setting.
  3. SCION_SERVER_BASE_URL — the server’s public base URL (also used for OAuth redirects).
  4. Auto-computed http://localhost:{port} (last resort).

For local development where the Hub runs on localhost but agents are in containers, set server.broker.container_hub_endpoint to a container-accessible address like http://host.containers.internal:8080.

Notification channels deliver agent messages to external systems. Configure them under server.hub.notification_channels as a list of channel objects. Each object has a type, a params map, and optional filters.

server:
hub:
notification_channels:
- type: <channel-type>
params:
# channel-specific key/value pairs
filter_urgent_only: false # if true, only deliver urgent messages
filter_types: # if set, only deliver these message types
- input-needed
- state-change

Delivers notifications via a Slack incoming webhook using Slack’s text payload format.

Type: slack

Parameters:

ParamRequiredDescription
webhook_urlyesSlack incoming webhook URL (must use https://).
channelnoOverride the webhook’s default channel.
mention_on_urgentnoMention string added when msg.Urgent == true (e.g. @here, @channel).

Example:

notification_channels:
- type: slack
params:
webhook_url: https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXX
mention_on_urgent: "@here"

Delivers notifications as a raw HTTP POST to an arbitrary URL. Use this when you need the full structured payload without truncation or when integrating with a custom receiver.

Type: webhook

Parameters:

ParamRequiredDescription
webhook_urlyesDestination URL (must use https://).

Example:

notification_channels:
- type: webhook
params:
webhook_url: https://example.com/scion-notifications

Delivers notifications by email.

Type: email

Parameters:

ParamRequiredDescription
toyesRecipient email address.
fromnoSender address override.
smtpnoSMTP server host:port.

Example:

notification_channels:
- type: email
params:
to: oncall@example.com

Delivers notifications via a Discord incoming webhook using Discord’s native webhook format (rich embeds, colour-coded severity, allowed-mentions-controlled role/user pings). Unlike the Slack channel, the Discord channel targets the Discord-native endpoint — the /slack-compatibility suffix is explicitly rejected because it dilutes what each channel type means and silently hides the user’s real intent.

Type: discord

Parameters:

ParamRequiredDescription
webhook_urlyesDiscord incoming webhook URL. Must use https:// and one of the allowed Discord hosts: discord.com, discordapp.com, ptb.discord.com, canary.discord.com. Path must begin with /api/webhooks/ and must not end with /slack.
mention_on_urgentnoMention string applied when msg.Urgent == true. Use Discord mention syntax: <@&ROLE_ID> for a role, <@USER_ID> for a user. @here and @everyone are intentionally not supported — the channel sets allowed_mentions.parse: [] so Discord will not resolve them even if present.
usernamenoOverride the webhook’s default username for delivered messages.
avatar_urlnoOverride the webhook’s default avatar for delivered messages.

Embed colours by message type:

TypeColourHex
state-changeblue#3498db
input-neededyellow#f1c40f
instructiongrey#95a5a6
(urgent — any type)red#e74c3c (overrides the type colour)

Truncation: Discord caps embed descriptions at 2048 characters. Messages longer than that are truncated with a …(truncated) marker — use the webhook channel type if you need the full structured payload without truncation.

Example:

notification_channels:
- type: discord
params:
webhook_url: https://discord.com/api/webhooks/123456789012345678/abcDEFghiJKLmnoPQR_stu
mention_on_urgent: "<@&987654321098765432>"
username: Scion Hub
filter_urgent_only: false
filter_types:
- input-needed
- state-change

Two-Tier Settings Architecture (HA Deployments)

Section titled “Two-Tier Settings Architecture (HA Deployments)”

In HA deployments where multiple Hub replicas share a Postgres database, settings are split into two tiers to prevent node drift while keeping bootstrap settings file-managed.

Settings required before the database connection exists, or that are restart-bound. Managed exclusively via settings.yaml and SCION_SERVER_* environment variables. Cannot be written via the admin APIPUT /api/v1/admin/server-config returns 422 if any Layer-0 key is present.

GroupKeys (server. prefix unless noted)
Databasedatabase.*
Listenershub.port, hub.host, hub.read_timeout, hub.write_timeout, broker.*
Auth stackauth.mode, auth.dev_mode, auth.dev_token, auth.dev_token_file, auth.proxy.*, auth.transport.*, oauth.*
Secrets/storagesecrets.*, storage.*, workspace_storage.*
Identity/modemode, env, hub.hub_id, hub.gcp_project_id
Logginglog_level, log_format
CORShub.cors.*, broker.cors
Messaging/pluginsmessage_broker.*, plugins.*

Layer 1 — Operational (Postgres hub_settings table)

Section titled “Layer 1 — Operational (Postgres hub_settings table)”

Settings that can be changed at runtime and are shared across all replicas. Stored as section-per-row in the hub_settings table. In SQLite/workstation mode, these fall back to settings.yaml (unchanged behavior).

SectionContents
accessadmin_emails, user_access_mode, authorized_domains
lifecycleauto_suspend_stalled, soft_delete_retention, soft_delete_retain_files
maintenanceadmin_mode, maintenance_message (durable + cluster-wide)
telemetryFull telemetry.* subtree (enabled, cloud, hub, local, filter, resource)
agent_defaultsdefault_template, default_harness_config, default_max_turns, default_max_model_calls, default_max_duration, default_resources
endpointshub.public_url, image_registry
github_appapp_id, api_base_url, webhooks_enabled, installation_url, private_key_path
notificationsnotification_channels[]
(reserved) global_defaultsReserved for future hub-resource design — not implemented

In Postgres mode, the effective value for any Layer-1 key is resolved in this order (highest priority first):

  1. SCION_SERVER_* environment variable — node-local escape hatch
  2. hub_settings DB row — cluster-shared, set via admin API
  3. settings.yaml Layer-1 fields — fallback when key absent in DB
  4. Compiled defaults
  • First startup: the first replica to start seeds hub_settings from its local settings.yaml (Layer-1 keys only) under an advisory lock. Subsequent replicas see the seed marker and skip.
  • Seeding reads file values only — environment overrides are not baked into shared state.
  • DB wins: once a section is seeded/written to DB, the DB row fully owns that section. Omitted fields within the section fall to compiled defaults, not to the file.
  • Rollback safety: older builds ignore the hub_settings table entirely and read files — rolling back reverts to pre-change behavior.

Because env overrides on Layer-1 keys reintroduce per-node drift, the system warns administrators:

  • GET /api/v1/admin/server-config includes an env_overrides array listing which Layer-1 keys are overridden by env vars on the serving node.
  • A startup WARN log lists any overridden Layer-1 keys.
  • The admin UI renders a visible warning banner when env overrides are detected.

PUT partitioning: The request body is partitioned by the section registry. Layer-1 fields are written to DB sections. Layer-0 fields trigger a 422 rejection. Unclassified fields (e.g. runtimes, profiles) are ignored and reported in ignored_keys.

Revision CAS: The request body may include expected_revisions — a map of section name to expected revision number. On mismatch, the response is 409 Conflict with the conflicting sections and their current revisions. Omitted sections use last-writer-wins semantics. Sections are written in alphabetical order for deterministic partial-apply behavior.

Presence-aware clearing: The PUT handler distinguishes omitted fields (preserve current DB value) from explicitly-sent empty values ("", [], null) which clear the field. This enables clearing admin_emails, user_access_mode, authorized_domains, notification_channels, and public_url without sending every field.

Maintenance durability: PUT /api/v1/admin/maintenance writes to the maintenance section in DB, making admin/maintenance mode durable across restarts and propagated to all replicas. SCION_SERVER_ADMINMODE env var still force-enables per node for break-glass access.

Schema endpoint: GET /api/v1/admin/server-config/schema returns JSON-schema fragments and koanf key paths per section for UI form generation and CLI validation.