#configuration-management #keychain #api-bindings

enact-config

Unified configuration management for Enact - secure storage with keychain and encrypted files

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)

MIT license

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 (~/.enact by 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):

  1. Environment variables (highest priority)
  2. Project configuration (projects/<slug>/project.yaml)
  3. Agent configuration (agents/<name>/agent.yaml)
  4. 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