How Agent Reach Persists Configuration in ~/.agent-reach/config.yaml
Agent Reach stores user-specific settings in a YAML file at ~/.agent-reach/config.yaml using a secure Config class that handles directory creation, atomic writes with 600 permissions, and environment variable fallback.
Agent Reach is an open-source Python framework hosted at Panniantong/Agent-Reach that manages user-specific settings through a local configuration file. The persistence logic resides in agent_reach/config.py and ensures sensitive data like API keys are stored securely with restricted file permissions. This implementation is tested in tests/test_config.py and relies on PyYAML (declared in pyproject.toml) for serialization.
Configuration File Location and Structure
Default Path Resolution
The configuration system resolves the storage path using Python's pathlib module. According to the source code in agent_reach/config.py, the framework defines:
CONFIG_DIRasPath.home() / ".agent-reach"(line 18)CONFIG_FILEasCONFIG_DIR / "config.yaml"(line 19)
This creates a hidden directory in the user's home folder, following Unix conventions for application-specific data.
The Config Class Persistence Mechanism
Lazy Directory Creation with _ensure_dir()
Before writing any data, the private method _ensure_dir() ensures the configuration directory exists. Found at lines 37-40 in agent_reach/config.py, this method uses Path.mkdir(parents=True, exist_ok=True) to create missing parent directories without raising errors if the path already exists. This method is invoked during class initialization and prior to every save operation.
Loading Existing Configuration
The load() method (lines 41-48) handles configuration retrieval by checking for file existence first. If config.yaml exists, it parses the content using yaml.safe_load; otherwise, it initializes an empty dictionary. This approach prevents FileNotFoundError exceptions on first run while safely handling YAML parsing.
Secure File Writing with Atomic Permissions
The save() method implements platform-aware security measures to prevent credential leakage.
On Unix systems (lines 49-60), it uses low-level os.open() with flags O_WRONLY | O_CREAT | O_TRUNC and mode 0o600 (owner read/write only). This atomic creation prevents race conditions where temporary files might be world-readable.
For Windows compatibility (lines 63-68), the method falls back to standard open() when os.open flags aren't supported, ensuring cross-platform functionality.
The configuration data is serialized using yaml.dump() with default_flow_style=False and allow_unicode=True for human-readable output.
Accessor Methods and Environment Integration
Reading Configuration Values
The get(key, default) method (lines 69-78) implements a two-tier lookup strategy:
- Check the in-memory configuration dictionary
- Fall back to uppercase environment variables if the key is absent
This allows environment variables to override file-based settings without modifying the YAML.
Writing and Deleting Keys
The set(key, value) method (lines 80-84) updates the internal dictionary and immediately calls save() to persist changes to disk. Similarly, delete(key) (lines 85-88) removes the specified key and triggers a save operation, ensuring the file system remains synchronized with the in-memory state.
Feature-Specific Configuration Validation
Checking Feature Requirements
The FEATURE_REQUIREMENTS dictionary maps optional features (like "exa_search") to their required configuration keys (e.g., ["exa_api_key"]). The is_configured(feature) method (lines 90-94) returns True only when all required keys exist in the configuration or environment variables.
Aggregating Feature Status
get_configured_features() (lines 95-100) iterates through all defined features and returns those with complete configurations, enabling the application to dynamically enable functionality based on available credentials.
Security and Privacy Protections
Credential Masking
The to_dict() method (lines 102-108) provides a sanitized view of the configuration by masking values for keys containing sensitive substrings: "key", "token", "password", or "proxy". This prevents accidental credential exposure in logs or debug output.
Working with Agent Reach Configuration
from agent_reach.config import Config
# Initialize (loads existing config or creates empty one)
cfg = Config()
# Set API key - automatically persisted with 600 permissions
cfg.set('exa_api_key', 'sk-abcdef123456')
# Retrieve value (checks file first, then environment)
api_key = cfg.get('exa_api_key')
# Remove sensitive data
cfg.delete('exa_api_key')
# Verify feature readiness
if cfg.is_configured('exa_search'):
print("EXA search is ready")
Summary
- Agent Reach persists configuration in
~/.agent-reach/config.yamlusing theConfigclass fromagent_reach/config.py - The
_ensure_dir()method creates the directory structure on demand withexist_ok=True - File writes use
os.open()with0o600permissions on Unix to ensure only the owner can read sensitive data - The
get()method supports environment variable fallback for containerized deployments - Feature validation via
is_configured()ensures required API keys are present before enabling functionality to_dict()masks sensitive values to prevent credential leakage in logs
Frequently Asked Questions
How does Agent Reach handle first-time setup when the config file doesn't exist?
When initializing the Config class for the first time, the load() method checks for file existence. If ~/.agent-reach/config.yaml is missing, it initializes an empty dictionary rather than raising an error. The directory and file are created only when set() or save() is first called, utilizing the _ensure_dir() method to create parent directories automatically.
Why does Agent Reach use file mode 0o600 when saving configuration?
The save() method uses os.open() with mode 0o600 (owner read/write only) to prevent other users on the system from accessing sensitive credentials like API keys. This creates the file with restrictive permissions atomically at creation time, avoiding race conditions where temporary files might be readable by others. On Windows, where these Unix permissions aren't available, it gracefully falls back to standard file operations.
Can environment variables override the YAML configuration file?
Yes. The get() method implements a priority system where it first checks the in-memory configuration dictionary loaded from the YAML file. If the key is not found, it automatically checks for an uppercase environment variable with the same name. This allows Docker containers and CI/CD pipelines to inject credentials without modifying the local config file.
How does Agent Reach prevent API keys from appearing in logs?
The to_dict() method sanitizes the configuration before returning it by masking any value where the key contains sensitive substrings like "key", "token", "password", or "proxy". When debugging or logging the configuration object, developers should use to_dict() rather than accessing the raw internal dictionary to avoid accidentally exposing credentials in log files or error reports.
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 →