Configuration Model
AIControls configuration has two tiers with exactly one source of truth each: Helm configures the deployment; the app configures itself. This page explains the split, how a fresh install is seeded, and what happens on upgrade.
This page describes values.yaml/config.yaml behavior for Self-Hosted Kubernetes deployments. If you're on the managed SaaS offering, every setting on this page is UI/API-managed already — there is no config.yaml to reason about.
Two tiers, not two sources of precedence
Every setting lives in exactly one place. There is no "which one wins" logic to reason about — only which tier a given setting belongs to:
| Tier | Lives in | Examples |
|---|---|---|
| Deployment settings | values.yaml / config.yaml only, never the database | Listen/admin ports, storage backend and connection string, TLS/CA file paths, replica count, audit sink wiring |
| App settings | The app database only, managed via Settings in the Admin UI (or the admin API) | Rate limits, budgets, LLM upstreams, OIDC providers, passthrough mode, allowed domains/emails, PAT policy, policy.failOpen, anomaly and session thresholds, content-safety patterns |
The rule of thumb: if changing a setting requires re-binding a socket, re-mounting a file, or re-pointing at a different database, it's a deployment setting. Everything else is an app setting.
Before this split, every app setting saved once through the UI would shadow values.yaml on every subsequent restart or upgrade — with no visible indicator that it was happening. That precedence problem is gone: app settings are no longer read from config.yaml at all after the first boot (see below), so there's nothing left to shadow.
Seeding on first boot
values.yaml/config.yaml still accepts every app-tier field — it just plays a narrower role than it used to. The first time AIControls starts against a fresh database, it copies any app-tier values it finds in config.yaml into the database, one time, for whichever settings the database doesn't already have a value for.
After that first-boot seed, config.yaml is not consulted again for that setting. Managing it going forward means using Settings in the Admin UI (or the equivalent admin API endpoint) — editing values.yaml and running helm upgrade has no further effect on it.
This preserves day-0 automation for GitOps/Helm-driven installs — you can still set an initial rate limit or policy.failOpen: false in values.yaml before the first helm install — without bringing back the old shadowing problem for every install afterward.
What happens on upgrade
- No behavior change for settings you've already configured. If a setting has a value in the database — whether you set it in the UI or it was seeded on an earlier first boot — that value keeps winning. An in-place upgrade never silently reverts a setting you've configured.
values.yamlonly fills gaps. On each boot, AIControls seeds a setting fromconfig.yamlonly if the database has no value for it yet. Settings that already have a database value are left untouched.- Chart-rendered app settings are deprecated. A future release removes app-tier fields from the Helm chart's
config.yamltemplate entirely, replaced by a first-boot-onlyappSettings.bootstrapblock for zero-touch installs. Start managing rate limits, budgets, OIDC, and the other app settings listed above from Settings now, if you aren't already.
If values.yaml sets an app-tier field to a value that differs from what's already in the database, AIControls keeps the database value (per "no behavior change" above) and logs a warning identifying the mismatch — see below for where the corresponding in-app notification appears.
Configuration drift notifications
Every app setting change, and every case where config.yaml disagrees with a value already in the database, raises a notification so admins don't have to go looking for it:
| Event | Fires when | Where you'll see it |
|---|---|---|
| Settings changed | Any app setting is saved through the UI or admin API | In-app (the bell icon) |
| Policy fail-open changed | policy.failOpen specifically changes value | In-app, email, and Slack — deliberately more prominent, since this setting silently determines whether a policy-evaluation error blocks or allows the request |
| Config value ignored | On boot, config.yaml sets one or more app-tier settings that already have a different value in the database | In-app, listing every setting key affected so you can reconcile values.yaml with what's actually in effect |
Configure who receives these and through which channels from Settings → Notifications → Routing, the same as any other notification type.
See also
- Self-Hosted Kubernetes — Helm install and upgrade steps
- Configure Detection Thresholds — an example of an app setting managed entirely from the UI
- Set a Budget — another app setting group