Understanding the L1-L4 Layered Configuration System in AIOS Pro
AIOS Pro resolves runtime configuration through a deterministic five-level hierarchy where L1 (Framework) → L2 (Project) → Pro → L3 (App) → L4 (Local), with each successive layer overwriting the previous according to deep-merge rules defined in ADR-PRO-002.
The L1-L4 layered configuration system in AIOS Pro provides a structured approach to managing environment-specific settings while maintaining team-wide consistency. Implemented in the SynkraAI/aios-core repository, this architecture ensures that global defaults remain stable, shared policies are enforceable, and individual developers can safely customize their environments without polluting version control.
The Five-Level Configuration Hierarchy
AIOS Pro evaluates configuration through five distinct layers, though only the first four (L1-L4) are present in every project. Each layer serves a specific purpose and is merged in a deterministic order, with higher layers overwriting lower ones based on the merge rules in ADR-PRO-002.
| Layer | File (relative to project root) | Purpose | Typical contents |
|---|---|---|---|
| L1 – Framework | .aios-core/framework-config.yaml |
Read-only defaults shipped with the AIOS package | Global metadata, default performance limits, built-in resource locations |
| L2 – Project | .aios-core/project-config.yaml |
Team-wide settings that live in source control | Project name, shared resource quotas, CI/CD integration points |
| L3 – App (optional) | aios-app.config.yaml (inside an app folder) |
Per-app customisation – only loaded when options.appDir is supplied |
UI themes, feature flags that differ between micro-apps |
| L4 – Local | .aios-core/local-config.yaml (git-ignored) |
Developer-specific, machine-specific overrides (secrets, IDE preferences, local tooling) | ${ENV_VAR} interpolation allowed, secret tokens, personal IDE selections |
How Configuration Inheritance Works in AIOS Pro
The inheritance mechanism in AIOS Pro is implemented in .aios-core/core/config/config-resolver.js and follows a strict loading and merging protocol.
Loading and Resolution Order
The entry point resolveConfig(projectRoot, options) first checks whether the repository is in legacy mode (detecting core-config.yaml). If not, it delegates to loadLayeredConfig() to orchestrate the hierarchy.
The merge sequence follows this exact order:
// Pseudo-code from loadLayeredConfig() in config-resolver.js
config = deepMerge(L1, L2) // L2 overrides L1
config = deepMerge(config, Pro) // Pro layer overrides previous
config = deepMerge(config, L3) // optional app overrides
config = deepMerge(config, L4) // L4 overrides everything above it
config = deepMerge(config, L5) // user-wide overrides (outside L1-L4 scope)
Deep Merge Strategy and Rules
The actual merging logic resides in .aios-core/core/config/merge-utils.js and applies different strategies based on data type:
- Scalars (string, number, boolean) – Last-wins semantics apply. The value from the higher layer completely replaces the lower layer value.
- Objects – Deep-merged recursively. Nested properties from higher layers override specific keys in lower layer objects without discarding sibling properties.
- Arrays – Replaced entirely by the higher-level array, unless the configuration key ends with the special suffix
+append, which triggers concatenation instead of replacement. nullvalues – When a higher layer explicitly sets a key tonull, that key is deleted from the resulting configuration.
Validation and Environment Handling
After each layer loads, the resolver enforces strict validation rules:
- Environment Pattern Linting – The
lintEnvPatternsfunction (inenv-interpolator.js) ensures that only L4 (Local) configuration files may contain${VAR}placeholders. Attempting to use environment variable interpolation in L1, L2, or L3 triggers a validation warning. - JSON-Schema Validation – Each layer has its own schema file (
framework-config.schema.json,project-config.schema.json,local-config.schema.json,user-config.schema.json). ThevalidateConfigfunction checks layer-specific constraints and collects warnings inresult.warnings, accessible via the CLI commandaios config validate --level L4.
When options.debug === true, loadLayeredConfig() builds a sources map that records which specific layer (L1-L4) supplied each configuration key, enabling precise provenance tracking via aios config show --debug.
Practical Implementation Example
To resolve a project's effective configuration with full provenance tracking:
const { resolveConfig } = require('.aios-core/core/config/config-resolver');
// Resolve a project's final config (debug mode shows provenance)
const { config, sources, warnings } = resolveConfig('/path/to/my-project', {
debug: true,
appDir: '/path/to/my-project/apps/dashboard',
});
console.log('Effective config →', config);
console.log('Where each key came from →', sources);
if (warnings.length) console.warn('Config warnings:', warnings);
Step-by-step override example:
-
L1 (Framework) supplies default
metadata.framework_name = "AIOS‑FullStack"andperformance_defaults.max_concurrent_operations = 4. -
L2 (Project) overrides the performance setting:
performance_defaults.max_concurrent_operations = 8. -
L4 (Local) provides developer-specific overrides:
performance_defaults: max_concurrent_operations: 16 # L4 overrides L2 ide: selected: - vscode - claude-code -
Final merged result:
max_concurrent_operations = 16(from L4),metadata.framework_name = "AIOS‑FullStack"(from L1), and the IDE selection from L4, with all other L1/L2 values preserved.
Key Source Files for the L1-L4 System
| File | Role | Link |
|---|---|---|
config-resolver.js |
Detects legacy vs layered mode, orchestrates loading, validation, and source tracking | 🔗 |
merge-utils.js |
Implements the deep-merge algorithm used for inheritance | 🔗 |
config-cache.js |
TTL-based cache for already-resolved configs (optimises repeated calls) | 🔗 |
env-interpolator.js |
Handles ${VAR} interpolation and linting of env patterns |
🔗 |
schemas/framework-config.schema.json |
JSON-Schema for L1 | 🔗 |
schemas/project-config.schema.json |
JSON-Schema for L2 | 🔗 |
schemas/local-config.schema.json |
JSON-Schema for L4 (local) | 🔗 |
tests/config/fixtures/local-config.yaml |
Example L4 file used in the test suite | 🔗 |
Summary
- The L1-L4 layered configuration system in AIOS Pro provides a deterministic five-level hierarchy (L1 Framework → L2 Project → Pro → L3 App → L4 Local) where each layer overwrites the previous according to deep-merge rules.
- L1 contains read-only framework defaults, L2 stores team-wide project settings, L3 handles optional per-app customizations, and L4 holds git-ignored, developer-specific overrides including secrets.
- The
resolveConfig()function inconfig-resolver.jsorchestrates loading, whilemerge-utils.jsimplements the inheritance logic: scalars use last-wins, objects deep-merge, arrays replace (unless using+append), andnulldeletes keys. - Environment variable interpolation is restricted to L4 only, enforced by
env-interpolator.js, and each layer undergoes JSON-Schema validation to ensure configuration integrity.
Frequently Asked Questions
What happens if L4 local-config.yaml conflicts with L2 project-config.yaml?
When the same key exists in both files, L4 always wins. According to the merge logic in merge-utils.js, the deterministic loading order (L1 → L2 → Pro → L3 → L4) means that L4's values overwrite any previous layer's settings for scalar values, while nested objects are deep-merged with L4's properties taking precedence. This ensures that developer-specific overrides in .aios-core/local-config.yaml (which is git-ignored) can safely supersede team-wide policies without affecting other team members.
Can I use environment variables in AIOS Pro configuration files?
Only in L4 (Local) configuration. The env-interpolator.js module enforces a strict linting rule via lintEnvPatterns that restricts ${VAR} interpolation to .aios-core/local-config.yaml. If you attempt to use environment variable placeholders in L1 (Framework), L2 (Project), or L3 (App) configs, the validator will emit warnings. This security measure ensures that secrets and machine-specific values stay out of source control while allowing developers to reference local environment variables for personal tooling and API tokens.
How do I debug which configuration layer is providing a specific value?
Enable debug mode in the resolveConfig() options. When you call resolveConfig(projectRoot, { debug: true }), the loadLayeredConfig() function in config-resolver.js builds a sources map that records the provenance of every configuration key. This map indicates whether each value originated from L1, L2, L3, or L4. You can also use the CLI command aios config show --debug to inspect the effective configuration alongside its source layers, making it easy to trace why a particular setting has its current value.
Is the L3 App layer required for all AIOS Pro projects?
No, L3 is optional. The App layer (aios-app.config.yaml) is only loaded when the options.appDir parameter is explicitly supplied to resolveConfig(). This design supports micro-frontend or micro-service architectures where individual applications within a monorepo need distinct UI themes or feature flags. If you are building a single-application project or do not specify an app directory, the configuration resolver skips L3 entirely and proceeds directly from the Pro layer to L4 (Local), simplifying the inheritance chain.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →