const DEFAULT_CONFIG: &str = include_str!("../default.yaml");
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BrainConfig {
pub brain: GeneralConfig,
pub storage: StorageConfig,
pub llm: LlmConfig,
pub embedding: EmbeddingConfig,
pub memory: MemoryConfig,
pub encryption: EncryptionConfig,
pub security: SecurityConfig,
pub actions: ActionsConfig,
pub proactivity: ProactivityConfig,
pub adapters: AdaptersConfig,
pub access: AccessConfig,
#[serde(default)]
pub channel: ChannelIntelligenceConfig,
#[serde(default)]
pub agents: AgentsConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GeneralConfig {
pub version: String,
pub data_dir: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageConfig {
pub ruvector_path: String,
pub sqlite_path: String,
pub hnsw: HnswConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HnswConfig {
pub ef_construction: u32,
pub m: u32,
pub ef_search: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LlmConfig {
pub provider: String,
pub model: String,
pub base_url: String,
pub temperature: f64,
pub max_tokens: u32,
#[serde(default)]
pub api_key: String,
#[serde(default)]
pub providers: Vec<ProviderEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderEntry {
pub name: String,
pub kind: String,
#[serde(default)]
pub base_url: String,
#[serde(default)]
pub api_key: String,
pub model: String,
#[serde(default)]
pub preferred_models: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmbeddingConfig {
pub model: String,
pub dimensions: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryConfig {
pub episodic: EpisodicConfig,
pub semantic: SemanticConfig,
pub search: SearchConfig,
pub consolidation: ConsolidationConfig,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct EpisodicConfig {}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SemanticConfig {
pub similarity_threshold: f64,
pub max_results: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchConfig {
pub rrf_k: u32,
#[serde(default = "default_pre_fusion_limit")]
pub pre_fusion_limit: u32,
#[serde(default = "default_importance_weight")]
pub importance_weight: f64,
#[serde(default = "default_recency_weight")]
pub recency_weight: f64,
#[serde(default = "default_decay_rate")]
pub decay_rate: f64,
}
fn default_pre_fusion_limit() -> u32 {
50
}
fn default_importance_weight() -> f64 {
0.3
}
fn default_recency_weight() -> f64 {
0.2
}
fn default_decay_rate() -> f64 {
0.01
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConsolidationConfig {
pub enabled: bool,
pub interval_hours: u32,
pub forgetting_threshold: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncryptionConfig {
pub enabled: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityConfig {
pub exec_allowlist: Vec<String>,
pub exec_timeout_seconds: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActionsConfig {
pub web_search: WebSearchActionConfig,
pub scheduling: SchedulingActionConfig,
pub messaging: MessagingActionConfig,
#[serde(default)]
pub resilience: ResilienceConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResilienceConfig {
pub max_retries: u32,
pub retry_base_ms: u64,
pub circuit_breaker_threshold: u32,
pub circuit_breaker_cooldown_secs: u64,
}
impl Default for ResilienceConfig {
fn default() -> Self {
Self {
max_retries: 2,
retry_base_ms: 500,
circuit_breaker_threshold: 5,
circuit_breaker_cooldown_secs: 60,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum WebSearchProvider {
#[default]
#[serde(alias = "duckduckgo", rename = "duckduckgo")]
DuckDuckGo,
Searxng,
Tavily,
Custom,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebSearchActionConfig {
pub enabled: bool,
#[serde(default)]
pub provider: WebSearchProvider,
pub endpoint: String,
#[serde(default)]
pub api_key: String,
pub timeout_ms: u64,
pub default_top_k: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SchedulingActionConfig {
pub enabled: bool,
pub mode: SchedulingMode,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum SchedulingMode {
PersistOnly,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChannelConfig {
pub url: String,
#[serde(default)]
pub body: String,
#[serde(default)]
pub headers: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MessagingActionConfig {
pub enabled: bool,
pub timeout_ms: u64,
#[serde(deserialize_with = "deserialize_channels", default)]
pub channels: HashMap<String, ChannelConfig>,
}
fn deserialize_channels<'de, D>(deserializer: D) -> Result<HashMap<String, ChannelConfig>, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum ChannelEntry {
Full(ChannelConfig),
UrlOnly(String),
}
let raw: HashMap<String, ChannelEntry> = HashMap::deserialize(deserializer)?;
Ok(raw
.into_iter()
.map(|(k, v)| {
let config = match v {
ChannelEntry::Full(c) => c,
ChannelEntry::UrlOnly(url) => ChannelConfig {
url,
body: String::new(),
headers: HashMap::new(),
},
};
(k, config)
})
.collect())
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ChannelIntelligenceConfig {
#[serde(default)]
pub relays: Vec<RelayEntry>,
#[serde(default)]
pub transports: Vec<TransportEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransportEntry {
pub id: String,
pub label: String,
pub preset: String,
#[serde(default = "default_relay_namespace")]
pub namespace: String,
#[serde(default)]
pub credential: String,
#[serde(default)]
pub signing_secret: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RelayEntry {
pub id: String,
pub label: String,
pub url: String,
#[serde(default = "default_relay_namespace")]
pub namespace: String,
#[serde(default)]
pub api_key: String,
#[serde(default = "default_relay_initial_backoff_ms")]
pub initial_backoff_ms: u64,
#[serde(default = "default_relay_max_backoff_ms")]
pub max_backoff_ms: u64,
}
fn default_relay_namespace() -> String {
"personal".to_string()
}
fn default_relay_initial_backoff_ms() -> u64 {
1_000
}
fn default_relay_max_backoff_ms() -> u64 {
60_000
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AgentsConfig {
#[serde(default)]
pub delegates: Vec<AgentEntry>,
#[serde(default)]
pub fallbacks: Vec<String>,
#[serde(default = "default_retry_on_timeout")]
pub retry_on_timeout: bool,
#[serde(default = "default_auto_discovery")]
pub auto_discovery: bool,
#[serde(default)]
pub discovery_overrides: std::collections::HashMap<String, AgentDiscoveryOverride>,
}
fn default_retry_on_timeout() -> bool {
true
}
fn default_auto_discovery() -> bool {
true
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AgentDiscoveryOverride {
#[serde(default)]
pub binary: Option<String>,
#[serde(default)]
pub disabled: bool,
#[serde(default)]
pub args: Option<Vec<String>>,
#[serde(default)]
pub prompt_via_stdin: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentEntry {
pub name: String,
pub kind: String,
#[serde(default)]
pub alias: Option<String>,
#[serde(default)]
pub binary: String,
#[serde(default)]
pub args: Vec<String>,
#[serde(default)]
pub workdir: Option<String>,
#[serde(default = "default_prompt_via_stdin")]
pub prompt_via_stdin: bool,
#[serde(default)]
pub tags: Vec<String>,
}
fn default_prompt_via_stdin() -> bool {
true
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProactivityConfig {
pub enabled: bool,
pub max_per_day: u32,
pub min_interval_minutes: u32,
pub quiet_hours: QuietHoursConfig,
#[serde(default)]
pub delivery: DeliveryConfig,
#[serde(default)]
pub open_loop: OpenLoopDetectionConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OpenLoopDetectionConfig {
pub enabled: bool,
pub scan_window_hours: u32,
pub resolution_window_hours: u32,
pub check_interval_minutes: u32,
}
impl Default for OpenLoopDetectionConfig {
fn default() -> Self {
Self {
enabled: true,
scan_window_hours: 72,
resolution_window_hours: 24,
check_interval_minutes: 120,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeliveryConfig {
pub outbox: bool,
pub broadcast: bool,
pub webhook_channels: Vec<String>,
pub max_outbox_age_days: u32,
}
impl Default for DeliveryConfig {
fn default() -> Self {
Self {
outbox: true,
broadcast: true,
webhook_channels: Vec::new(),
max_outbox_age_days: 7,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuietHoursConfig {
pub start: String,
pub end: String,
#[serde(default = "default_timezone")]
pub timezone: String,
}
fn default_timezone() -> String {
"UTC".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiKeyConfig {
pub key: String,
pub name: String,
pub permissions: Vec<String>,
}
impl ApiKeyConfig {
pub fn has_permission(&self, perm: &str) -> bool {
self.permissions.iter().any(|p| p == perm)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccessConfig {
pub api_keys: Vec<ApiKeyConfig>,
}
impl AccessConfig {
pub fn find_key(&self, key: &str) -> Option<&ApiKeyConfig> {
self.api_keys.iter().find(|k| k.key == key)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdaptersConfig {
pub http: HttpAdapterConfig,
pub ws: WebSocketAdapterConfig,
pub mcp: McpAdapterConfig,
pub grpc: GrpcAdapterConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HttpAdapterConfig {
pub enabled: bool,
pub host: String,
pub port: u16,
pub cors: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebSocketAdapterConfig {
pub enabled: bool,
pub port: u16,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpAdapterConfig {
pub enabled: bool,
pub stdio: bool,
pub http: bool,
pub port: u16,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GrpcAdapterConfig {
pub enabled: bool,
pub port: u16,
}
impl BrainConfig {}
mod loader;
#[cfg(test)]
mod tests;