Nanobot Config Schema Versioning: How HKUDS/nanobot Manages Configuration Evolution

Nanobot config schema versioning relies on an implicit, schema-driven approach where the Pydantic-based Config model automatically migrates missing fields via defaults and validates deprecated names without requiring an explicit version field.

The HKUDS/nanobot project stores all runtime settings in a centralized configuration system that eliminates traditional version numbers. Instead of maintaining a schema_version field, Nanobot config schema versioning uses the Python type definitions themselves as the source of truth. When the application starts, it parses the user's ~/.nanobot/config.json against the Config model defined in nanobot/config/schema.py, allowing the structure to evolve while preserving backward compatibility.

Understanding Nanobot Config Schema Versioning

The Root Configuration Model

All configuration logic originates from the Config class in nanobot/config/schema.py. This root Pydantic model aggregates specialized sections—AgentsConfig, ChannelsConfig, ProvidersConfig, ToolsConfig, and others—each implemented as typed classes with explicit defaults. The Config class also applies environment overrides using the prefix NANOBOT_, ensuring that environment variables take precedence over file-based settings.

Implicit Version Management

Rather than storing a version identifier in JSON, Nanobot determines compatibility by overlaying user configurations atop default values defined in code. When load_config() from nanobot/config/loader.py executes, it resolves the file path via nanobot/config/paths.py and parses the JSON content. Pydantic automatically populates any missing fields from the class definitions, effectively performing a zero-downtime migration whenever new releases add parameters.

Runtime Configuration Resolution

Loading and Persisting Settings

The configuration lifecycle begins with the load_config() function, which returns a fully instantiated Config object. Runtime modifications persist through the save() method, which writes updates back to the user's config file.

from nanobot.config.loader import load_config

# Load defaults + user file + environment overrides

cfg = load_config()  # Returns Config instance

# Update provider credentials and persist

cfg.providers.openai.api_key = "sk-****"
cfg.save()  # Writes to ~/.nanobot/config.json

Model Preset Resolution

Model-specific settings reside in ModelPresetConfig instances. The Config.resolve_preset() method determines the active preset based on agents.defaults.model_preset, falling back to the implicit default preset defined in Config.resolve_default_preset. This resolution mechanism allows new preset fields to be added to the schema without breaking existing installations.


# Access the active model preset (handles versioned defaults automatically)

preset = cfg.resolve_preset()  # Returns ModelPresetConfig

# Change preset at runtime

cfg.agents.defaults.model_preset = "my-high-quality"
new_preset = cfg.resolve_preset()  # Returns updated configuration

Dynamic Provider Matching

The Config._match_provider method contains the logic that maps model names to provider configurations. When calling cfg.get_provider(model="gpt-4o-mini"), the system respects newly added providers while maintaining compatibility with older model strings. This centralization ensures the schema can grow to support new LLM providers without requiring users to manually update their configuration structure.

Migration and Validation Mechanisms

Automatic Field Migration

When new releases introduce fields—for example, a new provider in ProvidersConfig or additional flags in ToolsConfig—the corresponding Pydantic classes receive default values. Existing user configurations remain compatible because the loader overlays the user file on top of these code-defined defaults. This schema-driven approach ensures that missing fields are automatically populated according to the current codebase's expectations.

Deprecated Name Validation

The validator Config._validate_model_preset actively scans for reserved or deprecated identifiers, such as the preset name "default". When the loader detects an incompatibility, it raises a descriptive ValueError that guides the user to update their configuration to match the current schema requirements, preventing silent failures from outdated settings.

Summary

  • Nanobot config schema versioning eliminates explicit version fields by using Pydantic model definitions as the single source of truth.
  • The Config class in nanobot/config/schema.py aggregates all sections, with load_config() in nanobot/config/loader.py handling the merge of user settings, defaults, and environment variables prefixed with NANOBOT_.
  • Automatic migration occurs when new fields are added, populated from class defaults in AgentsConfig, ProvidersConfig, and other section models.
  • Runtime preset resolution via resolve_preset() and provider matching through _match_provider allow the schema to evolve without user intervention.
  • Deprecated configurations trigger clear validation errors through _validate_model_preset, ensuring users receive actionable feedback to update their ~/.nanobot/config.json.

Frequently Asked Questions

Does Nanobot store a version number in the config file?

No. The HKUDS/nanobot repository does not include an explicit schema version field in ~/.nanobot/config.json. Instead, versioning is implicit: the Pydantic model definitions in nanobot/config/schema.py act as the schema version, with default values ensuring backward compatibility when new fields are introduced.

How does Nanobot handle missing configuration fields after an update?

When a new release adds fields to Config or its sub-models like ToolsConfig, the load_config() function automatically applies the defaults defined in the Pydantic classes. This schema-driven approach means existing configurations seamlessly inherit new settings without requiring manual migration scripts.

What happens if I use a deprecated preset name?

The Config._validate_model_preset validator checks for reserved names such as "default". If your configuration contains deprecated identifiers, Nanobot raises a descriptive ValueError during startup, directing you to update the agents.defaults.model_preset value to comply with the current schema.

Can I change the active model preset at runtime?

Yes. You can modify cfg.agents.defaults.model_preset on the loaded Config instance and call cfg.resolve_preset() to retrieve the updated ModelPresetConfig. Changes persist when you call cfg.save(), which writes the updated configuration back to the JSON file.

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 →