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 the cognition system overall
80    pub enabled: bool,
81    /// Enable session-scoped runtime orchestration for hook-driven agent sessions
82    pub auto_runtime_enabled: bool,
83    /// Enable derivation of explicit observations from raw memories
84    pub derive_enabled: bool,
85    /// Interval, in seconds, between derivation cycles
86    pub derive_interval_secs: u64,
87    /// Enable session digest generation
88    pub digest_enabled: bool,
89    /// Interval, in seconds, between digest cycles
90    pub digest_interval_secs: u64,
91    /// Enable reflective/dream processing
92    pub reflect_enabled: bool,
93    /// Interval, in seconds, between reflection cycles
94    pub reflect_interval_secs: u64,
95    /// Enable low-signal activity distillation into higher-level summaries
96    pub activity_distill_enabled: bool,
97    /// Whether to trigger a bounded dream pass when a session ends
98    pub dream_on_session_end: bool,
99    /// Whether compact/checkpoint events should trigger a bounded flush
100    pub checkpoint_flush_enabled: bool,
101    /// Idle timeout, in seconds, for session-scoped runtime state
102    pub runtime_idle_timeout_secs: u64,
103    /// Maximum number of cognition jobs to claim in one worker batch
104    pub max_job_batch: usize,
105    /// Lease TTL, in seconds, for claimed cognition jobs
106    pub lease_ttl_secs: u64,
107    /// Maximum memories to include when building a working representation
108    pub working_representation_max_items: usize,
109    /// Target token budget for short digests
110    pub digest_short_target_tokens: usize,
111    /// Target token budget for long digests
112    pub digest_long_target_tokens: usize,
113    /// Timeout, in seconds, for direct hook-enrichment attempts
114    pub direct_enrichment_timeout_secs: u64,
115    /// Minimum number of events required before activity distillation runs
116    pub activity_distill_min_events: usize,
117    /// Maximum number of events to include in one activity distillation batch
118    pub activity_distill_max_events: usize,
119    /// Whether raw memories should be included by default in retrieval surfaces
120    pub include_raw_by_default: bool,
121    /// Timeout, in seconds, for best-effort dream work during session shutdown
122    pub session_end_dream_timeout_secs: u64,
123    /// Maximum retry-buffer artifacts to process during a bounded flush
124    pub retry_buffer_drain_limit: usize,
125    /// Enable belief revision when contradictions are detected (reduce confidence on sources)
126    pub contradiction_belief_revision_enabled: bool,
127    /// How much to reduce confidence on each contradicted memory (0.0–1.0)
128    pub contradiction_confidence_penalty: f32,
129    /// Enable memory aging decay in ranking blend
130    pub memory_decay_enabled: bool,
131    /// Days before a memory starts decaying in ranking score
132    pub memory_decay_age_days: u64,
133    /// Days that a recent access resets the decay clock
134    pub memory_decay_access_boost_days: u64,
135    /// Enable adaptive dream interval in the periodic supervisor loop
136    pub adaptive_dream_enabled: bool,
137    /// Floor (minimum) for adaptive dream interval in seconds
138    pub adaptive_dream_min_interval_secs: u64,
139    /// Ceiling (maximum) for adaptive dream interval in seconds
140    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/// Dream trigger configuration
182#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct DreamTriggerConfig {
184    /// Whether to trigger a nap on session end
185    pub nap_on_session_end: bool,
186    /// Idle timeout in seconds before a nap is triggered
187    pub nap_idle_timeout_secs: u64,
188    /// Number of new memories that trigger a dream cycle
189    pub dream_memory_threshold: usize,
190    /// Minimum hours between deep dream cycles
191    pub deep_dream_cooldown_hours: u64,
192    /// Minimum inactivity minutes before deep dream
193    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/// Autonomous cognitive system configuration
209#[derive(Debug, Clone, Serialize, Deserialize)]
210#[serde(default)]
211pub struct CognitiveSystemConfig {
212    pub enabled: bool,
213    /// System bootstrap mode: "silent" (default) or "chatty" (verbose)
214    pub bootstrap_mode: String,
215    /// Dream triggers
216    pub dream_triggers: DreamTriggerConfig,
217    /// Maximum entries in the hot cognitive cache
218    pub hot_cache_max_entries: usize,
219    /// Percentage of context window allocated to Nexus context
220    pub context_allocation_pct: f32,
221    /// Whether mid-session rescoring is enabled
222    pub mid_session_rescore_enabled: bool,
223    /// Turn interval for rescoring
224    pub rescore_turn_interval: u32,
225    /// Topic drift threshold for rescoring
226    pub rescore_drift_threshold: f32,
227    /// Similarity threshold for pattern clustering in dream
228    pub similarity_threshold: f32,
229    /// Subconscious mode: "whisper" (default), "full", or "off"
230    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/// Main configuration for Nexus
251#[derive(Debug, Clone, Serialize, Deserialize, Default)]
252pub struct Config {
253    /// Database configuration
254    pub database: DatabaseConfig,
255
256    /// Server configuration
257    pub server: ServerConfig,
258
259    /// Embedding configuration
260    pub embedding: EmbeddingConfig,
261
262    /// Sync configuration
263    pub sync: SyncConfig,
264
265    /// LLM configuration
266    pub llm: LlmConfig,
267
268    /// Agent configuration
269    pub agent: AgentConfig,
270
271    /// Cognition/runtime configuration
272    pub cognition: CognitionConfig,
273
274    /// Autonomous cognitive system configuration
275    pub cognitive_system: CognitiveSystemConfig,
276}
277
278impl Config {
279    /// Load configuration from environment variables
280    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        // LLM configuration
344        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        // Agent configuration
358        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            // Backward compat: old name without unit suffix
373            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            // Backward compat: old name without unit suffix
383            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        // Cognitive System configuration
523        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    /// Get the database URL
596    pub fn database_url(&self) -> String {
597        format!("sqlite:{}", self.database.path.display())
598    }
599}
600
601/// Database configuration
602#[derive(Debug, Clone, Serialize, Deserialize)]
603pub struct DatabaseConfig {
604    /// Path to SQLite database file
605    pub path: PathBuf,
606
607    /// Enable foreign key constraints
608    pub foreign_keys: bool,
609
610    /// Connection pool size
611    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/// Server configuration
628#[derive(Debug, Clone, Serialize, Deserialize)]
629pub struct ServerConfig {
630    /// Server host
631    pub host: String,
632
633    /// Server port
634    pub port: u16,
635
636    /// Web dashboard port
637    pub web_port: u16,
638
639    /// Transport type (stdio, web)
640    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/// Embedding configuration
655#[derive(Debug, Clone, Serialize, Deserialize)]
656pub struct EmbeddingConfig {
657    /// Enable embeddings
658    pub enabled: bool,
659
660    /// Embedding backend (`local` or `openai-compatible`)
661    pub backend: String,
662
663    /// Embedding provider/profile (`inherit`, `openai`, `gemini`, `openrouter`,
664    /// `vllm`, `lmstudio`, `llamacpp`, `custom`, etc.)
665    pub provider: String,
666
667    /// Embedding model name
668    pub model: String,
669
670    /// API key environment variable name for remote embedding providers
671    pub api_key_env: String,
672
673    /// Base URL override for remote or local OpenAI-compatible runtimes
674    pub base_url: Option<String>,
675
676    /// Embedding dimension
677    pub dimension: usize,
678
679    /// Request timeout for remote embedding providers
680    pub timeout_secs: u64,
681
682    /// Path to a local ONNX embedding model
683    pub local_model_path: Option<String>,
684
685    /// Path to the tokenizer directory for local ONNX embeddings
686    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/// Sync configuration for cross-agent synchronization
707#[derive(Debug, Clone, Serialize, Deserialize)]
708pub struct SyncConfig {
709    /// Sync policy (manual, auto, aggressive)
710    pub policy: String,
711
712    /// Sync interval in seconds (for auto policy)
713    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}