1use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct LlmConfig {
9 pub provider: String,
11 pub model: String,
13 pub api_key_env: String,
15 pub base_url: Option<String>,
17 pub timeout_secs: u64,
19 pub max_tokens: u32,
21 pub temperature: f32,
23}
24
25impl Default for LlmConfig {
26 fn default() -> Self {
27 Self {
28 provider: "openai".to_string(),
29 model: "gpt-4o-mini".to_string(),
30 api_key_env: "OPENAI_API_KEY".to_string(),
31 base_url: None,
32 timeout_secs: 60,
33 max_tokens: 4096,
34 temperature: 0.3,
35 }
36 }
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct AgentConfig {
42 pub enabled: bool,
44 pub namespace: String,
46 pub agent_type: String,
48 pub inbox_dir: String,
50 pub scan_interval_secs: u64,
52 pub consolidation_interval_mins: u64,
54 pub consolidation_batch_size: usize,
56 pub query_context_limit: usize,
58}
59
60impl Default for AgentConfig {
61 fn default() -> Self {
62 Self {
63 enabled: false,
64 namespace: "nexus-agent".to_string(),
65 agent_type: std::env::var("NEXUS_AGENT_TYPE")
66 .unwrap_or_else(|_| "nexus-agent".to_string()),
67 inbox_dir: "./inbox".to_string(),
68 scan_interval_secs: 5,
69 consolidation_interval_mins: 30,
70 consolidation_batch_size: 10,
71 query_context_limit: 50,
72 }
73 }
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct CognitionConfig {
79 pub enabled: bool,
81 pub auto_runtime_enabled: bool,
83 pub derive_enabled: bool,
85 pub derive_interval_secs: u64,
87 pub digest_enabled: bool,
89 pub digest_interval_secs: u64,
91 pub reflect_enabled: bool,
93 pub reflect_interval_secs: u64,
95 pub activity_distill_enabled: bool,
97 pub dream_on_session_end: bool,
99 pub checkpoint_flush_enabled: bool,
101 pub runtime_idle_timeout_secs: u64,
103 pub max_job_batch: usize,
105 pub lease_ttl_secs: u64,
107 pub working_representation_max_items: usize,
109 pub digest_short_target_tokens: usize,
111 pub digest_long_target_tokens: usize,
113 pub direct_enrichment_timeout_secs: u64,
115 pub activity_distill_min_events: usize,
117 pub activity_distill_max_events: usize,
119 pub include_raw_by_default: bool,
121 pub session_end_dream_timeout_secs: u64,
123 pub retry_buffer_drain_limit: usize,
125 pub contradiction_belief_revision_enabled: bool,
127 pub contradiction_confidence_penalty: f32,
129 pub memory_decay_enabled: bool,
131 pub memory_decay_age_days: u64,
133 pub memory_decay_access_boost_days: u64,
135 pub adaptive_dream_enabled: bool,
137 pub adaptive_dream_min_interval_secs: u64,
139 pub adaptive_dream_max_interval_secs: u64,
141}
142
143impl Default for CognitionConfig {
144 fn default() -> Self {
145 Self {
146 enabled: true,
147 auto_runtime_enabled: true,
148 derive_enabled: true,
149 derive_interval_secs: 60,
150 digest_enabled: true,
151 digest_interval_secs: 300,
152 reflect_enabled: true,
153 reflect_interval_secs: 600,
154 activity_distill_enabled: true,
155 dream_on_session_end: true,
156 checkpoint_flush_enabled: true,
157 runtime_idle_timeout_secs: 900,
158 max_job_batch: 8,
159 lease_ttl_secs: 120,
160 working_representation_max_items: 24,
161 digest_short_target_tokens: 600,
162 digest_long_target_tokens: 1800,
163 direct_enrichment_timeout_secs: 8,
164 activity_distill_min_events: 8,
165 activity_distill_max_events: 60,
166 include_raw_by_default: false,
167 session_end_dream_timeout_secs: 8,
168 retry_buffer_drain_limit: 8,
169 contradiction_belief_revision_enabled: true,
170 contradiction_confidence_penalty: 0.15,
171 memory_decay_enabled: true,
172 memory_decay_age_days: 90,
173 memory_decay_access_boost_days: 30,
174 adaptive_dream_enabled: true,
175 adaptive_dream_min_interval_secs: 60,
176 adaptive_dream_max_interval_secs: 1800,
177 }
178 }
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct DreamTriggerConfig {
184 pub nap_on_session_end: bool,
186 pub nap_idle_timeout_secs: u64,
188 pub dream_memory_threshold: usize,
190 pub deep_dream_cooldown_hours: u64,
192 pub deep_dream_inactivity_mins: u64,
194}
195
196impl Default for DreamTriggerConfig {
197 fn default() -> Self {
198 Self {
199 nap_on_session_end: true,
200 nap_idle_timeout_secs: 600,
201 dream_memory_threshold: 20,
202 deep_dream_cooldown_hours: 24,
203 deep_dream_inactivity_mins: 30,
204 }
205 }
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize)]
210#[serde(default)]
211pub struct CognitiveSystemConfig {
212 pub enabled: bool,
213 pub bootstrap_mode: String,
215 pub dream_triggers: DreamTriggerConfig,
217 pub hot_cache_max_entries: usize,
219 pub context_allocation_pct: f32,
221 pub mid_session_rescore_enabled: bool,
223 pub rescore_turn_interval: u32,
225 pub rescore_drift_threshold: f32,
227 pub similarity_threshold: f32,
229 pub subconscious_mode: String,
231}
232
233impl Default for CognitiveSystemConfig {
234 fn default() -> Self {
235 Self {
236 enabled: true,
237 bootstrap_mode: "silent".to_string(),
238 dream_triggers: DreamTriggerConfig::default(),
239 hot_cache_max_entries: 20,
240 context_allocation_pct: 0.10,
241 mid_session_rescore_enabled: true,
242 rescore_turn_interval: 5,
243 rescore_drift_threshold: 0.70,
244 similarity_threshold: 0.85,
245 subconscious_mode: "whisper".to_string(),
246 }
247 }
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize, Default)]
252pub struct Config {
253 pub database: DatabaseConfig,
255
256 pub server: ServerConfig,
258
259 pub embedding: EmbeddingConfig,
261
262 pub sync: SyncConfig,
264
265 pub llm: LlmConfig,
267
268 pub agent: AgentConfig,
270
271 pub cognition: CognitionConfig,
273
274 pub cognitive_system: CognitiveSystemConfig,
276}
277
278impl Config {
279 pub fn from_env() -> crate::Result<Self> {
281 let mut config = Self::default();
282
283 if let Ok(path) = std::env::var("NEXUS_DATABASE_PATH") {
284 config.database.path = PathBuf::from(path);
285 }
286
287 if let Ok(host) = std::env::var("NEXUS_HOST") {
288 config.server.host = host;
289 }
290
291 if let Ok(port) = std::env::var("NEXUS_PORT") {
292 config.server.port = port.parse().unwrap_or(8768);
293 }
294
295 if let Ok(enabled) = std::env::var("NEXUS_EMBEDDINGS_ENABLED") {
296 config.embedding.enabled = enabled.parse().unwrap_or(false);
297 }
298
299 if let Ok(backend) = std::env::var("NEXUS_EMBEDDING_BACKEND") {
300 config.embedding.backend = backend;
301 }
302
303 if let Ok(provider) = std::env::var("NEXUS_EMBEDDING_PROVIDER") {
304 config.embedding.provider = provider;
305 }
306
307 if let Ok(model) = std::env::var("NEXUS_EMBEDDING_MODEL") {
308 config.embedding.model = model;
309 }
310
311 if let Ok(key_env) = std::env::var("NEXUS_EMBEDDING_API_KEY_ENV") {
312 config.embedding.api_key_env = key_env;
313 }
314
315 if let Ok(base_url) = std::env::var("NEXUS_EMBEDDING_BASE_URL") {
316 config.embedding.base_url = Some(base_url);
317 }
318
319 if let Ok(dimension) = std::env::var("NEXUS_EMBEDDING_DIMENSION") {
320 config.embedding.dimension = dimension
321 .parse()
322 .unwrap_or(EmbeddingConfig::default().dimension);
323 }
324
325 if let Ok(timeout) = std::env::var("NEXUS_EMBEDDING_TIMEOUT_SECS") {
326 config.embedding.timeout_secs = timeout
327 .parse()
328 .unwrap_or(EmbeddingConfig::default().timeout_secs);
329 }
330
331 if let Ok(model_path) = std::env::var("NEXUS_EMBEDDING_MODEL_PATH") {
332 config.embedding.local_model_path = Some(model_path);
333 }
334
335 if let Ok(tokenizer_path) = std::env::var("NEXUS_TOKENIZER_PATH") {
336 config.embedding.local_tokenizer_path = Some(tokenizer_path);
337 }
338
339 if let Ok(policy) = std::env::var("NEXUS_SYNC_POLICY") {
340 config.sync.policy = policy;
341 }
342
343 if let Ok(provider) = std::env::var("NEXUS_LLM_PROVIDER") {
345 config.llm.provider = provider;
346 }
347 if let Ok(model) = std::env::var("NEXUS_LLM_MODEL") {
348 config.llm.model = model;
349 }
350 if let Ok(key_env) = std::env::var("NEXUS_LLM_API_KEY_ENV") {
351 config.llm.api_key_env = key_env;
352 }
353 if let Ok(base_url) = std::env::var("NEXUS_LLM_BASE_URL") {
354 config.llm.base_url = Some(base_url);
355 }
356
357 if let Ok(enabled) = std::env::var("NEXUS_AGENT_ENABLED") {
359 config.agent.enabled = enabled.parse().unwrap_or(false);
360 }
361 if let Ok(namespace) = std::env::var("NEXUS_AGENT_NAMESPACE") {
362 config.agent.namespace = namespace;
363 }
364 if let Ok(inbox) = std::env::var("NEXUS_AGENT_INBOX_DIR") {
365 config.agent.inbox_dir = inbox;
366 }
367 if let Ok(interval) = std::env::var("NEXUS_AGENT_CONSOLIDATION_INTERVAL_MINS") {
368 config.agent.consolidation_interval_mins = interval
369 .parse()
370 .unwrap_or(AgentConfig::default().consolidation_interval_mins);
371 } else if let Ok(interval) = std::env::var("NEXUS_AGENT_CONSOLIDATION_INTERVAL") {
372 config.agent.consolidation_interval_mins = interval
374 .parse()
375 .unwrap_or(AgentConfig::default().consolidation_interval_mins);
376 }
377 if let Ok(interval) = std::env::var("NEXUS_AGENT_SCAN_INTERVAL_SECS") {
378 config.agent.scan_interval_secs = interval
379 .parse()
380 .unwrap_or(AgentConfig::default().scan_interval_secs);
381 } else if let Ok(interval) = std::env::var("NEXUS_AGENT_SCAN_INTERVAL") {
382 config.agent.scan_interval_secs = interval
384 .parse()
385 .unwrap_or(AgentConfig::default().scan_interval_secs);
386 }
387
388 if let Ok(enabled) = std::env::var("NEXUS_COGNITION_ENABLED") {
389 config.cognition.enabled = enabled.parse().unwrap_or(true);
390 }
391 if let Ok(enabled) = std::env::var("NEXUS_COGNITION_AUTO_RUNTIME_ENABLED") {
392 config.cognition.auto_runtime_enabled = enabled.parse().unwrap_or(true);
393 }
394 if let Ok(enabled) = std::env::var("NEXUS_COGNITION_DERIVE_ENABLED") {
395 config.cognition.derive_enabled = enabled.parse().unwrap_or(true);
396 }
397 if let Ok(interval) = std::env::var("NEXUS_COGNITION_DERIVE_INTERVAL_SECS") {
398 config.cognition.derive_interval_secs = interval
399 .parse()
400 .unwrap_or(CognitionConfig::default().derive_interval_secs);
401 }
402 if let Ok(enabled) = std::env::var("NEXUS_COGNITION_DIGEST_ENABLED") {
403 config.cognition.digest_enabled = enabled.parse().unwrap_or(true);
404 }
405 if let Ok(interval) = std::env::var("NEXUS_COGNITION_DIGEST_INTERVAL_SECS") {
406 config.cognition.digest_interval_secs = interval
407 .parse()
408 .unwrap_or(CognitionConfig::default().digest_interval_secs);
409 }
410 if let Ok(enabled) = std::env::var("NEXUS_COGNITION_REFLECT_ENABLED") {
411 config.cognition.reflect_enabled = enabled.parse().unwrap_or(true);
412 }
413 if let Ok(interval) = std::env::var("NEXUS_COGNITION_REFLECT_INTERVAL_SECS") {
414 config.cognition.reflect_interval_secs = interval
415 .parse()
416 .unwrap_or(CognitionConfig::default().reflect_interval_secs);
417 }
418 if let Ok(enabled) = std::env::var("NEXUS_COGNITION_ACTIVITY_DISTILL_ENABLED") {
419 config.cognition.activity_distill_enabled = enabled.parse().unwrap_or(true);
420 }
421 if let Ok(enabled) = std::env::var("NEXUS_COGNITION_DREAM_ON_SESSION_END") {
422 config.cognition.dream_on_session_end = enabled.parse().unwrap_or(true);
423 }
424 if let Ok(enabled) = std::env::var("NEXUS_COGNITION_CHECKPOINT_FLUSH_ENABLED") {
425 config.cognition.checkpoint_flush_enabled = enabled.parse().unwrap_or(true);
426 }
427 if let Ok(timeout) = std::env::var("NEXUS_COGNITION_RUNTIME_IDLE_TIMEOUT_SECS") {
428 config.cognition.runtime_idle_timeout_secs = timeout
429 .parse()
430 .unwrap_or(CognitionConfig::default().runtime_idle_timeout_secs);
431 }
432 if let Ok(batch) = std::env::var("NEXUS_COGNITION_MAX_JOB_BATCH") {
433 config.cognition.max_job_batch = batch
434 .parse()
435 .unwrap_or(CognitionConfig::default().max_job_batch);
436 }
437 if let Ok(ttl) = std::env::var("NEXUS_COGNITION_LEASE_TTL_SECS") {
438 config.cognition.lease_ttl_secs = ttl
439 .parse()
440 .unwrap_or(CognitionConfig::default().lease_ttl_secs);
441 }
442 if let Ok(items) = std::env::var("NEXUS_COGNITION_WORKING_REPRESENTATION_MAX_ITEMS") {
443 config.cognition.working_representation_max_items = items
444 .parse()
445 .unwrap_or(CognitionConfig::default().working_representation_max_items);
446 }
447 if let Ok(tokens) = std::env::var("NEXUS_COGNITION_DIGEST_SHORT_TARGET_TOKENS") {
448 config.cognition.digest_short_target_tokens = tokens
449 .parse()
450 .unwrap_or(CognitionConfig::default().digest_short_target_tokens);
451 }
452 if let Ok(tokens) = std::env::var("NEXUS_COGNITION_DIGEST_LONG_TARGET_TOKENS") {
453 config.cognition.digest_long_target_tokens = tokens
454 .parse()
455 .unwrap_or(CognitionConfig::default().digest_long_target_tokens);
456 }
457 if let Ok(timeout) = std::env::var("NEXUS_COGNITION_DIRECT_ENRICHMENT_TIMEOUT_SECS") {
458 config.cognition.direct_enrichment_timeout_secs = timeout
459 .parse()
460 .unwrap_or(CognitionConfig::default().direct_enrichment_timeout_secs);
461 }
462 if let Ok(events) = std::env::var("NEXUS_COGNITION_ACTIVITY_DISTILL_MIN_EVENTS") {
463 config.cognition.activity_distill_min_events = events
464 .parse()
465 .unwrap_or(CognitionConfig::default().activity_distill_min_events);
466 }
467 if let Ok(events) = std::env::var("NEXUS_COGNITION_ACTIVITY_DISTILL_MAX_EVENTS") {
468 config.cognition.activity_distill_max_events = events
469 .parse()
470 .unwrap_or(CognitionConfig::default().activity_distill_max_events);
471 }
472 if let Ok(include_raw) = std::env::var("NEXUS_COGNITION_INCLUDE_RAW_BY_DEFAULT") {
473 config.cognition.include_raw_by_default = include_raw.parse().unwrap_or(false);
474 }
475 if let Ok(timeout) = std::env::var("NEXUS_COGNITION_SESSION_END_DREAM_TIMEOUT_SECS") {
476 config.cognition.session_end_dream_timeout_secs = timeout
477 .parse()
478 .unwrap_or(CognitionConfig::default().session_end_dream_timeout_secs);
479 }
480 if let Ok(limit) = std::env::var("NEXUS_COGNITION_RETRY_BUFFER_DRAIN_LIMIT") {
481 config.cognition.retry_buffer_drain_limit = limit
482 .parse()
483 .unwrap_or(CognitionConfig::default().retry_buffer_drain_limit);
484 }
485 if let Ok(enabled) = std::env::var("NEXUS_COGNITION_CONTRADICTION_BELIEF_REVISION_ENABLED")
486 {
487 config.cognition.contradiction_belief_revision_enabled =
488 enabled.parse().unwrap_or(true);
489 }
490 if let Ok(penalty) = std::env::var("NEXUS_COGNITION_CONTRADICTION_CONFIDENCE_PENALTY") {
491 config.cognition.contradiction_confidence_penalty = penalty
492 .parse()
493 .unwrap_or(CognitionConfig::default().contradiction_confidence_penalty);
494 }
495 if let Ok(enabled) = std::env::var("NEXUS_COGNITION_MEMORY_DECAY_ENABLED") {
496 config.cognition.memory_decay_enabled = enabled.parse().unwrap_or(true);
497 }
498 if let Ok(days) = std::env::var("NEXUS_COGNITION_MEMORY_DECAY_AGE_DAYS") {
499 config.cognition.memory_decay_age_days = days
500 .parse()
501 .unwrap_or(CognitionConfig::default().memory_decay_age_days);
502 }
503 if let Ok(days) = std::env::var("NEXUS_COGNITION_MEMORY_DECAY_ACCESS_BOOST_DAYS") {
504 config.cognition.memory_decay_access_boost_days = days
505 .parse()
506 .unwrap_or(CognitionConfig::default().memory_decay_access_boost_days);
507 }
508 if let Ok(enabled) = std::env::var("NEXUS_COGNITION_ADAPTIVE_DREAM_ENABLED") {
509 config.cognition.adaptive_dream_enabled = enabled.parse().unwrap_or(true);
510 }
511 if let Ok(secs) = std::env::var("NEXUS_COGNITION_ADAPTIVE_DREAM_MIN_INTERVAL_SECS") {
512 config.cognition.adaptive_dream_min_interval_secs = secs
513 .parse()
514 .unwrap_or(CognitionConfig::default().adaptive_dream_min_interval_secs);
515 }
516 if let Ok(secs) = std::env::var("NEXUS_COGNITION_ADAPTIVE_DREAM_MAX_INTERVAL_SECS") {
517 config.cognition.adaptive_dream_max_interval_secs = secs
518 .parse()
519 .unwrap_or(CognitionConfig::default().adaptive_dream_max_interval_secs);
520 }
521
522 if let Ok(enabled) = std::env::var("NEXUS_COGNITIVE_ENABLED") {
524 config.cognitive_system.enabled = enabled.parse().unwrap_or(true);
525 }
526 if let Ok(mode) = std::env::var("NEXUS_BOOTSTRAP_MODE") {
527 config.cognitive_system.bootstrap_mode = mode;
528 }
529 if let Ok(max) = std::env::var("NEXUS_HOT_CACHE_MAX") {
530 config.cognitive_system.hot_cache_max_entries = max
531 .parse()
532 .unwrap_or(CognitiveSystemConfig::default().hot_cache_max_entries);
533 }
534 if let Ok(pct) = std::env::var("NEXUS_CONTEXT_ALLOCATION_PCT") {
535 config.cognitive_system.context_allocation_pct = pct
536 .parse::<f32>()
537 .ok()
538 .filter(|&v| (0.0..=1.0).contains(&v))
539 .unwrap_or(CognitiveSystemConfig::default().context_allocation_pct);
540 }
541 if let Ok(enabled) = std::env::var("NEXUS_RESCORE_ENABLED") {
542 config.cognitive_system.mid_session_rescore_enabled = enabled.parse().unwrap_or(true);
543 }
544 if let Ok(interval) = std::env::var("NEXUS_RESCORE_TURN_INTERVAL") {
545 config.cognitive_system.rescore_turn_interval = interval
546 .parse()
547 .unwrap_or(CognitiveSystemConfig::default().rescore_turn_interval);
548 }
549 if let Ok(threshold) = std::env::var("NEXUS_RESCORE_DRIFT_THRESHOLD") {
550 config.cognitive_system.rescore_drift_threshold = threshold
551 .parse()
552 .ok()
553 .filter(|&v: &f32| (0.0..=1.0).contains(&v))
554 .unwrap_or(CognitiveSystemConfig::default().rescore_drift_threshold);
555 }
556 if let Ok(mode) = std::env::var("NEXUS_SUBCONSCIOUS_MODE") {
557 config.cognitive_system.subconscious_mode = mode;
558 }
559 if let Ok(threshold) = std::env::var("NEXUS_DREAM_THRESHOLD") {
560 config
561 .cognitive_system
562 .dream_triggers
563 .dream_memory_threshold = threshold
564 .parse()
565 .unwrap_or(DreamTriggerConfig::default().dream_memory_threshold);
566 }
567 if let Ok(hours) = std::env::var("NEXUS_DEEP_DREAM_COOLDOWN_HOURS") {
568 config
569 .cognitive_system
570 .dream_triggers
571 .deep_dream_cooldown_hours = hours
572 .parse()
573 .unwrap_or(DreamTriggerConfig::default().deep_dream_cooldown_hours);
574 }
575
576 if let Ok(mins) = std::env::var("NEXUS_DEEP_DREAM_INACTIVITY_MINS") {
577 config
578 .cognitive_system
579 .dream_triggers
580 .deep_dream_inactivity_mins = mins
581 .parse()
582 .unwrap_or(DreamTriggerConfig::default().deep_dream_inactivity_mins);
583 }
584 if let Ok(threshold) = std::env::var("NEXUS_SIMILARITY_THRESHOLD") {
585 config.cognitive_system.similarity_threshold = threshold
586 .parse()
587 .ok()
588 .filter(|&v: &f32| (0.0..=1.0).contains(&v))
589 .unwrap_or(CognitiveSystemConfig::default().similarity_threshold);
590 }
591
592 Ok(config)
593 }
594
595 pub fn database_url(&self) -> String {
597 format!("sqlite:{}", self.database.path.display())
598 }
599}
600
601#[derive(Debug, Clone, Serialize, Deserialize)]
603pub struct DatabaseConfig {
604 pub path: PathBuf,
606
607 pub foreign_keys: bool,
609
610 pub pool_size: u32,
612}
613
614impl Default for DatabaseConfig {
615 fn default() -> Self {
616 let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
617 let base_path = PathBuf::from(home).join(".nexus");
618
619 Self {
620 path: base_path.join("nexus.db"),
621 foreign_keys: true,
622 pool_size: 5,
623 }
624 }
625}
626
627#[derive(Debug, Clone, Serialize, Deserialize)]
629pub struct ServerConfig {
630 pub host: String,
632
633 pub port: u16,
635
636 pub web_port: u16,
638
639 pub transport: String,
641}
642
643impl Default for ServerConfig {
644 fn default() -> Self {
645 Self {
646 host: "127.0.0.1".to_string(),
647 port: 8768,
648 web_port: 8768,
649 transport: "stdio".to_string(),
650 }
651 }
652}
653
654#[derive(Debug, Clone, Serialize, Deserialize)]
656pub struct EmbeddingConfig {
657 pub enabled: bool,
659
660 pub backend: String,
662
663 pub provider: String,
666
667 pub model: String,
669
670 pub api_key_env: String,
672
673 pub base_url: Option<String>,
675
676 pub dimension: usize,
678
679 pub timeout_secs: u64,
681
682 pub local_model_path: Option<String>,
684
685 pub local_tokenizer_path: Option<String>,
687}
688
689impl Default for EmbeddingConfig {
690 fn default() -> Self {
691 Self {
692 enabled: false,
693 backend: "local".to_string(),
694 provider: "local".to_string(),
695 model: "all-MiniLM-L6-v2".to_string(),
696 api_key_env: "OPENAI_API_KEY".to_string(),
697 base_url: None,
698 dimension: 384,
699 timeout_secs: 60,
700 local_model_path: Some("models/all-MiniLM-L6-v2.onnx".to_string()),
701 local_tokenizer_path: Some("models/all-MiniLM-L6-v2-tokenizer".to_string()),
702 }
703 }
704}
705
706#[derive(Debug, Clone, Serialize, Deserialize)]
708pub struct SyncConfig {
709 pub policy: String,
711
712 pub interval_secs: u64,
714}
715
716impl Default for SyncConfig {
717 fn default() -> Self {
718 Self {
719 policy: "manual".to_string(),
720 interval_secs: 300,
721 }
722 }
723}
724
725#[cfg(test)]
726mod tests {
727 use super::*;
728 use serial_test::serial;
729
730 #[test]
731 fn test_default_config() {
732 let config = Config::default();
733 assert!(!config.embedding.enabled);
734 assert_eq!(config.embedding.backend, "local");
735 assert_eq!(config.embedding.provider, "local");
736 assert_eq!(config.embedding.dimension, 384);
737 assert_eq!(config.server.port, 8768);
738 }
739
740 #[test]
741 fn test_database_url() {
742 let config = Config::default();
743 let url = config.database_url();
744 assert!(url.starts_with("sqlite:"));
745 }
746
747 #[test]
748 fn test_cognition_config_defaults() {
749 let config = Config::default();
750 assert!(config.cognition.enabled);
751 assert!(config.cognition.derive_enabled);
752 assert!(config.cognition.digest_enabled);
753 assert!(config.cognition.reflect_enabled);
754 assert!(config.cognition.activity_distill_enabled);
755 assert_eq!(config.cognition.derive_interval_secs, 60);
756 assert_eq!(config.cognition.digest_interval_secs, 300);
757 assert_eq!(config.cognition.reflect_interval_secs, 600);
758 assert_eq!(config.cognition.working_representation_max_items, 24);
759 assert!(!config.cognition.include_raw_by_default);
760 assert!(config.cognition.contradiction_belief_revision_enabled);
761 assert!((config.cognition.contradiction_confidence_penalty - 0.15).abs() < f32::EPSILON);
762 assert!(config.cognition.memory_decay_enabled);
763 assert_eq!(config.cognition.memory_decay_age_days, 90);
764 assert!(config.cognition.adaptive_dream_enabled);
765 assert_eq!(config.cognition.adaptive_dream_min_interval_secs, 60);
766 assert_eq!(config.cognition.adaptive_dream_max_interval_secs, 1800);
767 }
768
769 #[test]
770 #[serial]
771 fn test_cognition_config_from_env() {
772 std::env::set_var("NEXUS_COGNITION_ENABLED", "false");
773 std::env::set_var("NEXUS_COGNITION_DERIVE_ENABLED", "false");
774 std::env::set_var("NEXUS_COGNITION_DERIVE_INTERVAL_SECS", "120");
775 std::env::set_var("NEXUS_COGNITION_DIGEST_ENABLED", "false");
776 std::env::set_var("NEXUS_COGNITION_DIGEST_INTERVAL_SECS", "600");
777 std::env::set_var("NEXUS_COGNITION_REFLECT_ENABLED", "false");
778 std::env::set_var("NEXUS_COGNITION_REFLECT_INTERVAL_SECS", "900");
779 std::env::set_var("NEXUS_COGNITION_MAX_JOB_BATCH", "16");
780 std::env::set_var("NEXUS_COGNITION_LEASE_TTL_SECS", "240");
781 std::env::set_var("NEXUS_COGNITION_WORKING_REPRESENTATION_MAX_ITEMS", "42");
782 std::env::set_var("NEXUS_COGNITION_DIGEST_SHORT_TARGET_TOKENS", "800");
783 std::env::set_var("NEXUS_COGNITION_DIGEST_LONG_TARGET_TOKENS", "2000");
784 std::env::set_var("NEXUS_COGNITION_INCLUDE_RAW_BY_DEFAULT", "true");
785
786 let config = Config::from_env().expect("config from env");
787 assert!(!config.cognition.enabled);
788 assert!(!config.cognition.derive_enabled);
789 assert_eq!(config.cognition.derive_interval_secs, 120);
790 assert!(!config.cognition.digest_enabled);
791 assert_eq!(config.cognition.digest_interval_secs, 600);
792 assert!(!config.cognition.reflect_enabled);
793 assert_eq!(config.cognition.reflect_interval_secs, 900);
794 assert_eq!(config.cognition.max_job_batch, 16);
795 assert_eq!(config.cognition.lease_ttl_secs, 240);
796 assert_eq!(config.cognition.working_representation_max_items, 42);
797 assert_eq!(config.cognition.digest_short_target_tokens, 800);
798 assert_eq!(config.cognition.digest_long_target_tokens, 2000);
799 assert!(config.cognition.include_raw_by_default);
800
801 std::env::remove_var("NEXUS_COGNITION_ENABLED");
802 std::env::remove_var("NEXUS_COGNITION_DERIVE_ENABLED");
803 std::env::remove_var("NEXUS_COGNITION_DERIVE_INTERVAL_SECS");
804 std::env::remove_var("NEXUS_COGNITION_DIGEST_ENABLED");
805 std::env::remove_var("NEXUS_COGNITION_DIGEST_INTERVAL_SECS");
806 std::env::remove_var("NEXUS_COGNITION_REFLECT_ENABLED");
807 std::env::remove_var("NEXUS_COGNITION_REFLECT_INTERVAL_SECS");
808 std::env::remove_var("NEXUS_COGNITION_MAX_JOB_BATCH");
809 std::env::remove_var("NEXUS_COGNITION_LEASE_TTL_SECS");
810 std::env::remove_var("NEXUS_COGNITION_WORKING_REPRESENTATION_MAX_ITEMS");
811 std::env::remove_var("NEXUS_COGNITION_DIGEST_SHORT_TARGET_TOKENS");
812 std::env::remove_var("NEXUS_COGNITION_DIGEST_LONG_TARGET_TOKENS");
813 std::env::remove_var("NEXUS_COGNITION_INCLUDE_RAW_BY_DEFAULT");
814 }
815
816 #[test]
817 fn test_cognitive_system_config_defaults() {
818 let config = Config::default();
819 assert!(config.cognitive_system.enabled);
820 assert_eq!(config.cognitive_system.bootstrap_mode, "silent");
821 assert_eq!(config.cognitive_system.hot_cache_max_entries, 20);
822 assert!((config.cognitive_system.context_allocation_pct - 0.10).abs() < f32::EPSILON);
823 assert!(config.cognitive_system.dream_triggers.nap_on_session_end);
824 assert_eq!(
825 config
826 .cognitive_system
827 .dream_triggers
828 .dream_memory_threshold,
829 20
830 );
831 }
832
833 #[test]
834 #[serial]
835 fn test_cognitive_system_config_from_env() {
836 std::env::set_var("NEXUS_COGNITIVE_ENABLED", "false");
837 std::env::set_var("NEXUS_BOOTSTRAP_MODE", "chatty");
838 std::env::set_var("NEXUS_HOT_CACHE_MAX", "50");
839 std::env::set_var("NEXUS_DREAM_THRESHOLD", "10");
840
841 let config = Config::from_env().expect("config from env");
842 assert!(!config.cognitive_system.enabled);
843 assert_eq!(config.cognitive_system.bootstrap_mode, "chatty");
844 assert_eq!(config.cognitive_system.hot_cache_max_entries, 50);
845 assert_eq!(
846 config
847 .cognitive_system
848 .dream_triggers
849 .dream_memory_threshold,
850 10
851 );
852
853 std::env::remove_var("NEXUS_COGNITIVE_ENABLED");
854 std::env::remove_var("NEXUS_BOOTSTRAP_MODE");
855 std::env::remove_var("NEXUS_HOT_CACHE_MAX");
856 std::env::remove_var("NEXUS_DREAM_THRESHOLD");
857 }
858
859 #[test]
860 #[serial]
861 fn test_embedding_config_from_env() {
862 std::env::set_var("NEXUS_EMBEDDINGS_ENABLED", "true");
863 std::env::set_var("NEXUS_EMBEDDING_BACKEND", "openai-compatible");
864 std::env::set_var("NEXUS_EMBEDDING_PROVIDER", "inherit");
865 std::env::set_var("NEXUS_EMBEDDING_MODEL", "text-embedding-004");
866 std::env::set_var("NEXUS_EMBEDDING_API_KEY_ENV", "GEMINI_API_KEY");
867 std::env::set_var(
868 "NEXUS_EMBEDDING_BASE_URL",
869 "https://generativelanguage.googleapis.com/v1beta/openai",
870 );
871 std::env::set_var("NEXUS_EMBEDDING_TIMEOUT_SECS", "45");
872
873 let config = Config::from_env().expect("config from env");
874 assert!(config.embedding.enabled);
875 assert_eq!(config.embedding.backend, "openai-compatible");
876 assert_eq!(config.embedding.provider, "inherit");
877 assert_eq!(config.embedding.model, "text-embedding-004");
878 assert_eq!(config.embedding.api_key_env, "GEMINI_API_KEY");
879 assert_eq!(
880 config.embedding.base_url.as_deref(),
881 Some("https://generativelanguage.googleapis.com/v1beta/openai")
882 );
883 assert_eq!(config.embedding.timeout_secs, 45);
884
885 std::env::remove_var("NEXUS_EMBEDDINGS_ENABLED");
886 std::env::remove_var("NEXUS_EMBEDDING_BACKEND");
887 std::env::remove_var("NEXUS_EMBEDDING_PROVIDER");
888 std::env::remove_var("NEXUS_EMBEDDING_MODEL");
889 std::env::remove_var("NEXUS_EMBEDDING_API_KEY_ENV");
890 std::env::remove_var("NEXUS_EMBEDDING_BASE_URL");
891 std::env::remove_var("NEXUS_EMBEDDING_TIMEOUT_SECS");
892 }
893}