# How Nemori's Configuration System Works: A Complete Guide to MemoryConfig

> Discover how Nemori manages configuration with MemoryConfig. Learn about its central dataclass, validation, and thread-safe sharing across the library stack for efficient use.

- Repository: [Nemori AI/nemori](https://github.com/nemori-ai/nemori)
- Tags: how-to-guide
- Published: 2026-03-08

---

**Nemori manages its configuration through a single, central `MemoryConfig` dataclass that is validated once and shared thread‑safely across the entire library stack.**

The nemori-ai/nemori repository implements a unified configuration system designed to balance simplicity for new users with fine‑grained control for advanced deployments. At its heart lies the `MemoryConfig` object, which aggregates every tunable setting—from LLM model names to storage paths and feature flags—and propagates these values to all subsystems.

## The Central Configuration Object: MemoryConfig

### Defining Configuration in src/config.py

The configuration schema lives in [`src/config.py`](https://github.com/nemori-ai/nemori/blob/main/src/config.py) as a Python dataclass. This design choice provides type safety, automatic attribute handling, and clear default values.

```python
from dataclasses import dataclass, field
from typing import Optional
import os

@dataclass
class MemoryConfig:
    llm_model: str = "gpt-4o"
    embedding_model: str = "text-embedding-3-large"
    buffer_size_max: int = 10
    storage_path: str = "./memory_store"
    vector_index_backend: str = "chroma"
    enable_semantic_memory: bool = True
    enable_episode_merging: bool = True
    # ... additional fields

```

Default values are declared directly in the class definition, allowing users to instantiate a working system with `MemoryConfig()` and no arguments.

### Environment Variable Integration

Sensitive credentials and deployment‑specific values are injected via environment variables at construction time. The dataclass uses `default_factory` to read from `os.getenv` only when the field is not explicitly provided:

```python
@dataclass
class MemoryConfig:
    openai_api_key: Optional[str] = field(
        default_factory=lambda: os.getenv("OPENAI_API_KEY")
    )
    # Additional environment-driven fields follow the same pattern

```

This approach keeps secrets out of source code while maintaining the ability to override them programmatically when needed.

### Validation and Error Handling

Validation logic resides in the `__post_init__` method, ensuring that any `MemoryConfig` instance is internally consistent before it reaches downstream components:

```python
def __post_init__(self):
    if not self.openai_api_key:
        raise ValueError("OPENAI_API_KEY must be set via environment variable or passed explicitly")
    if self.buffer_size_max < 1:
        raise ValueError("buffer_size_max must be positive")
    # Additional consistency checks...

```

Clear `ValueError` messages guide users to fix configuration issues immediately at startup.

## Injecting Configuration Throughout the System

### Core Memory System Initialization

The `MemorySystem` class in [`src/core/memory_system.py`](https://github.com/nemori-ai/nemori/blob/main/src/core/memory_system.py) acts as the central engine. It accepts an optional `MemoryConfig` parameter and falls back to a default instance if none is provided:

```python
class MemorySystem:
    def __init__(self, config: Optional[MemoryConfig] = None):
        self.config = config or MemoryConfig()
        # Subsequent initialization uses self.config...

```

This pattern ensures that every subsystem receives the same configuration object, maintaining consistency across the library.

### Provider Factory Pattern

The `DefaultProviders` factory in [`src/services/providers.py`](https://github.com/nemori-ai/nemori/blob/main/src/services/providers.py) translates configuration values into concrete implementations. It inspects the `MemoryConfig` to decide which storage backends and index types to instantiate:

```python
class DefaultProviders:
    def __init__(self, config: MemoryConfig):
        self.config = config
        
        # Storage backend selection

        if config.storage_path:
            self.storage = FileSystemStorage(config.storage_path)
        else:
            self.storage = InMemoryStorage()
            
        # Vector index selection

        if config.vector_index_backend == "chroma":
            self.vector_index = ChromaIndex(config)
        else:
            self.vector_index = MemoryVectorIndex()
            
        # Feature flags control optional components

        if config.enable_semantic_memory:
            self.semantic_generator = SemanticGenerator(config)

```

This centralized factory ensures that feature flags like `enable_semantic_memory` or `enable_batch_segmentation` consistently enable or disable components across the stack.

### Public API Facade

The user-facing `NemoriMemory` class in [`src/api/facade.py`](https://github.com/nemori-ai/nemori/blob/main/src/api/facade.py) exposes the configuration system to end users. It accepts a `MemoryConfig` object and passes it to the internal `MemorySystem`:

```python
class NemoriMemory:
    def __init__(self, config: Optional[MemoryConfig] = None):
        self._system = MemorySystem(config=config)
        
    @classmethod
    def from_env(cls):
        """Convenience constructor that creates config from environment variables."""
        return cls(config=MemoryConfig())

```

Users can instantiate the system in three ways: providing a custom config object, loading from a file via `MemoryConfig.from_dict()`, or using the `from_env()` convenience method.

## Runtime Configuration Flexibility

### Dynamic Component Loading

Because the same `MemoryConfig` instance is shared across components, any subsystem can read flags at runtime to adjust behavior. For example, the semantic generator checks `self.config.enable_semantic_memory` before executing operations.

The `MemorySystem` also supports dynamic reloading of user-specific indices based on configuration flags. The method `load_user_data_and_indices_for_method` in [`src/core/memory_system.py`](https://github.com/nemori-ai/nemori/blob/main/src/core/memory_system.py) (lines 72-104) inspects the configuration to determine whether to load vector indices, BM25 lexical indices, or hybrid combinations.

### Configuration Persistence for Experiments

For reproducible research and evaluation, Nemori supports serializing configurations to JSON or YAML. The `MemoryConfig.from_dict()` class method deserializes dictionaries into valid configuration objects.

Evaluation scripts such as [`scripts/init_workspace.py`](https://github.com/nemori-ai/nemori/blob/main/scripts/init_workspace.py), [`evaluation/longmemeval/search.py`](https://github.com/nemori-ai/nemori/blob/main/evaluation/longmemeval/search.py), and [`evaluation/locomo/search.py`](https://github.com/nemori-ai/nemori/blob/main/evaluation/locomo/search.py) demonstrate this pattern in production. They load JSON configuration files, deserialize them with `MemoryConfig.from_dict()`, and pass the resulting objects to the library. This ensures that experimental parameters are version-controlled and reproducible across different environments.

## Practical Configuration Examples

### Basic Environment-Driven Setup

```python
from nemori import NemoriMemory

# Automatically reads OPENAI_API_KEY from environment

# Uses all default values for models and storage

memory = NemoriMemory()

```

### Programmatic Configuration Override

```python
from nemori import MemoryConfig, NemoriMemory

cfg = MemoryConfig(
    llm_model="gpt-4o-mini",
    embedding_model="text-embedding-3-small",
    buffer_size_max=30,          # Larger buffer before flushing to storage

    enable_episode_merging=False # Disable automatic episode consolidation

)

memory = NemoriMemory(config=cfg)

```

### Loading from JSON Configuration File

```python
import json
from pathlib import Path
from nemori import MemoryConfig, NemoriMemory

config_path = Path("experiments/config.json")
config_dict = json.loads(config_path.read_text())
cfg = MemoryConfig.from_dict(config_dict)

memory = NemoriMemory(config=cfg)

```

### Explicit Environment Constructor

```python
from nemori import NemoriMemory

# Explicitly indicates configuration comes from environment variables

memory = NemoriMemory.from_env()

```

### Custom Provider Accessing Configuration

```python
from nemori.config import MemoryConfig

class CustomStorageProvider:
    def __init__(self, config: MemoryConfig):
        # Access configuration values directly

        self.persistence_path = config.storage_path
        self.vector_backend = config.vector_index_backend
        
        # Check feature flags

        if config.enable_semantic_memory:
            self.init_semantic_layer()

```

## Summary

- **Centralized Configuration**: Nemori uses a single `MemoryConfig` dataclass in [`src/config.py`](https://github.com/nemori-ai/nemori/blob/main/src/config.py) as the immutable source of truth for all library settings.

- **Environment Integration**: Sensitive values like `OPENAI_API_KEY` are automatically read from environment variables via `default_factory` lambdas, keeping credentials out of source code.

- **Validation at Construction**: The `__post_init__` method ensures configuration consistency immediately, raising clear `ValueError` messages for missing API keys or invalid parameters.

- **Dependency Injection**: The `MemorySystem` class in [`src/core/memory_system.py`](https://github.com/nemori-ai/nemori/blob/main/src/core/memory_system.py) accepts the configuration object and propagates it to all subsystems, while `DefaultProviders` in [`src/services/providers.py`](https://github.com/nemori-ai/nemori/blob/main/src/services/providers.py) uses it to select concrete implementations based on feature flags.

- **Flexible Instantiation**: Users can configure Nemori programmatically, load settings from JSON/YAML files via `MemoryConfig.from_dict()`, or use the `NemoriMemory.from_env()` convenience method.

## Frequently Asked Questions

### How does Nemori handle sensitive configuration like API keys?

Nemori reads sensitive values from environment variables at configuration construction time. In [`src/config.py`](https://github.com/nemori-ai/nemori/blob/main/src/config.py), the `MemoryConfig` dataclass uses `field(default_factory=lambda: os.getenv("OPENAI_API_KEY"))` to inject the OpenAI API key without hardcoding it. If the variable is missing, the `__post_init__` validation raises a `ValueError` with a clear message indicating the required environment variable.

### Can I change Nemori's configuration after initializing the memory system?

No, the `MemoryConfig` object is designed to be immutable once constructed. The `MemorySystem` in [`src/core/memory_system.py`](https://github.com/nemori-ai/nemori/blob/main/src/core/memory_system.py) stores a reference to the configuration instance during initialization and shares it across all providers. While you cannot modify the configuration object itself after creation, you can create a new `MemoryConfig` instance with different parameters and initialize a separate `NemoriMemory` object if you need different behavior.

### What is the difference between using `NemoriMemory()` and `NemoriMemory.from_env()`?

Both methods ultimately create a memory system configured via environment variables, but `from_env()` is an explicit convenience constructor. When you call `NemoriMemory()`, the underlying `MemorySystem` creates a default `MemoryConfig()` which automatically reads `OPENAI_API_KEY` from the environment. The `from_env()` classmethod in [`src/api/facade.py`](https://github.com/nemori-ai/nemori/blob/main/src/api/facade.py) makes this behavior explicit in your code, signaling to other developers that the configuration is entirely environment-driven rather than programmatically specified.

### How do I switch between different storage backends in Nemori?

You control the storage backend via the `MemoryConfig` dataclass parameters before initializing the system. In [`src/services/providers.py`](https://github.com/nemori-ai/nemori/blob/main/src/services/providers.py), the `DefaultProviders` factory inspects `config.storage_path` and `config.vector_index_backend` to decide between filesystem versus in-memory storage, and Chroma versus memory vector indices. To use a specific backend, set the appropriate fields when creating your configuration object, such as `MemoryConfig(storage_path="./data", vector_index_backend="chroma")`, then pass this config to `NemoriMemory(config=cfg)`.