Skip to content

Configuration Internals

In a nutshell

This page covers how ovos-config is built under the hood: the Python classes backing each config layer, the in-memory patch overlay, the bus events that keep processes in sync, environment-variable overrides, XDG path helpers, and the package layout. Most users never need this. If you only want to change a setting, see Configuration Management. For the settings catalog, see the Configuration Reference.


Patch Mechanism

The __patch overlay is an in-memory dict merged on top of all file-backed layers. It is used for temporary overrides that should not be persisted to disk. Writing a key on the singleton goes into this patch:

config = Configuration()
config["lang"] = "fr-fr"   # stored in the in-memory __patch layer

The patch is applied and cleared via Configuration.patch(message) and Configuration.patch_clear(message). Both are @staticmethods that take a bus Message (they read message.data["config"]), not a plain dict. In practice they are driven by the bus handlers below rather than called directly.


Bus Integration

Configuration.set_config_update_handlers(bus) registers the following listeners:

Bus Event Handler Action
configuration.updated Configuration.updated Reload the default, distribution, system, assistant, and XDG layers
configuration.patch Configuration.patch Apply data["config"] as an in-memory patch
configuration.patch.clear Configuration.patch_clear Clear the in-memory patch
configuration.cache.clear Configuration.clear_cache Drop the memoized merged config stack, then re-emit configuration.updated

configuration.updated reloads every layer: reload() re-reads default, distribution, system, assistant, and all the XDG configs from disk (the read-only layers included — their reload() does a real, mtime-checked re-read). A script that edits any layer's file and then emits this event sees the change immediately, without waiting for the file watcher.

Configuration.set_config_watcher() uses ovos-utils' FileWatcher (watchdog) to monitor config files on disk, and reloads when a watched file changes.

The watch list is built once, from the files that exist at that moment ([p for p in paths if isfile(p)]), and is never rebuilt. A config file created after the service started is therefore not watched at all. On a fresh install with no ~/.config/mycroft/mycroft.conf, creating one later needs a restart before live reload applies to it.


Config Models

Each layer is a LocalConf instance, a file-backed dict subclass.

Module: ovos_config.models

Class Path Notes
LocalConf any path Base class; supports JSON and YAML
ReadOnlyConfig any path Raises PermissionError on mutation (unless allow_overwrite=True)
DefaultConfig bundled mycroft.conf ReadOnlyConfig
DistributionConfig /usr/share/mycroft/mycroft.conf ReadOnlyConfig
SystemConfig /etc/mycroft/mycroft.conf ReadOnlyConfig
AssistantConfig ~/.config/mycroft/runtime.conf OVOS's own runtime-write layer (LocalConf)
UserConfig ~/.config/mycroft/mycroft.conf Primary user layer (LocalConf)

MycroftDefaultConfig/OvosDistributionConfig/MycroftSystemConfig/MycroftUserConfig are deprecated aliases of the classes above, kept for backward compatibility and slated for removal. MycroftXDGConfig is a deprecated alias of UserConfig. RemoteConf (the old backend / paired-server cache) is deprecated without replacement — constructing it emits a DeprecationWarning, and Configuration no longer instantiates it at all.

from ovos_config.models import LocalConf, MycroftUserConfig

# Direct access to a layer
user = MycroftUserConfig()
user["tts"] = {"module": "ovos-tts-plugin-phoonnx"}
user.store()   # write to disk

LocalConf Key Methods

Method Description
load_local(path=None) Read from path (or self.path) and merge into self
store(path=None) Write current contents to disk
merge(conf) Deep-merge another dict into self
reload() Re-read from disk if the file changed since last load

LocalConf uses a single shared class-level NamedLock("ovos_config") to coordinate concurrent reads and writes across all instances.

Merge Semantics

  • Scalar values: higher-priority layer wins

  • Dict values: recursively merged

  • List values: higher-priority layer replaces (no deduplication)


Accessing Individual Layers

The individual layers are class attributes on Configuration (not per-instance):

Configuration.default       # DefaultConfig
Configuration.distribution  # DistributionConfig
Configuration.system        # SystemConfig
Configuration.assistant     # AssistantConfig — OVOS's own runtime-write layer
Configuration.xdg_configs   # list[LocalConf] — the user/XDG layer(s)

There is no Configuration.remote — the old Mycroft Home / mycroft.ai backend layer was removed as a breaking change (ovos-config 5a7d1a3, #194, first tag 3.0.0a1). Accessing Configuration.remote now raises AttributeError, both from the class and from an instance. RemoteConf stays importable as a deprecated class that warns on construction.

There is no .user attribute. The editable user config is the last entry in Configuration.xdg_configs. The list runs from lowest to highest precedence — system-wide XDG dirs such as /etc/xdg/mycroft/mycroft.conf first, then $XDG_CONFIG_HOME — and the merge runs left to right, so the last entry wins: the user's own file. See Config Layer Stack. To write the user file directly, use MycroftUserConfig() (see Config Models above). Or call update_mycroft_config() to merge a change and emit the configuration.patch bus notification in one step.


Environment Variable Overrides

Module: ovos_config.meta

Variable Default Effect
OVOS_CONFIG_BASE_FOLDER "mycroft" XDG subdirectory name for all config/data/cache paths
OVOS_CONFIG_FILENAME "mycroft.conf" Config filename inside the XDG config directory
OVOS_DEFAULT_CONFIG package mycroft.conf Path to the bundled default config

The framework reads these when ovos_config.config is first imported: the Configuration singleton resolves its layer paths (default, distribution, system, xdg_configs) as class attributes at that moment, and nothing later rebuilds them — Configuration.reset() and reload() re-read the existing file paths, they do not recompute the path list.

Python setters exist (set_xdg_base, set_config_filename, set_default_config in ovos_config.meta), but they only affect code that later calls the dynamic path helpers (get_xdg_config_save_path(), find_user_config(), get_xdg_config_dirs()). They cannot redirect the Configuration singleton itself: importing ovos_config.meta executes the package __init__, which imports ovos_config.config first, so by the time a setter is even importable the singleton's paths are already frozen. Setting the environment variables before the process starts is the only mechanism that changes where Configuration looks:

export OVOS_CONFIG_BASE_FOLDER=my_distro          # ~/.config/my_distro/
export OVOS_CONFIG_FILENAME=mycroft.conf          # filename inside the XDG dir
export OVOS_DEFAULT_CONFIG=/opt/my_distro/default.conf

Distribution Overrides

Distributions can change the default XDG base folder or config filename by setting environment variables:

  • OVOS_CONFIG_BASE_FOLDER: changes ~/.config/mycroft/ to ~/.config/custom/ (default: mycroft).

  • OVOS_CONFIG_FILENAME: changes mycroft.conf to custom.json (default: mycroft.conf).

  • OVOS_DEFAULT_CONFIG: provides a full path to a custom default configuration file.


XDG Path Helpers

Module: ovos_config.locations. Helper functions such as get_xdg_config_save_path() and find_user_config() compute the paths above. See Locations for the full reference and usage examples.


Package Layout

ovos_config/
├── config.py       # Configuration singleton
├── models.py       # LocalConf, ReadOnlyConfig, layer classes
├── locations.py    # XDG path helpers and constants
├── meta.py         # Env var overrides (XDG base, filename, default config)
├── locale.py       # Language/timezone helpers
├── utils.py        # init_module_config(), deprecated FileWatcher re-exports
└── __main__.py     # ovos-config CLI (show / get / set / telemetry / autoconfigure)

Entry Points

ovos-config registers no plugin entry points of its own. Every other OVOS component consumes it as a dependency.

The CLI is registered in pyproject.toml:

[project.scripts]
ovos-config = "ovos_config.__main__:config"

Source code: OpenVoiceOS/ovos-config.


Read next: Configuration Management Related: Configuration Reference · Locations · All Configuration Keys