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.
Summary for stakeholders
Section titled “Summary for stakeholders”- 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.
Business analysis
Section titled “Business analysis”- Locked vs overridable — Every global setting has an
overridableflag. Whenfalse, the global value is always used regardless of org settings. - Tier quotas — Subscription tier definitions are stored as global settings under the
tiercategory. 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.
Architecture and integration
Section titled “Architecture and integration”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"| RESOLVEResolution algorithm
Section titled “Resolution algorithm”The effective value for any setting key is resolved by SettingsTenantCascadeMixin.resolve_effective_setting:
- Look up the key in
global_settings(Tier 2). If not found, returnNone. - If the global setting has
overridable = false, return the global value immediately. - Look up the key in
organization_settingsfor the current org (Tier 3). If not found, return the global value. - If the org setting has
overridable = falseor no instance config is provided, return the org value. - Look up the key in the instance’s
configJSONB (Tier 4). If found, return it; otherwise return the org value.
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)Tier 2 — Global settings
Section titled “Tier 2 — Global settings”Global settings are platform-wide key-value pairs stored in the global_settings table. Sys_admins manage them via GET / PATCH /api/admin/settings.
Data model
Section titled “Data model”| 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) |
Non-overridable keys
Section titled “Non-overridable keys”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).
Bootstrap defaults
Section titled “Bootstrap defaults”At startup, the lifespan handler bootstraps missing global settings with defaults from AppSettings:
otel.exporter→otlp_grpcaccess_token_ttl_seconds→ fromCADENCE_ACCESS_TOKEN_TTL_SECONDSenv varrefresh_token_ttl_seconds→ fromCADENCE_REFRESH_TOKEN_TTL_SECONDSenv var- All
oauth.*enabled flags →false - All
oauth.*credential keys → from env vars or empty string
Subscription tier definitions
Section titled “Subscription tier definitions”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: free → plus → pro → premium → business → enterprise (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.
API reference
Section titled “API reference”| 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 |
Tier 3 — Organization settings
Section titled “Tier 3 — Organization settings”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.
Data model
Section titled “Data model”| 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 |
Orchestrator defaults
Section titled “Orchestrator defaults”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.
API reference
Section titled “API reference”| 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 |
Tier 4 — Instance config
Section titled “Tier 4 — Instance config”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.
Settings change propagation
Section titled “Settings change propagation”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.
Limitations
Section titled “Limitations”- 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.