How Nemori's Configuration System Works: A Complete Guide to MemoryConfig
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 as a Python dataclass. This design choice provides type safety, automatic attribute handling, and clear default values.
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:
@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:
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 acts as the central engine. It accepts an optional MemoryConfig parameter and falls back to a default instance if none is provided:
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 translates configuration values into concrete implementations. It inspects the MemoryConfig to decide which storage backends and index types to instantiate:
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 exposes the configuration system to end users. It accepts a MemoryConfig object and passes it to the internal MemorySystem:
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 (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, evaluation/longmemeval/search.py, and 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
from nemori import NemoriMemory
# Automatically reads OPENAI_API_KEY from environment
# Uses all default values for models and storage
memory = NemoriMemory()
Programmatic Configuration Override
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
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
from nemori import NemoriMemory
# Explicitly indicates configuration comes from environment variables
memory = NemoriMemory.from_env()
Custom Provider Accessing Configuration
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
MemoryConfigdataclass insrc/config.pyas the immutable source of truth for all library settings. -
Environment Integration: Sensitive values like
OPENAI_API_KEYare automatically read from environment variables viadefault_factorylambdas, keeping credentials out of source code. -
Validation at Construction: The
__post_init__method ensures configuration consistency immediately, raising clearValueErrormessages for missing API keys or invalid parameters. -
Dependency Injection: The
MemorySystemclass insrc/core/memory_system.pyaccepts the configuration object and propagates it to all subsystems, whileDefaultProvidersinsrc/services/providers.pyuses 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 theNemoriMemory.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, 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 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 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, 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).
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 →