pub(crate) 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,
#[serde(default)]
pub confirm: ConfirmConfig,
#[serde(default)]
pub identity: identity::IdentityConfig,
#[serde(default)]
pub reflex: ReflexConfig,
#[serde(default)]
pub logging: LoggingConfig,
#[serde(default)]
pub learning: LearningConfig,
#[serde(default)]
pub observability: ObservabilityConfig,
#[serde(default)]
pub monitoring: MonitoringConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct LearningConfig {
#[serde(default)]
pub capability_fitness: CapabilityFitnessConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CapabilityFitnessConfig {
#[serde(default = "CapabilityFitnessConfig::default_enabled")]
pub enabled: bool,
#[serde(default = "CapabilityFitnessConfig::default_half_life_days")]
pub half_life_days: f64,
}
impl CapabilityFitnessConfig {
fn default_enabled() -> bool {
true
}
fn default_half_life_days() -> f64 {
30.0
}
pub fn half_life_hours(&self) -> f64 {
self.half_life_days * 24.0
}
}
impl Default for CapabilityFitnessConfig {
fn default() -> Self {
Self {
enabled: Self::default_enabled(),
half_life_days: Self::default_half_life_days(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ObservabilityConfig {
#[serde(default = "ObservabilityConfig::default_resource_sample_secs")]
pub resource_sample_secs: u64,
#[serde(default)]
pub thresholds: ResourceThresholds,
#[serde(default)]
pub log_sampling: LogSamplingConfig,
}
impl ObservabilityConfig {
fn default_resource_sample_secs() -> u64 {
30
}
}
impl Default for ObservabilityConfig {
fn default() -> Self {
Self {
resource_sample_secs: Self::default_resource_sample_secs(),
thresholds: ResourceThresholds::default(),
log_sampling: LogSamplingConfig::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogSamplingConfig {
#[serde(default = "LogSamplingConfig::default_high_volume_1_in_n")]
pub high_volume_1_in_n: u32,
}
impl LogSamplingConfig {
fn default_high_volume_1_in_n() -> u32 {
1
}
}
impl Default for LogSamplingConfig {
fn default() -> Self {
Self {
high_volume_1_in_n: Self::default_high_volume_1_in_n(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceThresholds {
#[serde(default = "ResourceThresholds::default_rss_mb")]
pub rss_mb: u64,
#[serde(default = "ResourceThresholds::default_cpu_pct")]
pub cpu_pct: f64,
#[serde(default = "ResourceThresholds::default_disk_mb")]
pub disk_mb: u64,
#[serde(default = "ResourceThresholds::default_open_fds")]
pub open_fds: u64,
}
impl ResourceThresholds {
fn default_rss_mb() -> u64 {
2048
}
fn default_cpu_pct() -> f64 {
90.0
}
fn default_disk_mb() -> u64 {
10_240
}
fn default_open_fds() -> u64 {
1024
}
}
impl Default for ResourceThresholds {
fn default() -> Self {
Self {
rss_mb: Self::default_rss_mb(),
cpu_pct: Self::default_cpu_pct(),
disk_mb: Self::default_disk_mb(),
open_fds: Self::default_open_fds(),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MonitoringConfig {
#[serde(default)]
pub services: Vec<ServiceCheck>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ServiceCheckKind {
#[default]
Http,
Tcp,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceCheck {
pub name: String,
#[serde(default)]
pub kind: ServiceCheckKind,
pub target: String,
#[serde(default = "ServiceCheck::default_interval_secs")]
pub interval_secs: u64,
#[serde(default = "ServiceCheck::default_timeout_secs")]
pub timeout_secs: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expect_status: Option<u16>,
}
impl ServiceCheck {
fn default_interval_secs() -> u64 {
60
}
fn default_timeout_secs() -> u64 {
10
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
#[serde(default = "LoggingConfig::default_level")]
pub level: String,
#[serde(default)]
pub targets: HashMap<String, String>,
#[serde(default)]
pub format: LogFormat,
#[serde(default)]
pub rotation: LogRotation,
}
impl LoggingConfig {
fn default_level() -> String {
"info".to_string()
}
}
impl Default for LoggingConfig {
fn default() -> Self {
Self {
level: Self::default_level(),
targets: HashMap::new(),
format: LogFormat::default(),
rotation: LogRotation::default(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum LogFormat {
#[default]
Pretty,
Json,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum LogRotation {
#[default]
Daily,
Hourly,
Never,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ReflexConfig {
#[serde(default)]
pub fs: Vec<FsReflexEntry>,
#[serde(default)]
pub cron: CronReflexEntry,
#[serde(default)]
pub sys: SysReflexEntry,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FsReflexEntry {
pub name: String,
pub paths: Vec<String>,
#[serde(default)]
pub recursive: bool,
#[serde(default = "FsReflexEntry::default_debounce_ms")]
pub debounce_ms: u64,
}
impl FsReflexEntry {
pub fn default_debounce_ms() -> u64 {
200
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CronReflexEntry {
#[serde(default)]
pub enabled: bool,
#[serde(default = "CronReflexEntry::default_poll_seconds")]
pub poll_interval_seconds: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub namespace_filter: Option<String>,
}
impl CronReflexEntry {
pub fn default_poll_seconds() -> u64 {
60
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SysReflexEntry {
#[serde(default)]
pub enabled: bool,
#[serde(default = "SysReflexEntry::default_poll_seconds")]
pub poll_interval_seconds: u64,
#[serde(default)]
pub rules: Vec<SysReflexRuleEntry>,
}
impl SysReflexEntry {
pub fn default_poll_seconds() -> u64 {
30
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum SysReflexRuleEntry {
BatteryBelow { threshold: u8 },
OnAcChanged,
NetworkChanged,
LockChanged,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ConfirmConfig {
#[serde(default)]
pub standing_approvals: Vec<StandingApprovalDecl>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StandingApprovalDecl {
pub agent_id: String,
pub verb_ns: String,
pub verb_action: String,
#[serde(default)]
pub note: Option<String>,
}
#[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,
#[serde(default = "HnswConfig::default_max_elements")]
pub max_elements: u32,
}
impl HnswConfig {
pub fn default_max_elements() -> u32 {
100_000
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LlmConfig {
#[deprecated(
note = "Set `llm.providers[]` instead. Single-provider mode is still functional but no longer the recommended shape."
)]
pub provider: String,
#[deprecated(
note = "Set `llm.providers[].model` (and optionally `preferred_models`) instead."
)]
pub model: String,
#[deprecated(
note = "Set `llm.providers[].base_url` instead. Embedder transport selection still reads this field as a fallback."
)]
pub base_url: String,
pub temperature: f64,
pub max_tokens: u32,
#[serde(default = "default_context_window")]
pub context_window: usize,
#[deprecated(
note = "Move credentials to `llm.providers[].api_key_file` (or `api_key_file` here) — the YAML field gets backed up and replicated."
)]
#[serde(default)]
pub api_key: String,
#[serde(default)]
pub api_key_file: Option<std::path::PathBuf>,
#[serde(default)]
pub providers: Vec<ProviderEntry>,
}
pub(crate) fn default_context_window() -> usize {
8192
}
#[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,
#[serde(default)]
pub api_key_file: Option<std::path::PathBuf>,
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 semantic: SemanticConfig,
pub search: SearchConfig,
pub consolidation: ConsolidationConfig,
}
#[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,
#[serde(default)]
pub allowed_paths: Vec<String>,
}
#[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>,
#[serde(default)]
pub capabilities: Option<CapabilitiesOverride>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CapabilitiesOverride {
#[serde(default)]
pub tags: Vec<String>,
#[serde(default)]
pub languages: Vec<String>,
#[serde(default = "default_capability_concurrency")]
pub max_concurrency: u32,
#[serde(default)]
pub needs_network: bool,
}
fn default_capability_concurrency() -> u32 {
1
}
#[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>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub agent_id: Option<String>,
}
impl ApiKeyConfig {
pub fn has_permission(&self, perm: &str) -> bool {
if self.permissions.iter().any(|p| p == "admin") {
return true;
}
self.permissions.iter().any(|p| p == perm)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccessConfig {
pub api_keys: Vec<ApiKeyConfig>,
#[serde(default)]
pub rate_limit: ClientRateLimitConfig,
}
impl AccessConfig {
pub fn find_key(&self, key: &str) -> Option<&ApiKeyConfig> {
crate::auth::find_key_ct(&self.api_keys, key)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientRateLimitConfig {
#[serde(default = "ClientRateLimitConfig::default_enabled")]
pub enabled: bool,
#[serde(default = "ClientRateLimitConfig::default_tokens_per_refill")]
pub tokens_per_refill: u32,
#[serde(default = "ClientRateLimitConfig::default_refill_interval_ms")]
pub refill_interval_ms: u64,
#[serde(default = "ClientRateLimitConfig::default_burst_capacity")]
pub burst_capacity: u32,
}
impl Default for ClientRateLimitConfig {
fn default() -> Self {
Self {
enabled: Self::default_enabled(),
tokens_per_refill: Self::default_tokens_per_refill(),
refill_interval_ms: Self::default_refill_interval_ms(),
burst_capacity: Self::default_burst_capacity(),
}
}
}
impl ClientRateLimitConfig {
pub fn default_enabled() -> bool {
true
}
pub fn default_tokens_per_refill() -> u32 {
60
}
pub fn default_refill_interval_ms() -> u64 {
60_000
}
pub fn default_burst_capacity() -> u32 {
20
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdaptersConfig {
pub http: HttpAdapterConfig,
pub ws: WebSocketAdapterConfig,
pub mcp: McpAdapterConfig,
pub grpc: GrpcAdapterConfig,
#[serde(default = "TerminalAdapterConfig::default_enabled")]
pub terminal: TerminalAdapterConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HttpAdapterConfig {
pub enabled: bool,
pub host: String,
pub port: u16,
pub cors: bool,
#[serde(default)]
pub sse_redact_previews: 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 port: u16,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GrpcAdapterConfig {
pub enabled: bool,
pub port: u16,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TerminalAdapterConfig {
pub enabled: bool,
pub port: u16,
}
impl TerminalAdapterConfig {
pub fn default_enabled() -> Self {
Self {
enabled: true,
port: 19793,
}
}
}
impl Default for TerminalAdapterConfig {
fn default() -> Self {
Self::default_enabled()
}
}
impl BrainConfig {}
mod loader;
pub mod migrate;
#[cfg(test)]
mod tests;