mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
5.6 KiB
5.6 KiB
Config Schema Versioning Guide
Overview
PicoClaw uses a schema versioning system for config.json to ensure smooth upgrades as the configuration format evolves.
Version History
Version 1
- Introduction: Initial version with version field support
- Changes: Added
versionfield to Config struct - Migration: No structural changes needed for existing configs
How It Works
Automatic Migration
When you load a config file:
- The system first reads the
versionfield from the JSON - Based on the detected version, it loads the appropriate config struct (
ConfigV0,ConfigV1, etc.) - If the loaded version is less than the latest, migrations are applied incrementally
- The version number is updated automatically
- The migrated config is automatically saved back to disk
Version Field
The version field in config.json indicates the schema version:
0or missing: Legacy config (no version field)1: Current version with versioning support
{
"version": 1,
"agents": {...},
...
}
Adding a New Migration
When making breaking changes to the config schema:
Step 1: Define the New Version Struct
Create a new struct for the new version if the structure changes significantly:
// ConfigV2 represents version 2 config structure
type ConfigV2 struct {
Version int `json:"version"`
Agents AgentsConfig `json:"agents"`
// ... other fields with new structure
}
Step 2: Update Current Config Version
const CurrentConfigVersion = 2 // Increment this
Step 3: Add a Loader Function
// loadConfigV2 loads a version 2 config
func loadConfigV2(data []byte) (*Config, error) {
cfg := DefaultConfig()
// Parse to ConfigV2 struct
var v2 ConfigV2
if err := json.Unmarshal(data, &v2); err != nil {
return nil, err
}
// Convert to current Config
cfg.Version = v2.Version
cfg.Agents = v2.Agents
// ... map other fields
return cfg, nil
}
Step 4: Add Migration Logic
// applyMigration applies a single migration step from fromVersion to toVersion
func applyMigration(cfg *Config, fromVersion, toVersion int) (*Config, error) {
switch toVersion {
case 1:
// Migration from version 0 to 1
return &Config{
Version: 1,
Agents: cfg.Agents,
// ... copy all fields
}, nil
case 2:
// Migration from version 1 to 2
// Example: Move or rename fields
migrated := *cfg
migrated.Version = 2
// Apply structural changes
if cfg.SomeOldField != "" {
migrated.SomeNewField = cfg.SomeOldField
}
return &migrated, nil
default:
return nil, fmt.Errorf("unsupported migration target version: %d", toVersion)
}
}
Step 5: Update LoadConfig Switch
func LoadConfig(path string) (*Config, error) {
// ... read file ...
switch versionInfo.Version {
case 0:
cfg, err = loadConfigV0(data)
case 1:
cfg, err = loadConfigV1(data)
case 2:
cfg, err = loadConfigV2(data)
default:
return nil, fmt.Errorf("unsupported config version: %d", versionInfo.Version)
}
// ... migrate and validate ...
}
Step 6: Test Your Migration
Create a test in config_migration_test.go:
func TestMigrateV1ToV2(t *testing.T) {
// Create a version 1 config
v1Config := Config{
Version: 1,
// ... set up test data
}
// Apply migration
migrated, err := applyMigration(&v1Config, 1, 2)
if err != nil {
t.Fatalf("Migration failed: %v", err)
}
// Verify version is updated
if migrated.Version != 2 {
t.Errorf("Expected version 2, got %d", migrated.Version)
}
// Verify data is preserved/transformed correctly
// ...
}
Migration Best Practices
- Version-Specific Structs: Define a separate struct for each version that has structural changes
- Backward Compatibility: Ensure old configs can still be loaded with their specific structs
- No Data Loss: Migrations should preserve all user settings
- Idempotent: Running the same migration multiple times should be safe
- Auto-Save: Migrated configs are automatically saved to update the user's file
- Test Thoroughly: Test with real user config files
- Update Defaults: Keep
defaults.goin sync with the latest schema
Example Migration
Scenario: Adding a new field with default value
Old config (version 1):
{
"version": 1,
"agents": {
"defaults": {
"max_tokens": 32768
}
}
}
Migration to version 2:
case 2:
migrated := *cfg
migrated.Version = 2
// Add new field with default value if not set
if migrated.Agents.Defaults.NewFeatureEnabled == false {
// Use default value
}
return &migrated, nil
New config (version 2):
{
"version": 2,
"agents": {
"defaults": {
"max_tokens": 32768,
"new_feature_enabled": false
}
}
}
Troubleshooting
Config Not Upgrading
- Check that
CurrentConfigVersionis incremented - Verify migration logic in
applyMigration()handles the target version - Ensure
migrateConfig()is called inLoadConfig()
Migration Errors
- Check error messages for specific migration failures
- Review migration logic for edge cases
- Ensure all required fields are properly initialized
- Verify the loader function for the source version
Data Loss After Migration
- Ensure all fields are copied during migration
- Check that the migration doesn't overwrite values with defaults unnecessarily
- Review the conversion logic in the loader functions