# How ai-memory Ensures a Single Configuration Read Path at Startup

> Learn how ai-memory ensures a single configuration read path by loading settings once into an immutable Arc<Config> for consistent access across your codebase.

- Repository: [Fabio Akita/ai-memory](https://github.com/akitaonrails/ai-memory)
- Tags: internals
- Published: 2026-09-05

---

**`ai-memory` guarantees a single configuration read path by loading all runtime settings exactly once through `Config::load`, storing the result in an immutable `Arc<Config>`, and sharing that reference throughout the entire codebase.**

The `ai-memory` project avoids a common pitfall in Rust applications: scattered environment variable reads that cause configuration drift. According to the source code in `akitaonrails/ai-memory`, the design intentionally eliminates any second read path by centralizing all configuration logic in one module and one function call.

## The Core Design: One Load, Immutable Share

The configuration system is documented directly in [`crates/ai-memory-cli/src/config.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-cli/src/config.rs). The module header explicitly states: "All settings are read exactly once at startup… and there is no second read path"【/cache/repos/github.com/akitaonrails/ai-memory/main/crates/ai-memory-cli/src/config.rs#L3-L6】.

This is enforced through three architectural decisions:

- **Single `Config::load` entry point** – merges environment variables, TOML file, and defaults in one Figment-based call
- **No `std::env::var` elsewhere** – eliminates hidden reads that caused bugs in earlier designs
- **Immutable `Arc<Config>` propagation** – every subsystem receives a reference, never reloading

The `Config` struct holds every configurable field: data paths, server bind address, LLM provider options, timeouts, and more. Once constructed by `Config::load`, it never changes【/cache/repos/github.com/akitaonrails/ai-memory/main/crates/ai-memory-cli/src/config.rs#L24-L40】【/cache/repos/github.com/akitaonrails/ai-memory/main/crates/ai-memory-cli/src/config.rs#L125-L136】.

## Loading Configuration Once in the CLI Entry Point

The single-read guarantee begins at program start. In [`crates/ai-memory-cli/src/lib.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-cli/src/lib.rs), the CLI entry point calls `Config::load` exactly once and wraps the result in `Arc`:

```rust
// crates/ai-memory-cli/src/lib.rs
let config = Arc::new(Config::load(config_path.as_deref(), data_dir)?);

```

This `Arc<Config>` is then passed to all subsystems. The `?` operator ensures that any configuration error fails fast before the application proceeds【/cache/repos/github.com/akitaonrails/ai-memory/main/crates/ai-memory-cli/src/lib.rs#L64-L66】.

## Propagating Configuration Without Reloading

Downstream modules receive the shared configuration reference. In [`crates/ai-memory-web/src/state.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-web/src/state.rs), the server state is constructed from the same `Arc<Config>`:

```rust
// crates/ai-memory-web/src/state.rs
pub fn new_state(config: Arc<Config>) -> State {
    State {
        bind: config.bind.clone(),
        server_url: config.server_url.clone(),
        // …other fields pulled from the same Config instance…
    }
}

```

No module calls `Config::load` or `std::env::var`. All values originate from the single load at startup.

## Reading Configuration in LLM Providers

Even deeply nested components follow the same pattern. The LLM factory in [`crates/ai-memory-llm/src/factory.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-llm/src/factory.rs) consumes configuration values without any file or environment access:

```rust
// crates/ai-memory-llm/src/factory.rs
let timeout = config.llm_timeout_secs;          // sourced from Config::load only
let provider = ProviderConfig::new(..., timeout);

```

This ensures that `llm_timeout_secs` comes from the same merged configuration as every other setting—no overrides, no hidden defaults, no divergence.

## How Figment Enables Single-Path Loading

The `Config::load` function uses the Figment crate to merge three configuration sources in one call:

1. `AI_MEMORY_`-prefixed environment variables
2. Optional TOML configuration file (path passed as argument)
3. Hardcoded defaults

This merge happens atomically. The resulting `Config` struct contains the final, resolved values. Because Figment handles all source prioritization internally, no other code needs to check environment variables or parse files.

## Preventing the Bug That Motivated This Design

The module header in [`config.rs`](https://github.com/akitaonrails/ai-memory/blob/main/config.rs) references a past bug: scattered `env::var` calls caused hidden configuration reads that were hard to trace and debug. The current design eliminates this class of error by construction—there is simply no API available to read configuration after startup.

## Summary

- **`Config::load`** in [`crates/ai-memory-cli/src/config.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-cli/src/config.rs) is the sole function that reads environment variables or configuration files
- **Immutable `Arc<Config>`** carries resolved settings to every subsystem without reloading
- **No `std::env::var` calls** exist outside the configuration module, preventing hidden reads
- **Figment-based merging** handles all source prioritization in one atomic operation

## Frequently Asked Questions

### What happens if I need to reload configuration without restarting?

`ai-memory` does not support runtime configuration reloads. The single-read design trades dynamic updates for predictability and simplicity. Any configuration change requires a process restart to take effect.

### Why use `Arc<Config>` instead of a global static?

`Arc<Config>` enables testability and explicit dependency injection. Tests can construct custom `Config` values and pass them to components without modifying global state. A static would require lazy initialization and could encourage hidden `env::var` reads.

### Where are the default values defined?

Defaults are embedded in the `Config` struct's Figment integration within [`crates/ai-memory-cli/src/config.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-cli/src/config.rs). They merge with environment variables and TOML values during the single `load` call, with explicit precedence: env vars override TOML, which overrides defaults.

### How does this design affect binary size or performance?

The single-read pattern has negligible runtime cost—one allocation for the `Arc` and cheap reference copies thereafter. It eliminates repeated environment lookups and file system checks, likely improving performance over scattered reads. The main benefit is reliability: configuration is resolved once and never questioned again.