2 releases
| 0.0.2 | Feb 26, 2026 |
|---|---|
| 0.0.1 | Feb 20, 2026 |
#969 in Asynchronous
48 downloads per month
Used in 14 crates
(11 directly)
135KB
3K
SLoC
enact-config
Unified configuration management for Enact with secure storage, multi-agent support, and YAML-based configuration.
Features
- ENACT_HOME: Centralized data directory (
~/.enactby default) - YAML Configuration: Human-readable, editable config files
- Multi-Agent Support: Define multiple agents with different capabilities
- Project Workspaces: Scoped configuration and memory per project
- Hierarchical Config: Global → Agent → Project → Environment variables
- Environment Variables: Read secrets from
ENACT_*environment variables - Encrypted Storage: AES-256-GCM encrypted secrets
- Agent/Project Registry: Programmatic access to agents and projects
ENACT_HOME Structure
~/.enact/
├── config.yaml # Global configuration
├── config.encrypted # Encrypted secrets
├── agents/ # Agent definitions
│ ├── assistant/
│ │ ├── agent.yaml
│ │ └── sessions/
│ └── coder/
│ ├── agent.yaml
│ └── sessions/
└── projects/ # Project workspaces
├── my-webapp/
│ ├── project.yaml
│ └── sessions/
└── docs/
├── project.yaml
└── sessions/
Quick Start
ENACT_HOME
use enact_config::{enact_home, ensure_home_dirs};
// Get ENACT_HOME (defaults to ~/.enact, override with ENACT_HOME env var)
let home = enact_home();
// Ensure directory structure exists
ensure_home_dirs(&home)?;
Global Configuration
use enact_config::Config;
// Load from ENACT_HOME/config.yaml
let config = Config::load_from_home()?;
// Modify and save
let mut config = Config::default();
config.runtime.max_concurrent_executions = 20;
config.save_to_home()?;
Agent Registry
use enact_config::{AgentRegistry, AgentDef};
// List all agents
let agents = AgentRegistry::list(&enact_home())?;
// Load specific agent
let agent = AgentRegistry::get(&enact_home(), "assistant")?;
// Create new agent
let agent = AgentDef {
name: "coder".to_string(),
description: Some("Code specialist".to_string()),
version: "1.0.0".to_string(),
model: Some("gpt-4o".to_string()),
system_prompt: Some("You are a coding expert.".to_string()),
tools: vec!["file_read".to_string(), "file_write".to_string()],
workflow: None,
approval: None,
memory: None,
};
agent.save(&enact_home())?;
Project Registry
use enact_config::{ProjectRegistry, ProjectDef, TaskBoard};
// List all projects
let projects = ProjectRegistry::list(&enact_home())?;
// Load specific project
let project = ProjectRegistry::get(&enact_home(), "my-webapp")?;
// Load task board
let board = TaskBoard::load(&enact_home(), "my-webapp")?;
// Create project
let project = ProjectDef {
name: "My Web App".to_string(),
slug: "my-webapp".to_string(),
repo: Some("/path/to/repo".to_string()),
default_agent: Some("coder".to_string()),
agents: vec!["coder".to_string(), "reviewer".to_string()],
description: Some("Web application project".to_string()),
memory: None,
};
project.save(&enact_home())?;
ConfigManager (Legacy Encrypted Config)
use enact_config::{ConfigManager, default_config_path};
let config_path = default_config_path()?;
let manager = ConfigManager::new(config_path).await?;
// Load configuration
let config = manager.load().await?;
// Set a secret (stored in encrypted file)
manager.set_secret("providers.azure.apiKey", "your-api-key").await?;
// Save configuration
manager.save(&config).await?;
Modules
home — ENACT_HOME Management
use enact_config::{enact_home, ensure_home_dirs};
/// Returns the Enact home directory.
/// Priority: 1. ENACT_HOME env var, 2. $HOME/.enact, 3. ./.enact
pub fn enact_home() -> PathBuf;
/// Ensures the standard ENACT_HOME directory structure exists.
/// Creates: agents/, projects/, state/, logs/
pub fn ensure_home_dirs(home: &Path) -> Result<()>;
config — Global Configuration
YAML-based configuration with serde support.
use enact_config::Config;
// Load/save from ENACT_HOME
impl Config {
pub fn load_from_home() -> Result<Self>;
pub fn save_to_home(&self) -> Result<()>;
// Load/save from specific path
pub fn load_from_yaml_path(path: &Path) -> Result<Self>;
pub fn save_to_yaml_path(&self, path: &Path) -> Result<()>;
}
agent_def — Agent Definitions
Agent registry and agent.yaml management.
use enact_config::{AgentDef, AgentRegistry};
/// Per-agent definition (agents/<name>/agent.yaml)
pub struct AgentDef {
pub name: String,
pub description: Option<String>,
pub version: String,
pub model: Option<String>,
pub system_prompt: Option<String>,
pub tools: Vec<String>,
pub workflow: Option<String>,
pub approval: Option<ApprovalConfig>,
pub memory: Option<MemoryConfig>,
}
impl AgentDef {
pub fn agent_dir(home: &Path, name: &str) -> PathBuf;
pub fn agent_yaml_path(home: &Path, name: &str) -> PathBuf;
pub fn sessions_dir(home: &Path, name: &str) -> PathBuf;
pub fn memory_dir(home: &Path, name: &str) -> PathBuf;
pub fn load(home: &Path, name: &str) -> Result<Option<Self>>;
pub fn save(&self, home: &Path) -> Result<()>;
}
/// Registry for discovering agents
pub struct AgentRegistry;
impl AgentRegistry {
pub fn list(home: &Path) -> Result<Vec<String>>;
pub fn get(home: &Path, name: &str) -> Result<Option<AgentDef>>;
pub fn get_default(name: &str) -> Result<Option<AgentDef>>;
}
project_def — Project Definitions
Project registry, task boards, and project.yaml management.
use enact_config::{ProjectDef, ProjectRegistry, TaskBoard, Task};
/// Per-project definition (projects/<slug>/project.yaml)
pub struct ProjectDef {
pub name: String,
pub slug: String,
pub repo: Option<String>,
pub default_agent: Option<String>,
pub agents: Vec<String>,
pub description: Option<String>,
pub memory: Option<MemoryConfig>,
}
impl ProjectDef {
pub fn project_dir(home: &Path, slug: &str) -> PathBuf;
pub fn project_yaml_path(home: &Path, slug: &str) -> PathBuf;
pub fn taskboard_path(home: &Path, slug: &str) -> PathBuf;
pub fn sessions_dir(home: &Path, slug: &str) -> PathBuf;
pub fn context_dir(home: &Path, slug: &str) -> PathBuf;
pub fn load(home: &Path, slug: &str) -> Result<Option<Self>>;
pub fn save(&self, home: &Path) -> Result<()>;
}
/// Task board (projects/<slug>/taskboard.yaml)
pub struct TaskBoard {
pub version: String,
pub updated_at: Option<String>,
pub todo: Vec<Task>,
pub in_progress: Vec<Task>,
pub done: Vec<Task>,
}
impl TaskBoard {
pub fn load(home: &Path, slug: &str) -> Result<Self>;
pub fn save(&self, home: &Path, slug: &str) -> Result<()>;
}
/// Single task on the board
pub struct Task {
pub id: String,
pub title: String,
pub description: Option<String>,
pub priority: Option<String>,
pub assigned_to: Option<String>,
pub created_at: Option<String>,
pub started_at: Option<String>,
pub completed_at: Option<String>,
}
/// Registry for discovering projects
pub struct ProjectRegistry;
impl ProjectRegistry {
pub fn list(home: &Path) -> Result<Vec<String>>;
pub fn get(home: &Path, slug: &str) -> Result<Option<ProjectDef>>;
pub fn get_default(slug: &str) -> Result<Option<ProjectDef>>;
}
encrypted_store — Encrypted Configuration
Legacy encrypted configuration storage.
use enact_config::{EncryptedStore, default_config_path};
let store = EncryptedStore::new("/path/to/config.encrypted")?;
store.save("{ ... }")?;
let config = store.load()?;
doctor — Configuration Health Check
Validates ENACT_HOME: directory structure, YAML validity of config.yaml and agent/project definitions, taskboards, provider API keys, encrypted secrets, and daemon state. Used by enact doctor, gateway startup (enact serve), and the pre-commit hook.
use enact_config::{enact_home, run_checks, DoctorReport, CheckStatus};
let home = enact_home();
let report = run_checks(&home);
if report.has_failures() {
for check in report.checks.iter().filter(|c| c.status == CheckStatus::Fail) {
eprintln!("{}: {}", check.item, check.message);
}
}
Configuration Resolution
Configuration is resolved in this order (later overrides earlier):
- Environment variables (highest priority)
- Project configuration (
projects/<slug>/project.yaml) - Agent configuration (
agents/<name>/agent.yaml) - Global configuration (
config.yaml)
Environment Variable Mapping
Keys are converted to environment variables by:
- Converting to uppercase
- Replacing dots with underscores
- Prefixing with
ENACT_
| Key | Environment Variable |
|---|---|
providers.openai.api_key |
ENACT_PROVIDERS_OPENAI_API_KEY |
memory.retention_days |
ENACT_MEMORY_RETENTION_DAYS |
approval.enabled |
ENACT_APPROVAL_ENABLED |
Air-Gapped Mode
When runtime.mode === "airgapped":
- Cloud sync is disabled
- Network access is blocked
- Only local features are available
Examples
Creating an Agent
use enact_config::{enact_home, AgentDef};
let agent = AgentDef {
name: "my-agent".to_string(),
description: Some("My custom agent".to_string()),
version: "1.0.0".to_string(),
model: Some("gpt-4o-mini".to_string()),
system_prompt: Some("You are helpful.".to_string()),
tools: vec![
"file_read".to_string(),
"file_write".to_string(),
"shell".to_string(),
],
workflow: None,
approval: None,
memory: None,
};
agent.save(&enact_home())?;
Creating a Project
use enact_config::{enact_home, ProjectDef, TaskBoard};
let project = ProjectDef {
name: "My Project".to_string(),
slug: "my-project".to_string(),
repo: Some("/path/to/repo".to_string()),
default_agent: Some("assistant".to_string()),
agents: vec!["assistant".to_string()],
description: Some("Description".to_string()),
memory: None,
};
project.save(&enact_home())?;
// Create empty task board
let board = TaskBoard::default();
board.save(&enact_home(), "my-project")?;
Loading Configuration
use enact_config::{enact_home, Config, AgentRegistry, ProjectRegistry};
// Load global config
let config = Config::load_from_home()?;
// Load agent
let agent = AgentRegistry::get(&enact_home(), "assistant")?
.expect("Agent not found");
// Load project
let project = ProjectRegistry::get(&enact_home(), "my-webapp")?
.expect("Project not found");
License
MIT
Dependencies
~14–33MB
~375K SLoC