How to Manage Configuration Using pydantic BaseSettings in DimOS

DimOS centralizes all runtime configuration in a singleton GlobalConfig class that inherits from pydantic_settings.BaseSettings, merging environment variables, dynamic CLI flags, and programmatic overrides into a single typed source of truth.

DimOS (dimensionalOS/dimos) implements a robust configuration management pattern using pydantic BaseSettings. The recommended approach relies on a global singleton instance that lazy-loads values from .env files, automatically exposes every field as a CLI option, and supports runtime mutations without requiring application restarts.

The GlobalConfig Singleton Pattern

All configuration resides in dimos/core/global_config.py within the GlobalConfig class. This class subclasses pydantic_settings.BaseSettings and instantiates a singleton global_config = GlobalConfig() that is imported across the codebase.

Because BaseSettings parses environment variables lazily on first access, the singleton reflects the final merged configuration throughout the process tree. Modules receive consistent values whether they import global_config directly or accept it as a default parameter.


# dimos/core/global_config.py

from pydantic_settings import BaseSettings, SettingsConfigDict

class GlobalConfig(BaseSettings):
    robot_ip: str = "localhost"
    simulation: bool = False
    
    model_config = SettingsConfigDict(
        env_file=".env",
        env_file_encoding="utf-8",
        extra="ignore",
    )

global_config = GlobalConfig()

Configuration Sources and Precedence

DimOS merges configuration from three sources in the following priority order:

  1. Environment variables loaded from a .env file via SettingsConfigDict(env_file=".env")
  2. CLI flags generated dynamically from GlobalConfig.model_fields
  3. Programmatic overrides via global_config.update(...)

Environment Variables and .env Files

The model_config specifies env_file=".env" and extra="ignore", allowing users to define defaults in a repository-root .env file without code changes. Variable names are case-insensitive and support standard pydantic type coercion.


# .env

ROBOT_IP=192.168.1.42
SIMULATION=true
VIEWER=rerun-web

CLI Flags (Dynamic Typer Integration)

The CLI entry point in dimos/robot/cli/dimos.py inspects GlobalConfig.model_fields to generate a Typer option for every configuration field. Adding a new field to GlobalConfig automatically exposes it as a --kebab-case flag without additional boilerplate.

Programmatic Overrides

For runtime mutations, GlobalConfig.update(**kwargs) merges new values into the existing singleton instance. This allows the CLI or custom scripts to inject overrides after initialization.

Declaring New Configuration Fields

Add typed fields to GlobalConfig to extend configuration. Each field requires a Python type annotation (bool, int, float, Literal[...]) to enable IDE autocompletion and static analysis.


# dimos/core/global_config.py

from typing import Literal, TypeAlias

ViewerBackend: TypeAlias = Literal["rerun", "rerun-web", "rexconnect", "foxglove", "none"]

class GlobalConfig(BaseSettings):
    viewer: ViewerBackend = "rerun"
    navigation_timeout: int | None = None
    # Existing fields...

    
    model_config = SettingsConfigDict(
        env_file=".env",
        env_file_encoding="utf-8",
        extra="ignore",
    )

Any new field immediately becomes:

  • An environment variable (e.g., NAVIGATION_TIMEOUT=30)
  • A CLI flag (e.g., --navigation-timeout 30)

Runtime Configuration Updates

Use the update() method to modify configuration after initialization. This pattern supports CLI flag injection and dynamic reconfiguration without recreating the singleton.


# Custom script or CLI callback

from dimos.core.global_config import global_config

# Override specific values

global_config.update(viewer="foxglove", simulation=True)

print(global_config.viewer)      # → "foxglove"

print(global_config.simulation)  # → True

Modules should import global_config to access current values:


# dimos/perception/perceive_loop_skill.py

from dimos.core.global_config import GlobalConfig, global_config

class PerceiveLoopSkill:
    def __init__(self, cfg: GlobalConfig = global_config):
        self._cfg = cfg
        
    def start(self):
        if self._cfg.simulation:
            self._setup_simulated_camera()

CLI Integration

Running dimos --help reveals dynamically generated options mapping to every GlobalConfig field:

$ dimos --help
Options:
  --robot-ip TEXT                 Override robot_ip in GlobalConfig
  --simulation / --no-simulation  Override simulation in GlobalConfig
  --viewer TEXT                   Override viewer in GlobalConfig
  --navigation-timeout INTEGER    Override navigation_timeout in GlobalConfig

The dynamic callback in dimos/robot/cli/dimos.py constructs these options by iterating over GlobalConfig.model_fields, ensuring new configuration fields are instantly available to users.

Key Implementation Files

Summary

  • Use the singleton global_config imported from dimos.core.global_config for consistent configuration access across modules.
  • Declare typed fields on GlobalConfig to add new configuration options; pydantic handles validation and .env loading automatically.
  • Leverage update() for runtime mutations without rebuilding the configuration object.
  • Rely on automatic CLI generation—new fields appear as flags immediately without modifying dimos/robot/cli/dimos.py.
  • Set defaults in .env using the env_file loading built into SettingsConfigDict.

Frequently Asked Questions

How does GlobalConfig handle environment variables?

GlobalConfig uses SettingsConfigDict(env_file=".env", extra="ignore") to load variables from a .env file at the repository root. Pydantic matches environment variable names to field names case-insensitively and coerces values to the declared Python types (e.g., parsing "true" as a boolean).

Can I override configuration values at runtime?

Yes. Call global_config.update(**kwargs) to merge new values into the existing singleton instance. This method is used by the CLI to inject flag overrides after parsing arguments, and can be called from any module to change configuration dynamically.

How do I add a new configuration option to DimOS?

Add a typed field to the GlobalConfig class in dimos/core/global_config.py. For example, new_param: str = "default". The field immediately becomes available as an environment variable NEW_PARAM, a CLI flag --new-param, and a mutable attribute on the global_config singleton.

Where is the CLI option generation handled?

The CLI dynamically builds options in dimos/robot/cli/dimos.py by inspecting GlobalConfig.model_fields. The code creates a Typer.Option for each field, mapping configuration keys to command-line flags without requiring manual updates when fields are added or removed.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →