1const DEFAULT_CONFIG: &str = include_str!("../default.yaml");
11
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct BrainConfig {
18 pub brain: GeneralConfig,
19 pub storage: StorageConfig,
20 pub llm: LlmConfig,
21 pub embedding: EmbeddingConfig,
22 pub memory: MemoryConfig,
23 pub encryption: EncryptionConfig,
24 pub security: SecurityConfig,
25 pub actions: ActionsConfig,
26 pub proactivity: ProactivityConfig,
27 pub adapters: AdaptersConfig,
28 pub access: AccessConfig,
29 #[serde(default)]
30 pub channel: ChannelIntelligenceConfig,
31 #[serde(default)]
32 pub agents: AgentsConfig,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct GeneralConfig {
37 pub version: String,
38 pub data_dir: String,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct StorageConfig {
43 pub ruvector_path: String,
44 pub sqlite_path: String,
45 pub hnsw: HnswConfig,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct HnswConfig {
50 pub ef_construction: u32,
51 pub m: u32,
52 pub ef_search: u32,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct LlmConfig {
57 pub provider: String,
58 pub model: String,
59 pub base_url: String,
60 pub temperature: f64,
61 pub max_tokens: u32,
62 #[serde(default)]
65 pub api_key: String,
66 #[serde(default)]
71 pub providers: Vec<ProviderEntry>,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct ProviderEntry {
82 pub name: String,
84 pub kind: String,
86 #[serde(default)]
89 pub base_url: String,
90 #[serde(default)]
92 pub api_key: String,
93 pub model: String,
95 #[serde(default)]
98 pub preferred_models: Vec<String>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct EmbeddingConfig {
103 pub model: String,
107 pub dimensions: u32,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct MemoryConfig {
114 pub episodic: EpisodicConfig,
115 pub semantic: SemanticConfig,
116 pub search: SearchConfig,
117 pub consolidation: ConsolidationConfig,
118}
119
120#[derive(Debug, Clone, Default, Serialize, Deserialize)]
121pub struct EpisodicConfig {}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct SemanticConfig {
125 pub similarity_threshold: f64,
126 pub max_results: u32,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct SearchConfig {
131 pub rrf_k: u32,
132 #[serde(default = "default_pre_fusion_limit")]
134 pub pre_fusion_limit: u32,
135 #[serde(default = "default_importance_weight")]
137 pub importance_weight: f64,
138 #[serde(default = "default_recency_weight")]
140 pub recency_weight: f64,
141 #[serde(default = "default_decay_rate")]
143 pub decay_rate: f64,
144}
145
146fn default_pre_fusion_limit() -> u32 {
147 50
148}
149fn default_importance_weight() -> f64 {
150 0.3
151}
152fn default_recency_weight() -> f64 {
153 0.2
154}
155fn default_decay_rate() -> f64 {
156 0.01
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct ConsolidationConfig {
161 pub enabled: bool,
162 pub interval_hours: u32,
163 pub forgetting_threshold: f64,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct EncryptionConfig {
168 pub enabled: bool,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct SecurityConfig {
173 pub exec_allowlist: Vec<String>,
174 pub exec_timeout_seconds: u32,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct ActionsConfig {
179 pub web_search: WebSearchActionConfig,
180 pub scheduling: SchedulingActionConfig,
181 pub messaging: MessagingActionConfig,
182 #[serde(default)]
183 pub resilience: ResilienceConfig,
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct ResilienceConfig {
188 pub max_retries: u32,
189 pub retry_base_ms: u64,
190 pub circuit_breaker_threshold: u32,
191 pub circuit_breaker_cooldown_secs: u64,
192}
193
194impl Default for ResilienceConfig {
195 fn default() -> Self {
196 Self {
197 max_retries: 2,
198 retry_base_ms: 500,
199 circuit_breaker_threshold: 5,
200 circuit_breaker_cooldown_secs: 60,
201 }
202 }
203}
204
205#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
206#[serde(rename_all = "snake_case")]
207pub enum WebSearchProvider {
208 #[default]
211 #[serde(alias = "duckduckgo", rename = "duckduckgo")]
212 DuckDuckGo,
213 Searxng,
214 Tavily,
215 Custom,
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct WebSearchActionConfig {
220 pub enabled: bool,
221 #[serde(default)]
222 pub provider: WebSearchProvider,
223 pub endpoint: String,
224 #[serde(default)]
225 pub api_key: String,
226 pub timeout_ms: u64,
227 pub default_top_k: usize,
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
231pub struct SchedulingActionConfig {
232 pub enabled: bool,
233 pub mode: SchedulingMode,
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
237#[serde(rename_all = "snake_case")]
238pub enum SchedulingMode {
239 PersistOnly,
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
243pub struct ChannelConfig {
244 pub url: String,
245 #[serde(default)]
246 pub body: String,
247 #[serde(default)]
248 pub headers: HashMap<String, String>,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct MessagingActionConfig {
253 pub enabled: bool,
254 pub timeout_ms: u64,
255 #[serde(deserialize_with = "deserialize_channels", default)]
256 pub channels: HashMap<String, ChannelConfig>,
257}
258
259fn deserialize_channels<'de, D>(deserializer: D) -> Result<HashMap<String, ChannelConfig>, D::Error>
261where
262 D: serde::Deserializer<'de>,
263{
264 #[derive(Deserialize)]
265 #[serde(untagged)]
266 enum ChannelEntry {
267 Full(ChannelConfig),
268 UrlOnly(String),
269 }
270
271 let raw: HashMap<String, ChannelEntry> = HashMap::deserialize(deserializer)?;
272 Ok(raw
273 .into_iter()
274 .map(|(k, v)| {
275 let config = match v {
276 ChannelEntry::Full(c) => c,
277 ChannelEntry::UrlOnly(url) => ChannelConfig {
278 url,
279 body: String::new(),
280 headers: HashMap::new(),
281 },
282 };
283 (k, config)
284 })
285 .collect())
286}
287
288#[derive(Debug, Clone, Default, Serialize, Deserialize)]
296pub struct ChannelIntelligenceConfig {
297 #[serde(default)]
298 pub relays: Vec<RelayEntry>,
299 #[serde(default)]
304 pub transports: Vec<TransportEntry>,
305}
306
307#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct TransportEntry {
311 pub id: String,
313 pub label: String,
315 pub preset: String,
317 #[serde(default = "default_relay_namespace")]
319 pub namespace: String,
320 #[serde(default)]
324 pub credential: String,
325 #[serde(default)]
329 pub signing_secret: Option<String>,
330}
331
332#[derive(Debug, Clone, Serialize, Deserialize)]
334pub struct RelayEntry {
335 pub id: String,
337 pub label: String,
339 pub url: String,
341 #[serde(default = "default_relay_namespace")]
343 pub namespace: String,
344 #[serde(default)]
346 pub api_key: String,
347 #[serde(default = "default_relay_initial_backoff_ms")]
349 pub initial_backoff_ms: u64,
350 #[serde(default = "default_relay_max_backoff_ms")]
352 pub max_backoff_ms: u64,
353}
354
355fn default_relay_namespace() -> String {
356 "personal".to_string()
357}
358fn default_relay_initial_backoff_ms() -> u64 {
359 1_000
360}
361fn default_relay_max_backoff_ms() -> u64 {
362 60_000
363}
364
365#[derive(Debug, Clone, Default, Serialize, Deserialize)]
368pub struct AgentsConfig {
369 #[serde(default)]
372 pub delegates: Vec<AgentEntry>,
373 #[serde(default)]
377 pub fallbacks: Vec<String>,
378 #[serde(default = "default_retry_on_timeout")]
382 pub retry_on_timeout: bool,
383 #[serde(default = "default_auto_discovery")]
387 pub auto_discovery: bool,
388 #[serde(default)]
391 pub discovery_overrides: std::collections::HashMap<String, AgentDiscoveryOverride>,
392}
393
394fn default_retry_on_timeout() -> bool {
395 true
396}
397
398fn default_auto_discovery() -> bool {
399 true
400}
401
402#[derive(Debug, Clone, Default, Serialize, Deserialize)]
405pub struct AgentDiscoveryOverride {
406 #[serde(default)]
408 pub binary: Option<String>,
409 #[serde(default)]
411 pub disabled: bool,
412 #[serde(default)]
414 pub args: Option<Vec<String>>,
415 #[serde(default)]
417 pub prompt_via_stdin: Option<bool>,
418}
419
420#[derive(Debug, Clone, Serialize, Deserialize)]
424pub struct AgentEntry {
425 pub name: String,
427 pub kind: String,
429 #[serde(default)]
432 pub alias: Option<String>,
433 #[serde(default)]
435 pub binary: String,
436 #[serde(default)]
439 pub args: Vec<String>,
440 #[serde(default)]
443 pub workdir: Option<String>,
444 #[serde(default = "default_prompt_via_stdin")]
448 pub prompt_via_stdin: bool,
449 #[serde(default)]
451 pub tags: Vec<String>,
452}
453
454fn default_prompt_via_stdin() -> bool {
455 true
456}
457
458#[derive(Debug, Clone, Serialize, Deserialize)]
459pub struct ProactivityConfig {
460 pub enabled: bool,
461 pub max_per_day: u32,
462 pub min_interval_minutes: u32,
463 pub quiet_hours: QuietHoursConfig,
464 #[serde(default)]
465 pub delivery: DeliveryConfig,
466 #[serde(default)]
467 pub open_loop: OpenLoopDetectionConfig,
468}
469
470#[derive(Debug, Clone, Serialize, Deserialize)]
472pub struct OpenLoopDetectionConfig {
473 pub enabled: bool,
475 pub scan_window_hours: u32,
477 pub resolution_window_hours: u32,
479 pub check_interval_minutes: u32,
481}
482
483impl Default for OpenLoopDetectionConfig {
484 fn default() -> Self {
485 Self {
486 enabled: true,
487 scan_window_hours: 72,
488 resolution_window_hours: 24,
489 check_interval_minutes: 120,
490 }
491 }
492}
493
494#[derive(Debug, Clone, Serialize, Deserialize)]
496pub struct DeliveryConfig {
497 pub outbox: bool,
499 pub broadcast: bool,
501 pub webhook_channels: Vec<String>,
503 pub max_outbox_age_days: u32,
505}
506
507impl Default for DeliveryConfig {
508 fn default() -> Self {
509 Self {
510 outbox: true,
511 broadcast: true,
512 webhook_channels: Vec::new(),
513 max_outbox_age_days: 7,
514 }
515 }
516}
517
518#[derive(Debug, Clone, Serialize, Deserialize)]
519pub struct QuietHoursConfig {
520 pub start: String,
521 pub end: String,
522 #[serde(default = "default_timezone")]
523 pub timezone: String,
524}
525
526fn default_timezone() -> String {
527 "UTC".to_string()
528}
529
530#[derive(Debug, Clone, Serialize, Deserialize)]
532pub struct ApiKeyConfig {
533 pub key: String,
535 pub name: String,
537 pub permissions: Vec<String>,
539}
540
541impl ApiKeyConfig {
542 pub fn has_permission(&self, perm: &str) -> bool {
544 self.permissions.iter().any(|p| p == perm)
545 }
546}
547
548#[derive(Debug, Clone, Serialize, Deserialize)]
550pub struct AccessConfig {
551 pub api_keys: Vec<ApiKeyConfig>,
552}
553
554impl AccessConfig {
555 pub fn find_key(&self, key: &str) -> Option<&ApiKeyConfig> {
557 self.api_keys.iter().find(|k| k.key == key)
558 }
559}
560
561#[derive(Debug, Clone, Serialize, Deserialize)]
562pub struct AdaptersConfig {
563 pub http: HttpAdapterConfig,
564 pub ws: WebSocketAdapterConfig,
565 pub mcp: McpAdapterConfig,
566 pub grpc: GrpcAdapterConfig,
567}
568
569#[derive(Debug, Clone, Serialize, Deserialize)]
570pub struct HttpAdapterConfig {
571 pub enabled: bool,
572 pub host: String,
573 pub port: u16,
574 pub cors: bool,
575}
576
577#[derive(Debug, Clone, Serialize, Deserialize)]
578pub struct WebSocketAdapterConfig {
579 pub enabled: bool,
580 pub port: u16,
581}
582
583#[derive(Debug, Clone, Serialize, Deserialize)]
584pub struct McpAdapterConfig {
585 pub enabled: bool,
586 pub stdio: bool,
587 pub http: bool,
588 pub port: u16,
589}
590
591#[derive(Debug, Clone, Serialize, Deserialize)]
592pub struct GrpcAdapterConfig {
593 pub enabled: bool,
594 pub port: u16,
595}
596
597impl BrainConfig {}
598
599mod loader;
600
601#[cfg(test)]
602mod tests;