Skip to content

Settings cascade

Four-tier configuration resolution from system defaults to instance overrides.

Intended audience: Stakeholders, Business analysts, Solution architects, Developers, Testers

Learning outcomes by role

Stakeholders

  • Understand how platform-wide defaults reduce per-organization configuration burden.

Business analysts

  • Document which settings are locked at the platform level versus overridable per org.

Solution architects

  • Design configuration strategies that leverage the cascade for consistent behavior.

Developers

  • Read and write settings at each tier using the correct API and understand resolution order.

Testers

  • Verify cascade resolution with org overrides, non-overridable globals, and instance config.

Cadence uses a four-tier settings cascade to resolve configuration values. Platform admins set global defaults (Tier 2), org admins override them per organization (Tier 3), and orchestrator instances can carry their own config (Tier 4). The cascade ensures that a missing org setting falls back to the global value, and a non-overridable global setting cannot be changed by any org.

  • Single source of truth — Every configuration key resolves through one deterministic path. No ambiguity about which value wins.
  • Platform control — Sys_admins can lock critical settings (token TTLs, OAuth credentials) so orgs cannot override them.
  • Org autonomy — Overridable settings let each org tune defaults without platform intervention.
  • Locked vs overridable — Every global setting has an overridable flag. When false, the global value is always used regardless of org settings.
  • Tier quotas — Subscription tier definitions are stored as global settings under the tier category. Quotas are enforced at orchestrator and plugin creation time.
  • Orchestrator defaults — Orgs can set default LLM config, model name, max tokens, temperature, and timeout that pre-fill orchestrator creation forms.
flowchart TD
T1["Tier 1: System defaults<br/>(hardcoded in code)"] --> T2
T2["Tier 2: Global settings<br/>(global_settings table)"] --> T3
T3["Tier 3: Org settings<br/>(organization_settings table)"] --> T4
T4["Tier 4: Instance config<br/>(orchestrator_instances.config JSONB)"]
T2 -.->|"overridable=false<br/>value locked"| RESOLVE["Effective value"]
T3 -.->|"overridable=true<br/>org overrides"| RESOLVE
T4 -.->|"instance-level override"| RESOLVE

The effective value for any setting key is resolved by SettingsTenantCascadeMixin.resolve_effective_setting:

  1. Look up the key in global_settings (Tier 2). If not found, return None.
  2. If the global setting has overridable = false, return the global value immediately.
  3. Look up the key in organization_settings for the current org (Tier 3). If not found, return the global value.
  4. If the org setting has overridable = false or no instance config is provided, return the org value.
  5. Look up the key in the instance’s config JSONB (Tier 4). If found, return it; otherwise return the org value.
cadence/domain/settings/tenant_cascade.py
async def resolve_effective_setting(self, key, org_id, instance_config=None):
global_setting = await self.global_settings_repo.get_by_key(key)
if global_setting is None:
return None
if not global_setting.overridable:
return global_setting.value
org_setting = await self.org_settings_repo.get_by_key(org_id, key)
if org_setting is None:
return global_setting.value
if not org_setting.overridable or instance_config is None:
return org_setting.value
return instance_config.get(key, org_setting.value)

Global settings are platform-wide key-value pairs stored in the global_settings table. Sys_admins manage them via GET / PATCH /api/admin/settings.

| Field | Type | Notes | | ------------- | ------------- | -------------------------------------------------- | | key | string(255) | Unique setting key | | value | JSONB | Setting value (any JSON type) | | value_type | string(50) | string, integer, boolean, float, json | | description | text | Human-readable description | | overridable | boolean | If false, orgs cannot override this key | | category | string(50) | Optional grouping (e.g. tier, telemetry) |

These keys are always locked at the global level (NON_OVERRIDABLE_GLOBAL_KEYS in cadence/domain/settings/global_settings.py):

| Key | Purpose | | -------------------------------- | ----------------------------------------- | | access_token_ttl_seconds | JWT access token lifetime | | refresh_token_ttl_seconds | Refresh token lifetime | | oauth.google.enabled | Google social login toggle | | oauth.github.enabled | GitHub social login toggle | | oauth.oauth2.enabled | Generic OAuth2 social login toggle | | oauth.google.client_id | Google OAuth client ID | | oauth.google.client_secret | Google OAuth client secret | | oauth.github.client_id | GitHub OAuth client ID | | oauth.github.client_secret | GitHub OAuth client secret | | oauth.oauth2.client_id | Generic OAuth2 client ID | | oauth.oauth2.client_secret | Generic OAuth2 client secret | | oauth.oauth2.authorization_url | Generic OAuth2 authorization URL | | oauth.oauth2.token_url | Generic OAuth2 token URL | | oauth.oauth2.userinfo_url | Generic OAuth2 userinfo URL | | oauth.oauth2.scopes | Generic OAuth2 scopes (default openid email profile) |

When GlobalSettingsService.update is called on one of these keys with overridable=True, the service silently forces effective_overridable = False before the upsert (line 97 of global_settings.py).

At startup, the lifespan handler bootstraps missing global settings with defaults from AppSettings:

  • otel.exporterotlp_grpc
  • access_token_ttl_seconds → from CADENCE_ACCESS_TOKEN_TTL_SECONDS env var
  • refresh_token_ttl_seconds → from CADENCE_REFRESH_TOKEN_TTL_SECONDS env var
  • All oauth.* enabled flags → false
  • All oauth.* credential keys → from env vars or empty string

Tier quotas are stored as global settings with category = "tier" and keys like tier.free, tier.plus, tier.pro, etc. Each value is a JSON object matching the TierQuota Pydantic schema (cadence/api/shared/schemas.py:6-18):

{
"max_orchestrators": 3,
"max_central_points": 0,
"max_members": 25,
"max_messages_per_month": 10000,
"max_messages_per_day": 500,
"rate_limit_rpm": 60,
"rate_chat_limit_rpm": 30,
"rate_limit_burst": 100,
"max_llm_configs": 5,
"description": ""
}

max_central_points and description are optional (max_central_points defaults to 0); the rest are required integers. A limit of -1 means unlimited (the QuotaCheckMixin.enforce_quota method short-circuits on limit == -1); a missing tier row makes the quota check fail open (no enforcement). rate_limit_burst is defined in the schema but has no consumer in the codebase today.

The canonical sort order is: freepluspropremiumbusinessenterprise (defined as SUBSCRIPTION_TIER_SORT_ORDER in domain/settings/global_settings.py; same set in KNOWN_SUBSCRIPTION_TIERS in api/admin/platform.py). Unknown tier names sort to the end.

| Method | Path | Permission | Description | | ------- | --------------------------------- | -------------------- | ------------------------------ | | GET | /api/admin/settings | cadence:system:admin | List all global settings | | PATCH | /api/admin/settings/{key} | cadence:system:admin | Update a global setting | | GET | /api/admin/tiers | cadence:system:admin | List tier definitions | | PATCH | /api/admin/tiers/{tier_name} | cadence:system:admin | Update tier quota limits |

Org settings are scoped to a single organization and stored in the organization_settings table. Org admins manage them via POST / GET /api/orgs/{org_id}/settings.

| Field | Type | Notes | | ------------- | ------------- | -------------------------------------------------- | | org_id | UUID7 | Owning organization | | key | string(255) | Setting key (unique per org) | | value | JSONB | Setting value | | overridable | boolean | Whether instance config can override this key |

Orgs can set default values that pre-fill orchestrator creation. These are stored as org settings with well-known keys (ORCHESTRATOR_DEFAULT_KEYS in domain/settings/org_settings.py:14-20):

| Key | Type | Purpose | | ----------------------- | ------- | ------------------------------------------ | | default_llm_config_id | string | Default LLM config for new orchestrators | | default_model_name | string | Default model name | | default_max_tokens | integer | Default max tokens for model calls | | default_temperature | float | Default temperature for model calls | | default_timeout | integer | Default per-node timeout |

OrgSettingsService.set_orchestrator_defaults writes all five keys in a single transaction (each overridable=True). A sixth setting, conversation_tone, is not part of the atomic write — it is read and returned alongside the defaults by the GET /api/orgs/{org_id}/orchestrator-defaults endpoint so the UI can render the tone control next to the other defaults.

| Method | Path | Permission | Description | | ------ | -------------------------------------------- | -------------------------------- | ------------------------------- | | POST | /api/orgs/{org_id}/settings | cadence:org:settings:write | Create or update an org setting | | GET | /api/orgs/{org_id}/settings | cadence:org:settings:read | List all org settings | | GET | /api/orgs/{org_id}/orchestrator-defaults | cadence:org:settings:read | Get orchestrator defaults | | PUT | /api/orgs/{org_id}/orchestrator-defaults | cadence:org:settings:write | Set orchestrator defaults |

Instance-level configuration lives in the config JSONB column of orchestrator_instances. It is set at creation time and updated via PATCH /api/orgs/{org_id}/orchestrators/{instance_id}/config.

Instance config can override org and global settings when both the global and org setting have overridable = true. The instance config is also where mode_config, active_plugins, and default_llm_config_id are stored.

See Orchestration backends for the full config object reference.

When settings change, the platform broadcasts events via RabbitMQ to reload affected orchestrator instances:

| Event | Routing key | Effect | | ------------------------- | ---------------------------- | --------------------------------------------------- | | Global settings changed | settings.global_changed | All hot-tier instances across all orgs are reloaded | | Org settings changed | settings.org_changed | Hot-tier instances in the affected org are reloaded | | Telemetry config changed | settings.telemetry_changed | OTel providers are reloaded without process restart |

See Event system for the full event topology.

  • No per-user settings — The cascade stops at org and instance level. Individual users cannot override settings.
  • No setting inheritance between orgs — Each org’s settings are independent; there is no org hierarchy.
  • Bootstrap is one-time — Missing global settings are bootstrapped at startup. Deleting a bootstrapped key requires manual re-insertion.