# Settings & Overrides

`RuntimeSettings` is how the outside world tells the runtime what to do.

It controls two different things:

- which plugins are enabled
- how specific service slots are configured or reprioritized

Both are live-reconcilable. You can hand the runtime a new `RuntimeSettings` at any time and it will converge, attaching and detaching as needed.

Read [Configuration](https://plugin-kit.saad-ardati.dev/concepts/configuration/) first if your question is "how does
a service read its config?" Stay here if your question is "how does the host
change what the runtime is doing?"

This is the piece that makes a real customization surface believable.

When a user opens a settings dialog, disables one feature, changes which
implementation should win a slot, and saves, you do not want a scavenger hunt
of manual teardown, rebuild, and stale singleton cleanup. You want one update
call and a runtime that converges.

## The shape

```dart
extractRegion(runtimeSettingsSource, 'runtime-settings-construct')
```

Two maps do the work.

| Field | Key format | Purpose |
|---|---|---|
| `plugins` | `pluginId` | enable or disable whole plugins |
| `services` | `pluginId:serviceId` or `*:serviceId` | override a specific service slot |

## Plugin-level enablement

Plugin enablement follows a fixed precedence.

1. `FeatureFlag.locked` wins immediately.
2. Explicit `PluginConfig.enabled` is respected next.
3. Otherwise the experimental heuristic kicks in: stable plugins on by default, experimental plugins off by default.

So:

- Stable plugins are enabled by default.
- Experimental plugins are disabled by default.
- Explicit settings beat both defaults.
- Locked plugins stay enabled regardless.

Dependency validation runs after that base decision, and it is transitive.

## `PluginConfig` has two fields, but mostly one matters today

`PluginConfig` contains `enabled` and `config`. In the current runtime, `enabled` drives lifecycle. The arbitrary `config` map is preserved by the settings model but is not automatically injected into plugins.

If you need plugin-wide configuration, read `PluginConfig.config` explicitly from your application layer, or (better) move the config onto a service that the plugin registers and let the registry do the injection.

## Service keys

Service override keys come in two forms.

| Key | Meaning |
|---|---|
| `pluginId:serviceId` | target one plugin's registration for that slot |
| `*:serviceId` | target the current winning registration for that slot |

```dart
'linter_suite:line_length_linter'  // specific plugin
'*:agent_service'                  // whoever currently wins
```

The `*:` form is the wildcard, or "winner-scoped," override.

## What `ServiceSettings` can change

`ServiceSettings` has three knobs.

| Field | Meaning |
|---|---|
| `config` | settings injected into `PluginService`s |
| `priority` | effective priority override for the targeted registration |
| `enabled` | on/off value carried in the settings model |

The common runtime uses are:

- plugin-level enablement through `PluginConfig`
- service-level enablement through `ServiceSettings.enabled` (disabled registrations are skipped, and resolution falls through to the next-highest-priority enabled one)
- service-level config injection through `ServiceSettings.config`
- service-level competition changes through `ServiceSettings.priority`

## Wildcard overrides

Wildcard overrides are one of the more useful pieces of the system.

If multiple plugins register the same slot, `*:serviceId` applies to whichever registration currently wins that slot.

```dart
extractRegion(editorSettingsSource, '08-editor-settings-settings')
```

You can target a slot by role instead of by plugin id. That matters when:

- the winning plugin may change at runtime
- the host UI configures a slot generically
- you want one setting to follow the winner
**Wildcards target the slot, not the plugin:** A `*:serviceId` entry applies to whichever registration currently wins that slot. If the winner changes (because another plugin's priority got bumped, or a service was disabled), the wildcard's config follows the new winner.

When both a wildcard and a plugin-specific entry target the same slot, the runtime merges them knob by knob:

- `enabled`: AND-merge. If either layer disables the slot, it stays disabled. A plugin-specific entry cannot force-enable a slot the wildcard disabled.
- `priority`: plugin-specific wins if set; otherwise the wildcard's priority applies to the current winner.
- `config`: plugin-specific config wins if non-empty; otherwise the wildcard's config fills in.

A concrete example: two plugins both register `model_router`. The wildcard supplies `temperature: 0.5`. A plugin-specific entry on `beta` only bumps priority. The result is that `beta` is now the winning registration, and it resolves with `temperature: 0.5` from the wildcard, because `beta`'s plugin-specific row has no `config` of its own for the merge to prefer.

```dart
extractRegion(runtimeSettingsSource, 'runtime-settings-wildcard-follows-winner')
```

If you want to fully replace the wildcard's config for one plugin, put a `config` map on the plugin-specific entry too; that is the case where plugin-specific beats wildcard.

![Plugin Kit Dialog Advanced tab showing the service registry inspector with namespaces, competing registrations, priority order, and the current winner picked out for each slot](https://plugin-kit.saad-ardati.dev/images/dialog/advanced_tab_dark.png)

*The Advanced tab is what these settings produce at runtime. Every slot, every registrant, every effective priority. The chip on the right of each row is the current winner for that slot.*

## Applying settings

There are three common paths.

### 1. Initialize with settings

```dart
extractRegion(pluginLifecycleSource, 'settings-init-with-settings')
```

### 2. Reconcile live settings

```dart
extractRegion(pluginLifecycleSource, 'settings-update-settings')
```

This is the full lifecycle-aware path. It reconciles the global scope and every active session.

This is the call behind things like:

- a plugin customization dialog
- a model/provider switcher
- workspace-scoped feature toggles
- "turn this experimental subsystem off right now" moments

If the user should be able to feel the change immediately, this is usually the
right path.

### 3. Update the snapshot without lifecycle work

```dart
extractRegion(pluginLifecycleSource, 'settings-snapshot-only')
```

This updates the runtime's stored settings and emits on `settingsStream`. It does not run reconciliation, and it does not inject new config into already-resolved services. Useful for optimistic UI, bookkeeping updates, and anything where you are confident the runtime does not need to converge. When unsure, reach for `updateSettings(...)` instead.

## Low-level scope-specific APIs

When you want finer control over which scope reconciles, the runtime exposes the two pieces separately:

- `updateGlobalSettings(...)`
- `updateSessionSettings(...)`

That split is useful when your application wants finer control over which scope reconciles when.

## Live-toggling session plugins

Toggling a session plugin via `updateSettings` runs the full lifecycle, same as global: `detach` on disable, `register` + `attach` on enable. Direct subscriptions from your plugin's `attach` are torn down on disable automatically, as long as you used the tracked helpers (`on`, `onRequest`, `bind`) instead of reaching into `context.bus`. See [Runtime](https://plugin-kit.saad-ardati.dev/concepts/runtime/) for the exact sequence.

## JSON support

`RuntimeSettings`, `PluginConfig`, and `ServiceSettings` all round-trip through JSON.

```dart
extractRegion(runtimeSettingsSource, 'runtime-settings-to-json')
```

That makes the settings model usable for persisted user preferences, workspace settings, admin-defined defaults, or UI-driven editors.

## Related reading

[Plugin Kit Dialog](https://plugin-kit.saad-ardati.dev/guides/plugin-kit-dialog/)
  [Runtime](https://plugin-kit.saad-ardati.dev/concepts/runtime/)
  [Plugin Services](https://plugin-kit.saad-ardati.dev/concepts/plugin-services/)
  [Capabilities](https://plugin-kit.saad-ardati.dev/concepts/capabilities/)