Skip to main content

nexus_memory_core/
config.rs

1//! Configuration types for Nexus Memory System
2
3use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6/// LLM provider configuration for agent operations
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct LlmConfig {
9    /// LLM provider name (openai, anthropic, gemini, openrouter, groq, zai, minimax, mistral)
10    pub provider: String,
11    /// Model name (e.g., "gpt-4o-mini", "claude-sonnet-4-20250514")
12    pub model: String,
13    /// API key environment variable name (e.g., "OPENAI_API_KEY")
14    pub api_key_env: String,
15    /// Base URL override (optional)
16    pub base_url: Option<String>,
17    /// Request timeout in seconds
18    pub timeout_secs: u64,
19    /// Maximum tokens to generate
20    pub max_tokens: u32,
21    /// Temperature for generation (0.0 to 1.0)
22    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/// Always-on agent configuration
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct AgentConfig {
42    /// Whether the always-on agent is enabled
43    pub enabled: bool,
44    /// Namespace name for agent-generated memories
45    pub namespace: String,
46    /// Agent type label for token budget estimation (e.g. "claude-code", "gemini")
47    pub agent_type: String,
48    /// Directory to watch for new files
49    pub inbox_dir: String,
50    /// File scan interval in seconds
51    pub scan_interval_secs: u64,
52    /// Consolidation interval in minutes
53    pub consolidation_interval_mins: u64,
54    /// Maximum memories to consolidate per run
55    pub consolidation_batch_size: usize,
56    /// Maximum memories to include in query context
57    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/// Cognition and runtime orchestration configuration
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct CognitionConfig {
79    /// Enable session-scoped runtime orchestration for hook-driven agent sessions
80    pub auto_runtime_enabled: bool,
81    /// Enable derivation of explicit observations from raw memories
82    pub derive_enabled: bool,
83    /// Enable session digest generation
84    pub digest_enabled: bool,
85    /// Enable reflective/dream processing
86    pub reflect_enabled: bool,
87    /// Enable low-signal activity distillation into higher-level summaries
88    pub activity_distill_enabled: bool,
89    /// Whether to trigger a bounded dream pass when a session ends
90    pub dream_on_session_end: bool,
91    /// Whether compact/checkpoint events should trigger a bounded flush
92    pub checkpoint_flush_enabled: bool,
93    /// Idle timeout, in seconds, for session-scoped runtime state
94    pub runtime_idle_timeout_secs: u64,
95    /// Maximum number of cognition jobs to claim in one worker batch
96    pub max_job_batch: usize,
97    /// Lease TTL, in seconds, for claimed cognition jobs
98    pub lease_ttl_secs: u64,
99    /// Maximum memories to include when building a working representation
100    pub representation_max_items: usize,
101    /// Target token budget for short digests
102    pub digest_short_target_tokens: usize,
103    /// Target token budget for long digests
104    pub digest_long_target_tokens: usize,
105    /// Timeout, in seconds, for direct hook-enrichment attempts
106    pub direct_enrichment_timeout_secs: u64,
107    /// Minimum number of events required before activity distillation runs
108    pub activity_distill_min_events: usize,
109    /// Maximum number of events to include in one activity distillation batch
110    pub activity_distill_max_events: usize,
111    /// Whether raw memories should be included by default in retrieval surfaces
112    pub include_raw_by_default: bool,
113    /// Timeout, in seconds, for best-effort dream work during session shutdown
114    pub session_end_dream_timeout_secs: u64,
115    /// Maximum retry-buffer artifacts to process during a bounded flush
116    pub retry_buffer_drain_limit: usize,
117    /// Enable belief revision when contradictions are detected (reduce confidence on sources)
118    pub contradiction_belief_revision_enabled: bool,
119    /// How much to reduce confidence on each contradicted memory (0.0–1.0)
120    pub contradiction_confidence_penalty: f32,
121    /// Enable memory aging decay in ranking blend
122    pub memory_decay_enabled: bool,
123    /// Days before a memory starts decaying in ranking score
124    pub memory_decay_age_days: u64,
125    /// Days that a recent access resets the decay clock
126    pub memory_decay_access_boost_days: u64,
127    /// Enable adaptive dream interval in the periodic supervisor loop
128    pub adaptive_dream_enabled: bool,
129    /// Floor (minimum) for adaptive dream interval in seconds
130    pub adaptive_dream_min_interval_secs: u64,
131    /// Ceiling (maximum) for adaptive dream interval in seconds
132    pub adaptive_dream_max_interval_secs: u64,
133}
134
135impl Default for CognitionConfig {
136    fn default() -> Self {
137        Self {
138            auto_runtime_enabled: true,
139            derive_enabled: true,
140            digest_enabled: true,
141            reflect_enabled: true,
142            activity_distill_enabled: true,
143            dream_on_session_end: true,
144            checkpoint_flush_enabled: true,
145            runtime_idle_timeout_secs: 900,
146            max_job_batch: 8,
147            lease_ttl_secs: 120,
148            representation_max_items: 24,
149            digest_short_target_tokens: 600,
150            digest_long_target_tokens: 1800,
151            direct_enrichment_timeout_secs: 8,
152            activity_distill_min_events: 8,
153            activity_distill_max_events: 60,
154            include_raw_by_default: false,
155            session_end_dream_timeout_secs: 8,
156            retry_buffer_drain_limit: 8,
157            contradiction_belief_revision_enabled: true,
158            contradiction_confidence_penalty: 0.15,
159            memory_decay_enabled: true,
160            memory_decay_age_days: 90,
161            memory_decay_access_boost_days: 30,
162            adaptive_dream_enabled: true,
163            adaptive_dream_min_interval_secs: 60,
164            adaptive_dream_max_interval_secs: 1800,
165        }
166    }
167}
168
169/// Dream trigger configuration
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct DreamTriggerConfig {
172    /// Whether to trigger a nap on session end
173    pub nap_on_session_end: bool,
174    /// Idle timeout in seconds before a nap is triggered
175    pub nap_idle_timeout_secs: u64,
176    /// Number of new memories that trigger a dream cycle
177    pub dream_memory_threshold: usize,
178    /// Minimum hours between deep dream cycles
179    pub deep_dream_cooldown_hours: u64,
180    /// Minimum inactivity minutes before deep dream
181    pub deep_dream_inactivity_mins: u64,
182}
183
184impl Default for DreamTriggerConfig {
185    fn default() -> Self {
186        Self {
187            nap_on_session_end: true,
188            nap_idle_timeout_secs: 600,
189            dream_memory_threshold: 20,
190            deep_dream_cooldown_hours: 24,
191            deep_dream_inactivity_mins: 30,
192        }
193    }
194}
195
196/// Autonomous cognitive system configuration
197#[derive(Debug, Clone, Serialize, Deserialize)]
198#[serde(default)]
199pub struct CognitiveSystemConfig {
200    pub enabled: bool,
201    /// System bootstrap mode: "silent" (default) or "chatty" (verbose)
202    pub bootstrap_mode: String,
203    /// Dream triggers
204    pub dream_triggers: DreamTriggerConfig,
205    /// Maximum entries in the hot cognitive cache
206    pub hot_cache_max_entries: usize,
207    /// Percentage of context window allocated to Nexus context
208    pub context_allocation_pct: f32,
209    /// Whether mid-session rescoring is enabled
210    pub mid_session_rescore_enabled: bool,
211    /// Turn interval for rescoring
212    pub rescore_turn_interval: u32,
213    /// Topic drift threshold for rescoring
214    pub rescore_drift_threshold: f32,
215    /// Similarity threshold for pattern clustering in dream
216    pub similarity_threshold: f32,
217    /// Subconscious mode: "whisper" (default), "full", or "off"
218    pub subconscious_mode: String,
219}
220
221impl Default for CognitiveSystemConfig {
222    fn default() -> Self {
223        Self {
224            enabled: true,
225            bootstrap_mode: "silent".to_string(),
226            dream_triggers: DreamTriggerConfig::default(),
227            hot_cache_max_entries: 20,
228            context_allocation_pct: 0.10,
229            mid_session_rescore_enabled: true,
230            rescore_turn_interval: 5,
231            rescore_drift_threshold: 0.70,
232            similarity_threshold: 0.85,
233            subconscious_mode: "whisper".to_string(),
234        }
235    }
236}
237
238/// Main configuration for Nexus
239#[derive(Debug, Clone, Serialize, Deserialize, Default)]
240pub struct Config {
241    /// Database configuration
242    pub database: DatabaseConfig,
243
244    /// Server configuration
245    pub server: ServerConfig,
246
247    /// Embedding configuration
248    pub embedding: EmbeddingConfig,
249
250    /// Sync configuration
251    pub sync: SyncConfig,
252
253    /// LLM configuration
254    pub llm: LlmConfig,
255
256    /// Agent configuration
257    pub agent: AgentConfig,
258
259    /// Cognition/runtime configuration
260    pub cognition: CognitionConfig,
261
262    /// Autonomous cognitive system configuration
263    pub cognitive_system: CognitiveSystemConfig,
264}
265
266impl Config {
267    /// Load configuration from environment variables
268    pub fn from_env() -> crate::Result<Self> {
269        let mut config = Self::default();
270
271        if let Ok(path) = std::env::var("NEXUS_DATABASE_PATH") {
272            config.database.path = PathBuf::from(path);
273        }
274
275        if let Ok(host) = std::env::var("NEXUS_HOST") {
276            config.server.host = host;
277        }
278
279        if let Ok(port) = std::env::var("NEXUS_PORT") {
280            config.server.port = port.parse().unwrap_or(8768);
281        }
282
283        if let Ok(enabled) = std::env::var("NEXUS_EMBEDDINGS_ENABLED") {
284            config.embedding.enabled = enabled.parse().unwrap_or(false);
285        }
286
287        if let Ok(backend) = std::env::var("NEXUS_EMBEDDING_BACKEND") {
288            config.embedding.backend = backend;
289        }
290
291        if let Ok(provider) = std::env::var("NEXUS_EMBEDDING_PROVIDER") {
292            config.embedding.provider = provider;
293        }
294
295        if let Ok(model) = std::env::var("NEXUS_EMBEDDING_MODEL") {
296            config.embedding.model = model;
297        }
298
299        if let Ok(key_env) = std::env::var("NEXUS_EMBEDDING_API_KEY_ENV") {
300            config.embedding.api_key_env = key_env;
301        }
302
303        if let Ok(base_url) = std::env::var("NEXUS_EMBEDDING_BASE_URL") {
304            config.embedding.base_url = Some(base_url);
305        }
306
307        if let Ok(dimension) = std::env::var("NEXUS_EMBEDDING_DIMENSION") {
308            config.embedding.dimension = dimension
309                .parse()
310                .unwrap_or(EmbeddingConfig::default().dimension);
311        }
312
313        if let Ok(timeout) = std::env::var("NEXUS_EMBEDDING_TIMEOUT_SECS") {
314            config.embedding.timeout_secs = timeout
315                .parse()
316                .unwrap_or(EmbeddingConfig::default().timeout_secs);
317        }
318
319        if let Ok(model_path) = std::env::var("NEXUS_EMBEDDING_MODEL_PATH") {
320            config.embedding.local_model_path = Some(model_path);
321        }
322
323        if let Ok(tokenizer_path) = std::env::var("NEXUS_TOKENIZER_PATH") {
324            config.embedding.local_tokenizer_path = Some(tokenizer_path);
325        }
326
327        if let Ok(policy) = std::env::var("NEXUS_SYNC_POLICY") {
328            config.sync.policy = policy;
329        }
330
331        // LLM configuration
332        if let Ok(provider) = std::env::var("NEXUS_LLM_PROVIDER") {
333            config.llm.provider = provider;
334        }
335        if let Ok(model) = std::env::var("NEXUS_LLM_MODEL") {
336            config.llm.model = model;
337        }
338        if let Ok(key_env) = std::env::var("NEXUS_LLM_API_KEY_ENV") {
339            config.llm.api_key_env = key_env;
340        }
341        if let Ok(base_url) = std::env::var("NEXUS_LLM_BASE_URL") {
342            config.llm.base_url = Some(base_url);
343        }
344
345        // Agent configuration
346        if let Ok(enabled) = std::env::var("NEXUS_AGENT_ENABLED") {
347            config.agent.enabled = enabled.parse().unwrap_or(false);
348        }
349        if let Ok(namespace) = std::env::var("NEXUS_AGENT_NAMESPACE") {
350            config.agent.namespace = namespace;
351        }
352        if let Ok(inbox) = std::env::var("NEXUS_AGENT_INBOX_DIR") {
353            config.agent.inbox_dir = inbox;
354        }
355        if let Ok(interval) = std::env::var("NEXUS_AGENT_CONSOLIDATION_INTERVAL_MINS") {
356            config.agent.consolidation_interval_mins = interval
357                .parse()
358                .unwrap_or(AgentConfig::default().consolidation_interval_mins);
359        } else if let Ok(interval) = std::env::var("NEXUS_AGENT_CONSOLIDATION_INTERVAL") {
360            // Backward compat: old name without unit suffix
361            config.agent.consolidation_interval_mins = interval
362                .parse()
363                .unwrap_or(AgentConfig::default().consolidation_interval_mins);
364        }
365        if let Ok(interval) = std::env::var("NEXUS_AGENT_SCAN_INTERVAL_SECS") {
366            config.agent.scan_interval_secs = interval
367                .parse()
368                .unwrap_or(AgentConfig::default().scan_interval_secs);
369        } else if let Ok(interval) = std::env::var("NEXUS_AGENT_SCAN_INTERVAL") {
370            // Backward compat: old name without unit suffix
371            config.agent.scan_interval_secs = interval
372                .parse()
373                .unwrap_or(AgentConfig::default().scan_interval_secs);
374        }
375
376        if let Ok(enabled) = std::env::var("NEXUS_COGNITION_AUTO_RUNTIME_ENABLED") {
377            config.cognition.auto_runtime_enabled = enabled.parse().unwrap_or(true);
378        }
379        if let Ok(enabled) = std::env::var("NEXUS_COGNITION_DERIVE_ENABLED") {
380            config.cognition.derive_enabled = enabled.parse().unwrap_or(true);
381        }
382        if let Ok(enabled) = std::env::var("NEXUS_COGNITION_DIGEST_ENABLED") {
383            config.cognition.digest_enabled = enabled.parse().unwrap_or(true);
384        }
385        if let Ok(enabled) = std::env::var("NEXUS_COGNITION_REFLECT_ENABLED") {
386            config.cognition.reflect_enabled = enabled.parse().unwrap_or(true);
387        }
388        if let Ok(enabled) = std::env::var("NEXUS_COGNITION_ACTIVITY_DISTILL_ENABLED") {
389            config.cognition.activity_distill_enabled = enabled.parse().unwrap_or(true);
390        }
391        if let Ok(enabled) = std::env::var("NEXUS_COGNITION_DREAM_ON_SESSION_END") {
392            config.cognition.dream_on_session_end = enabled.parse().unwrap_or(true);
393        }
394        if let Ok(enabled) = std::env::var("NEXUS_COGNITION_CHECKPOINT_FLUSH_ENABLED") {
395            config.cognition.checkpoint_flush_enabled = enabled.parse().unwrap_or(true);
396        }
397        if let Ok(timeout) = std::env::var("NEXUS_COGNITION_RUNTIME_IDLE_TIMEOUT_SECS") {
398            config.cognition.runtime_idle_timeout_secs = timeout
399                .parse()
400                .unwrap_or(CognitionConfig::default().runtime_idle_timeout_secs);
401        }
402        if let Ok(batch) = std::env::var("NEXUS_COGNITION_MAX_JOB_BATCH") {
403            config.cognition.max_job_batch = batch
404                .parse()
405                .unwrap_or(CognitionConfig::default().max_job_batch);
406        }
407        if let Ok(ttl) = std::env::var("NEXUS_COGNITION_LEASE_TTL_SECS") {
408            config.cognition.lease_ttl_secs = ttl
409                .parse()
410                .unwrap_or(CognitionConfig::default().lease_ttl_secs);
411        }
412        if let Ok(items) = std::env::var("NEXUS_COGNITION_REPRESENTATION_MAX_ITEMS") {
413            config.cognition.representation_max_items = items
414                .parse()
415                .unwrap_or(CognitionConfig::default().representation_max_items);
416        }
417        if let Ok(tokens) = std::env::var("NEXUS_COGNITION_DIGEST_SHORT_TARGET_TOKENS") {
418            config.cognition.digest_short_target_tokens = tokens
419                .parse()
420                .unwrap_or(CognitionConfig::default().digest_short_target_tokens);
421        }
422        if let Ok(tokens) = std::env::var("NEXUS_COGNITION_DIGEST_LONG_TARGET_TOKENS") {
423            config.cognition.digest_long_target_tokens = tokens
424                .parse()
425                .unwrap_or(CognitionConfig::default().digest_long_target_tokens);
426        }
427        if let Ok(timeout) = std::env::var("NEXUS_COGNITION_DIRECT_ENRICHMENT_TIMEOUT_SECS") {
428            config.cognition.direct_enrichment_timeout_secs = timeout
429                .parse()
430                .unwrap_or(CognitionConfig::default().direct_enrichment_timeout_secs);
431        }
432        if let Ok(events) = std::env::var("NEXUS_COGNITION_ACTIVITY_DISTILL_MIN_EVENTS") {
433            config.cognition.activity_distill_min_events = events
434                .parse()
435                .unwrap_or(CognitionConfig::default().activity_distill_min_events);
436        }
437        if let Ok(events) = std::env::var("NEXUS_COGNITION_ACTIVITY_DISTILL_MAX_EVENTS") {
438            config.cognition.activity_distill_max_events = events
439                .parse()
440                .unwrap_or(CognitionConfig::default().activity_distill_max_events);
441        }
442        if let Ok(include_raw) = std::env::var("NEXUS_COGNITION_INCLUDE_RAW_BY_DEFAULT") {
443            config.cognition.include_raw_by_default = include_raw.parse().unwrap_or(false);
444        }
445        if let Ok(timeout) = std::env::var("NEXUS_COGNITION_SESSION_END_DREAM_TIMEOUT_SECS") {
446            config.cognition.session_end_dream_timeout_secs = timeout
447                .parse()
448                .unwrap_or(CognitionConfig::default().session_end_dream_timeout_secs);
449        }
450        if let Ok(limit) = std::env::var("NEXUS_COGNITION_RETRY_BUFFER_DRAIN_LIMIT") {
451            config.cognition.retry_buffer_drain_limit = limit
452                .parse()
453                .unwrap_or(CognitionConfig::default().retry_buffer_drain_limit);
454        }
455        if let Ok(enabled) = std::env::var("NEXUS_COGNITION_CONTRADICTION_BELIEF_REVISION_ENABLED")
456        {
457            config.cognition.contradiction_belief_revision_enabled =
458                enabled.parse().unwrap_or(true);
459        }
460        if let Ok(penalty) = std::env::var("NEXUS_COGNITION_CONTRADICTION_CONFIDENCE_PENALTY") {
461            config.cognition.contradiction_confidence_penalty = penalty
462                .parse()
463                .unwrap_or(CognitionConfig::default().contradiction_confidence_penalty);
464        }
465        if let Ok(enabled) = std::env::var("NEXUS_COGNITION_MEMORY_DECAY_ENABLED") {
466            config.cognition.memory_decay_enabled = enabled.parse().unwrap_or(true);
467        }
468        if let Ok(days) = std::env::var("NEXUS_COGNITION_MEMORY_DECAY_AGE_DAYS") {
469            config.cognition.memory_decay_age_days = days
470                .parse()
471                .unwrap_or(CognitionConfig::default().memory_decay_age_days);
472        }
473        if let Ok(days) = std::env::var("NEXUS_COGNITION_MEMORY_DECAY_ACCESS_BOOST_DAYS") {
474            config.cognition.memory_decay_access_boost_days = days
475                .parse()
476                .unwrap_or(CognitionConfig::default().memory_decay_access_boost_days);
477        }
478        if let Ok(enabled) = std::env::var("NEXUS_COGNITION_ADAPTIVE_DREAM_ENABLED") {
479            config.cognition.adaptive_dream_enabled = enabled.parse().unwrap_or(true);
480        }
481        if let Ok(secs) = std::env::var("NEXUS_COGNITION_ADAPTIVE_DREAM_MIN_INTERVAL_SECS") {
482            config.cognition.adaptive_dream_min_interval_secs = secs
483                .parse()
484                .unwrap_or(CognitionConfig::default().adaptive_dream_min_interval_secs);
485        }
486        if let Ok(secs) = std::env::var("NEXUS_COGNITION_ADAPTIVE_DREAM_MAX_INTERVAL_SECS") {
487            config.cognition.adaptive_dream_max_interval_secs = secs
488                .parse()
489                .unwrap_or(CognitionConfig::default().adaptive_dream_max_interval_secs);
490        }
491
492        // Cognitive System configuration
493        if let Ok(enabled) = std::env::var("NEXUS_COGNITIVE_ENABLED") {
494            config.cognitive_system.enabled = enabled.parse().unwrap_or(true);
495        }
496        if let Ok(mode) = std::env::var("NEXUS_BOOTSTRAP_MODE") {
497            config.cognitive_system.bootstrap_mode = mode;
498        }
499        if let Ok(max) = std::env::var("NEXUS_HOT_CACHE_MAX") {
500            config.cognitive_system.hot_cache_max_entries = max
501                .parse()
502                .unwrap_or(CognitiveSystemConfig::default().hot_cache_max_entries);
503        }
504        if let Ok(pct) = std::env::var("NEXUS_CONTEXT_ALLOCATION_PCT") {
505            config.cognitive_system.context_allocation_pct = pct
506                .parse::<f32>()
507                .ok()
508                .filter(|&v| (0.0..=1.0).contains(&v))
509                .unwrap_or(CognitiveSystemConfig::default().context_allocation_pct);
510        }
511        if let Ok(enabled) = std::env::var("NEXUS_RESCORE_ENABLED") {
512            config.cognitive_system.mid_session_rescore_enabled = enabled.parse().unwrap_or(true);
513        }
514        if let Ok(interval) = std::env::var("NEXUS_RESCORE_TURN_INTERVAL") {
515            config.cognitive_system.rescore_turn_interval = interval
516                .parse()
517                .unwrap_or(CognitiveSystemConfig::default().rescore_turn_interval);
518        }
519        if let Ok(threshold) = std::env::var("NEXUS_RESCORE_DRIFT_THRESHOLD") {
520            config.cognitive_system.rescore_drift_threshold = threshold
521                .parse()
522                .ok()
523                .filter(|&v: &f32| (0.0..=1.0).contains(&v))
524                .unwrap_or(CognitiveSystemConfig::default().rescore_drift_threshold);
525        }
526        if let Ok(mode) = std::env::var("NEXUS_SUBCONSCIOUS_MODE") {
527            config.cognitive_system.subconscious_mode = mode;
528        }
529        if let Ok(threshold) = std::env::var("NEXUS_DREAM_THRESHOLD") {
530            config
531                .cognitive_system
532                .dream_triggers
533                .dream_memory_threshold = threshold
534                .parse()
535                .unwrap_or(DreamTriggerConfig::default().dream_memory_threshold);
536        }
537        if let Ok(hours) = std::env::var("NEXUS_DEEP_DREAM_COOLDOWN_HOURS") {
538            config
539                .cognitive_system
540                .dream_triggers
541                .deep_dream_cooldown_hours = hours
542                .parse()
543                .unwrap_or(DreamTriggerConfig::default().deep_dream_cooldown_hours);
544        }
545
546        if let Ok(mins) = std::env::var("NEXUS_DEEP_DREAM_INACTIVITY_MINS") {
547            config
548                .cognitive_system
549                .dream_triggers
550                .deep_dream_inactivity_mins = mins
551                .parse()
552                .unwrap_or(DreamTriggerConfig::default().deep_dream_inactivity_mins);
553        }
554        if let Ok(threshold) = std::env::var("NEXUS_SIMILARITY_THRESHOLD") {
555            config.cognitive_system.similarity_threshold = threshold
556                .parse()
557                .ok()
558                .filter(|&v: &f32| (0.0..=1.0).contains(&v))
559                .unwrap_or(CognitiveSystemConfig::default().similarity_threshold);
560        }
561
562        Ok(config)
563    }
564
565    /// Get the database URL
566    pub fn database_url(&self) -> String {
567        format!("sqlite:{}", self.database.path.display())
568    }
569}
570
571/// Database configuration
572#[derive(Debug, Clone, Serialize, Deserialize)]
573pub struct DatabaseConfig {
574    /// Path to SQLite database file
575    pub path: PathBuf,
576
577    /// Enable foreign key constraints
578    pub foreign_keys: bool,
579
580    /// Connection pool size
581    pub pool_size: u32,
582}
583
584impl Default for DatabaseConfig {
585    fn default() -> Self {
586        let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
587        let base_path = PathBuf::from(home).join(".nexus");
588
589        Self {
590            path: base_path.join("nexus.db"),
591            foreign_keys: true,
592            pool_size: 5,
593        }
594    }
595}
596
597/// Server configuration
598#[derive(Debug, Clone, Serialize, Deserialize)]
599pub struct ServerConfig {
600    /// Server host
601    pub host: String,
602
603    /// Server port
604    pub port: u16,
605
606    /// Web dashboard port
607    pub web_port: u16,
608
609    /// Transport type (stdio, web)
610    pub transport: String,
611}
612
613impl Default for ServerConfig {
614    fn default() -> Self {
615        Self {
616            host: "127.0.0.1".to_string(),
617            port: 8768,
618            web_port: 8768,
619            transport: "stdio".to_string(),
620        }
621    }
622}
623
624/// Embedding configuration
625#[derive(Debug, Clone, Serialize, Deserialize)]
626pub struct EmbeddingConfig {
627    /// Enable embeddings
628    pub enabled: bool,
629
630    /// Embedding backend (`local` or `openai-compatible`)
631    pub backend: String,
632
633    /// Embedding provider/profile (`inherit`, `openai`, `gemini`, `openrouter`,
634    /// `vllm`, `lmstudio`, `llamacpp`, `custom`, etc.)
635    pub provider: String,
636
637    /// Embedding model name
638    pub model: String,
639
640    /// API key environment variable name for remote embedding providers
641    pub api_key_env: String,
642
643    /// Base URL override for remote or local OpenAI-compatible runtimes
644    pub base_url: Option<String>,
645
646    /// Embedding dimension
647    pub dimension: usize,
648
649    /// Request timeout for remote embedding providers
650    pub timeout_secs: u64,
651
652    /// Path to a local ONNX embedding model
653    pub local_model_path: Option<String>,
654
655    /// Path to the tokenizer directory for local ONNX embeddings
656    pub local_tokenizer_path: Option<String>,
657}
658
659impl Default for EmbeddingConfig {
660    fn default() -> Self {
661        Self {
662            enabled: false,
663            backend: "local".to_string(),
664            provider: "local".to_string(),
665            model: "all-MiniLM-L6-v2".to_string(),
666            api_key_env: "OPENAI_API_KEY".to_string(),
667            base_url: None,
668            dimension: 384,
669            timeout_secs: 60,
670            local_model_path: Some("models/all-MiniLM-L6-v2.onnx".to_string()),
671            local_tokenizer_path: Some("models/all-MiniLM-L6-v2-tokenizer".to_string()),
672        }
673    }
674}
675
676/// Sync configuration for cross-agent synchronization
677#[derive(Debug, Clone, Serialize, Deserialize)]
678pub struct SyncConfig {
679    /// Sync policy (manual, auto, aggressive)
680    pub policy: String,
681
682    /// Sync interval in seconds (for auto policy)
683    pub interval_secs: u64,
684}
685
686impl Default for SyncConfig {
687    fn default() -> Self {
688        Self {
689            policy: "manual".to_string(),
690            interval_secs: 300,
691        }
692    }
693}
694
695#[cfg(test)]
696mod tests {
697    use super::*;
698    use serial_test::serial;
699
700    #[test]
701    fn test_default_config() {
702        let config = Config::default();
703        assert!(!config.embedding.enabled);
704        assert_eq!(config.embedding.backend, "local");
705        assert_eq!(config.embedding.provider, "local");
706        assert_eq!(config.embedding.dimension, 384);
707        assert_eq!(config.server.port, 8768);
708    }
709
710    #[test]
711    fn test_database_url() {
712        let config = Config::default();
713        let url = config.database_url();
714        assert!(url.starts_with("sqlite:"));
715    }
716
717    #[test]
718    fn test_cognition_config_defaults() {
719        let config = Config::default();
720        assert!(config.cognition.derive_enabled);
721        assert!(config.cognition.digest_enabled);
722        assert!(config.cognition.reflect_enabled);
723        assert!(config.cognition.activity_distill_enabled);
724        assert_eq!(config.cognition.representation_max_items, 24);
725        assert!(!config.cognition.include_raw_by_default);
726        assert!(config.cognition.contradiction_belief_revision_enabled);
727        assert!((config.cognition.contradiction_confidence_penalty - 0.15).abs() < f32::EPSILON);
728        assert!(config.cognition.memory_decay_enabled);
729        assert_eq!(config.cognition.memory_decay_age_days, 90);
730        assert!(config.cognition.adaptive_dream_enabled);
731        assert_eq!(config.cognition.adaptive_dream_min_interval_secs, 60);
732        assert_eq!(config.cognition.adaptive_dream_max_interval_secs, 1800);
733    }
734
735    #[test]
736    #[serial]
737    fn test_cognition_config_from_env() {
738        std::env::set_var("NEXUS_COGNITION_DERIVE_ENABLED", "false");
739        std::env::set_var("NEXUS_COGNITION_MAX_JOB_BATCH", "16");
740        std::env::set_var("NEXUS_COGNITION_REPRESENTATION_MAX_ITEMS", "42");
741        std::env::set_var("NEXUS_COGNITION_INCLUDE_RAW_BY_DEFAULT", "true");
742
743        let config = Config::from_env().expect("config from env");
744        assert!(!config.cognition.derive_enabled);
745        assert_eq!(config.cognition.max_job_batch, 16);
746        assert_eq!(config.cognition.representation_max_items, 42);
747        assert!(config.cognition.include_raw_by_default);
748
749        std::env::remove_var("NEXUS_COGNITION_DERIVE_ENABLED");
750        std::env::remove_var("NEXUS_COGNITION_MAX_JOB_BATCH");
751        std::env::remove_var("NEXUS_COGNITION_REPRESENTATION_MAX_ITEMS");
752        std::env::remove_var("NEXUS_COGNITION_INCLUDE_RAW_BY_DEFAULT");
753    }
754
755    #[test]
756    fn test_cognitive_system_config_defaults() {
757        let config = Config::default();
758        assert!(config.cognitive_system.enabled);
759        assert_eq!(config.cognitive_system.bootstrap_mode, "silent");
760        assert_eq!(config.cognitive_system.hot_cache_max_entries, 20);
761        assert!((config.cognitive_system.context_allocation_pct - 0.10).abs() < f32::EPSILON);
762        assert!(config.cognitive_system.dream_triggers.nap_on_session_end);
763        assert_eq!(
764            config
765                .cognitive_system
766                .dream_triggers
767                .dream_memory_threshold,
768            20
769        );
770    }
771
772    #[test]
773    #[serial]
774    fn test_cognitive_system_config_from_env() {
775        std::env::set_var("NEXUS_COGNITIVE_ENABLED", "false");
776        std::env::set_var("NEXUS_BOOTSTRAP_MODE", "chatty");
777        std::env::set_var("NEXUS_HOT_CACHE_MAX", "50");
778        std::env::set_var("NEXUS_DREAM_THRESHOLD", "10");
779
780        let config = Config::from_env().expect("config from env");
781        assert!(!config.cognitive_system.enabled);
782        assert_eq!(config.cognitive_system.bootstrap_mode, "chatty");
783        assert_eq!(config.cognitive_system.hot_cache_max_entries, 50);
784        assert_eq!(
785            config
786                .cognitive_system
787                .dream_triggers
788                .dream_memory_threshold,
789            10
790        );
791
792        std::env::remove_var("NEXUS_COGNITIVE_ENABLED");
793        std::env::remove_var("NEXUS_BOOTSTRAP_MODE");
794        std::env::remove_var("NEXUS_HOT_CACHE_MAX");
795        std::env::remove_var("NEXUS_DREAM_THRESHOLD");
796    }
797
798    #[test]
799    #[serial]
800    fn test_embedding_config_from_env() {
801        std::env::set_var("NEXUS_EMBEDDINGS_ENABLED", "true");
802        std::env::set_var("NEXUS_EMBEDDING_BACKEND", "openai-compatible");
803        std::env::set_var("NEXUS_EMBEDDING_PROVIDER", "inherit");
804        std::env::set_var("NEXUS_EMBEDDING_MODEL", "text-embedding-004");
805        std::env::set_var("NEXUS_EMBEDDING_API_KEY_ENV", "GEMINI_API_KEY");
806        std::env::set_var(
807            "NEXUS_EMBEDDING_BASE_URL",
808            "https://generativelanguage.googleapis.com/v1beta/openai",
809        );
810        std::env::set_var("NEXUS_EMBEDDING_TIMEOUT_SECS", "45");
811
812        let config = Config::from_env().expect("config from env");
813        assert!(config.embedding.enabled);
814        assert_eq!(config.embedding.backend, "openai-compatible");
815        assert_eq!(config.embedding.provider, "inherit");
816        assert_eq!(config.embedding.model, "text-embedding-004");
817        assert_eq!(config.embedding.api_key_env, "GEMINI_API_KEY");
818        assert_eq!(
819            config.embedding.base_url.as_deref(),
820            Some("https://generativelanguage.googleapis.com/v1beta/openai")
821        );
822        assert_eq!(config.embedding.timeout_secs, 45);
823
824        std::env::remove_var("NEXUS_EMBEDDINGS_ENABLED");
825        std::env::remove_var("NEXUS_EMBEDDING_BACKEND");
826        std::env::remove_var("NEXUS_EMBEDDING_PROVIDER");
827        std::env::remove_var("NEXUS_EMBEDDING_MODEL");
828        std::env::remove_var("NEXUS_EMBEDDING_API_KEY_ENV");
829        std::env::remove_var("NEXUS_EMBEDDING_BASE_URL");
830        std::env::remove_var("NEXUS_EMBEDDING_TIMEOUT_SECS");
831    }
832}