ai-memory-cli Config::load() Path: Why Single-Pass Configuration Loading Matters
The ai-memory-cli uses a single config::Config::load() call at startup that merges defaults, a TOML config file, and environment variables in one pass, with no second read path to prevent environment desynchronization bugs.
The ai-memory CLI from the akitaonrails/ai-memory repository implements a strict single-read configuration invariant. Every setting is resolved exactly once when the binary starts, producing an immutable Config value that remains unchanged for the program's entire lifetime. This design directly addresses a class of bugs where scattered environment reads caused inconsistent behavior across components.
How Config::load() Resolves the Configuration Path
The Config::load() function in crates/ai-memory-cli/src/config.rs (lines 75-106) follows a deterministic, layered resolution strategy:
Step 1: Determine the Data Directory
The loader prioritizes sources in this order:
- CLI flag
--data-dir(cli_data_dirparameter) takes highest precedence - Environment variable
AI_MEMORY_DATA_DIR(captured viaRuntimeEnv::data_dir) if no flag provided - Built-in default from
default_data_dir()as final fallback
Step 2: Resolve the Config File Path
With the data directory established, the loader constructs the final path:
- Explicit
--configflag (config_pathparameter) overrides everything - Otherwise defaults to
<data_dir>/config.toml
Step 3: Merge Configuration Layers Using Figment
The implementation uses the Figment library to merge three sources in strict order:
use figment::{Figment, providers::{Serialized, Toml, Env}};
let figment = Figment::from(Serialized::defaults(Self::default()))
.merge(Toml::file(&resolved_config_path)) // only if file exists
.merge(Env::prefixed("AI_MEMORY_").split("__"));
This merging order ensures that environment variables override file settings, which in turn override compiled defaults.
Step 4: Inject Runtime-Only Values
After extracting the merged Config, the loader performs exactly one read of process-specific environment variables:
// From config.rs — these values are captured once and never re-read
config.auth.bearer_token = runtime_env.auth_token; // AI_MEMORY_AUTH_TOKEN
config.server_url = runtime_env.server_url; // AI_MEMORY_SERVER_URL
config.home_dir = Some(runtime_env.home_dir); // canonicalized
config.data_dir = data_dir.canonicalize()?;
Step 5: Validate and Return
Finally, Config::load() enforces numeric invariants (decay weights, LLM token limits) and returns either a fully-populated, immutable Config or an early error.
Why No Second Config-Read Path Exists
The comment at the top of config.rs explicitly documents this architectural decision (lines 3-7):
//! All settings are read exactly once at startup, merged into a single
//! immutable [`Config`] value, and passed by reference everywhere. There is
//! no second read path (lesson from agentmemory #456 / #469 — the dimension
//! guard read `process.env` while the rest of the codebase used
//! `getMergedEnv()`, masking the bug for weeks).
The Historical Bug That Motivated This Design
Issues #456 and #469 in the related agentmemory project revealed a critical failure mode: one component read process.env directly while others used a merged configuration view. This desynchronization masked bugs for weeks because tests passed in isolation but production environments exhibited inconsistent behavior.
Four Architectural Guarantees
| Guarantee | Mechanism | Benefit |
|---|---|---|
| Consistency | RuntimeEnv::from_process() captures all environment variables in one call |
Every component sees identical values even if the external environment changes |
| Predictability | Single, well-defined merge order (defaults → file → environment) | No accidental overrides from late code re-reading configuration |
| Performance | One-time I/O and parsing | Fast startup for CLI subcommands |
| Thread Safety | Immutable Config struct |
Lock-free sharing across threads |
Practical Usage Example
Here's how the ai-memory-cli binary invokes the single-pass loader:
use ai_memory_cli::config::Config;
use std::path::Path;
fn main() -> anyhow::Result<()> {
// Parsed from clap arguments
let cli_data_dir: Option<&Path> = None; // from --data-dir
let config_path: Option<&Path> = None; // from --config
// SINGLE LOAD — this is the only configuration read in the entire program
let cfg = Config::load(config_path, cli_data_dir)?;
println!("Data directory: {}", cfg.data_dir.display());
println!("Server URL: {}", cfg.server_url);
// Runtime values injected during load, never re-read
if let Some(home) = cfg.home_dir.as_deref() {
println!("Home: {}", home);
}
Ok(())
}
Key Source Files
crates/ai-memory-cli/src/config.rs—Config::load()implementation andRuntimeEnvstruct (lines 45-87 and 75-106)crates/ai-memory-cli/src/cli.rs— CLI argument parsing that feeds--data-dirand--configinto the loader
Summary
- The
config::Config::load()path in ai-memory-cli merges defaults → TOML file → environment variables in a single pass using Figment - No second read path exists to prevent the environment desynchronization bugs that plagued earlier projects (#456, #469)
- Runtime-only values (
AI_MEMORY_AUTH_TOKEN,AI_MEMORY_SERVER_URL,HOME) are captured exactly once viaRuntimeEnv::from_process() - The resulting immutable
Configeliminates race conditions, ensures testability, and provides predictable behavior across all CLI subcommands
Frequently Asked Questions
What happens if the configuration file doesn't exist?
The loader continues without error. Toml::file() in Figment is non-failing for missing files—the merge simply proceeds with defaults and environment variables. This supports zero-configuration deployments where all settings come from environment variables.
Can I reload configuration without restarting ai-memory-cli?
No. The single-read invariant is architectural, not optional. To change configuration, you must restart the process. This trade-off prioritizes consistency over dynamic reconfiguration, which aligns with the CLI's short-lived process model.
Why use Figment instead of simpler alternatives like envy or config-rs?
Figment provides the precise layering semantics (defaults → file → environment) and prefix-based environment variable parsing (AI_MEMORY_* with __ delimiter for nested keys) that ai-memory-cli requires. It also gracefully handles missing files without custom logic.
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 →