Configuration Internals (defaults and overrides)
This page is for contributors: it explains where Pipelex defaults live, how configuration is merged at runtime, and where to change the behavior in code.
For the user-facing configuration guide, see Configuration Overview.
Mental model (for contributors)
Pipelex configuration is merged in layers:
- Shipped defaults: maintained by the Pipelex project and used as the baseline by the installed package.
- Global config (
~/.pipelex/): machine-wide settings for a developer, applied to every project on the machine. - Project config (
{project_root}/.pipelex/): per-project settings, edited by teams using Pipelex. - Override files at each level (
pipelex_local.toml,pipelex_{environment}.toml,pipelex_{run_mode}.toml,pipelex_override.toml,pipelex_temporary_override.toml): optional, typically gitignored, used for personal or ephemeral tweaks.
The key idea: global personal preferences layer under project-specific settings — both are loaded, and project values win on collisions. Nothing in ~/.pipelex/ is shadowed by the mere existence of a project .pipelex/.
Where defaults live (Pipelex repository)
- Default values (baseline):
pipelex/pipelex.toml, shipped inside the package - User-facing templates copied into
.pipelex/by the init flow:pipelex/kit/configs/…
Merge order (runtime)
The configuration loading/merging behavior is implemented in pipelex/system/configuration/config_loader.py. Files are deep-merged via load_toml_from_path_and_merge_with_overrides; the later a file appears in the load list, the higher its precedence per leaf key.
Load order:
- Package defaults —
pipelex/pipelex.tomlshipped with the installed package. - Global base —
~/.pipelex/pipelex.toml. - Global override sequence — from
~/.pipelex/, in this order:pipelex_local.tomlpipelex_{environment}.tomlpipelex_{run_mode}.toml(see unit-test special case below)pipelex_override.tomlpipelex_temporary_override.toml
- Project base —
{project_root}/.pipelex/pipelex.toml, if a project.pipelex/exists and is distinct from~/.pipelex/. - Project override sequence — same five files as step 3, read from
{project_root}/.pipelex/, if the project dir is distinct from the global dir. - Programmatic
extra_overridespassed intoConfigLoader.load_config()(if any).
Unit-test special case
When runtime_manager.is_unit_testing is true, the pipelex_{run_mode}.toml entry is sourced exclusively from ./tests/pipelex_{run_mode}.toml (e.g. tests/pipelex_unit_test.toml). Global and project run_mode files are not loaded, keeping test runs hermetic from machine-wide overrides.
The inference documents
.pipelex/inference/backends.toml and .pipelex/inference/routing_profiles.toml are not part of the pipelex.toml merge. Each is its own document with a shorter sequence, built by ConfigLoader.backends_file_paths() / routing_profiles_file_paths() and read by load_toml_from_base_and_overrides:
- The base — the project's file if the project
.pipelex/inference/has one, otherwise the global one. One file, winner takes all; a personal override cannot stand in for it. - Global override —
~/.pipelex/inference/backends_override.toml(respectivelyrouting_profiles_override.toml), if present. - Project override — the same file under the project's
.pipelex/inference/, if present and distinct from the global one.
The order differs from pipelex.toml's on purpose: there the project base beats the global override, here the global override beats the project base. A global inference override exists so one machine-wide choice ("run on this backend") reaches every project, and every project carries a tracked backends.toml, so a project base that won would defeat the file. The project override still wins over the global one. With an explicit config_dir (the doctor's --global, an init targeting one directory), the sequence is that directory's base and its own override, nothing layered.
Where to change things in code
- Config merge logic:
pipelex/system/configuration/config_loader.py - Override file sequences:
ConfigLoader._override_files_for_dirandConfigLoader._plugin_override_files_for_dirin that same file
Contributor guidelines for config changes
- If you change defaults, update
pipelex/pipelex.toml(and consider whether templates inpipelex/kit/configs/…should also be updated). - If you change templates, keep them user-focused and stable; avoid adding internal-only details.
- If you change merge order or override semantics, treat it as a potentially breaking change and document it (changelog + migration notes if needed).
- If you change the shape of a configuration model — renaming, moving or removing a key, or dropping an enumerated value —
make checkwill stop you until the change is recorded in that surface's migration ledger. Adding a key is not a shape change and needs nothing, provided the key has a default: the defaults layer is what an old file falls back on, and a required key with no default breaks every existing file. The gate ismake check-migration-schemas(aliascmig) and its regenerator ismake up-migration-schemas(aliasumig). The contract, including what a ledger may contain and what is guaranteed to a user whose files are migrated, is in The Migration Ledger. - The repository's own
.pipelex/dogfoods the templates, andmake check-config-sync(aliasccs) holds the two directories to the same contents;make up-kit-configs(aliasukc) mirrors.pipelex/back ontopipelex/kit/configs/. Both look past the same exclusions, declared together inpipelex/cli/dev_cli/config_sync_exclusions.py, so the check can never go red on something the sync would not fix. Three kinds of file are excluded: the per-developer overrides and the templates that intentionally differ between the two sides (CONFIG_SYNC_EXCLUDED_FILES, each with its reason beside it); the.gitignorepipelex writes into a configuration directory it owns, which is a runtime artifact of that directory and has no kit counterpart; and, by glob, the timestamped copies a migration leaves behind (CONFIG_SYNC_EXCLUDED_PATTERNS, built from the same infixes and stamp shapepipelex/migration/backup.pywrites, so a rename there cannot leave a pattern matching nothing). That last one matters because migrating in a checkout is an ordinary thing to do: the run's own.gitignorealready keeps those copies out ofgit status, and a sync check going red on them would put the dirtied-repository problem straight back, one gate over. The declaration sits under the dev CLI rather than beside the kit paths for a reason worth knowing before you move it: deriving the patterns from the namer means importingpipelex.migration, which reaches the pipeline layer, andpipelex/kit/paths.pyis inside the kernel boot closure that hub layering keeps free of exactly that. When you add a file to.pipelex/that genuinely has no kit counterpart, add it to one of those two sets with a comment saying why — do not delete it to get a green check.
Test Profile Configuration
The test profile system controls which AI models are included in parametrized tests. This is separate from the main Pipelex configuration.
Files
| File | Purpose | Tracked |
|---|---|---|
.pipelex-dev/test_profiles.toml |
Base profiles and model collections | Yes |
.pipelex-dev/test_profiles_override.toml |
Local customizations | No (gitignored) |
How it works
- Collections define reusable lists of models organized by provider
- Profiles reference collections or specify models directly
- The preprocessing command resolves references and generates fixture files
Key code locations
- Profile loading & merging:
pipelex/cli/dev_cli/commands/preprocess_test_models_cmd.py - Generated fixtures:
tests/integration/pipelex/fixtures/_generated_model_sets.py
Common tasks
# Regenerate fixtures with default (dev) profile
make regenerate-test-models
# Use a specific profile
make regenerate-test-models TEST_PROFILE=full
For full documentation, see Test Profile Configuration.