""" HAIM Configuration System ======================== Centralized, validated configuration with environment variable overrides. """ import os from pathlib import Path from typing import Optional from dataclasses import dataclass, field import yaml from mnemocore.core.exceptions import ConfigurationError @dataclass(frozen=True) class TierConfig: max_memories: int ltp_threshold_min: float eviction_policy: str = "lru" consolidation_interval_hours: Optional[int] = None storage_backend: str = "memory" compression: str = "gzip" archive_threshold_days: int = 30 @dataclass(frozen=True) class LTPConfig: initial_importance: float = 0.5 decay_lambda: float = 0.01 permanence_threshold: float = 0.95 half_life_days: float = 30.0 @dataclass(frozen=True) class HysteresisConfig: promote_delta: float = 0.15 demote_delta: float = 0.10 @dataclass(frozen=True) class RedisConfig: url: str = "redis://localhost:6379/0" stream_key: str = "haim:subconscious" max_connections: int = 10 socket_timeout: int = 5 password: Optional[str] = None @dataclass(frozen=True) class QdrantConfig: url: str = "http://localhost:6333" collection_hot: str = "haim_hot" collection_warm: str = "haim_warm" binary_quantization: bool = True always_ram: bool = True hnsw_m: int = 16 hnsw_ef_construct: int = 100 api_key: Optional[str] = None @dataclass(frozen=True) class GPUConfig: enabled: bool = False device: str = "cuda:0" batch_size: int = 1000 fallback_to_cpu: bool = True @dataclass(frozen=True) class SecurityConfig: api_key: Optional[str] = None cors_origins: list[str] = field(default_factory=lambda: ["*"]) rate_limit_enabled: bool = True rate_limit_requests: int = 100 rate_limit_window: int = 60 @dataclass(frozen=True) class ObservabilityConfig: metrics_port: int = 9090 log_level: str = "INFO" structured_logging: bool = True @dataclass(frozen=True) class MCPConfig: enabled: bool = False transport: str = "stdio" host: str = "127.0.0.1" port: int = 8110 api_base_url: str = "http://localhost:8100" api_key: Optional[str] = None timeout_seconds: int = 15 allow_tools: list[str] = field( default_factory=lambda: [ "memory_store", "memory_query", "memory_get", "memory_delete", "memory_stats", "memory_health", ] ) @dataclass(frozen=True) class PathsConfig: data_dir: str = "./data" memory_file: str = "./data/memory.jsonl" codebook_file: str = "./data/codebook.json" concepts_file: str = "./data/concepts.json" synapses_file: str = "./data/synapses.json" warm_mmap_dir: str = "./data/warm_tier" cold_archive_dir: str = "./data/cold_archive" @dataclass(frozen=True) class AttentionMaskingConfig: """Configuration for XOR-based project isolation (Phase 4.1).""" enabled: bool = True @dataclass(frozen=True) class ConsolidationConfig: """Configuration for semantic consolidation (Phase 4.0+).""" enabled: bool = True interval_seconds: int = 3600 # 1 hour similarity_threshold: float = 0.85 min_cluster_size: int = 2 hot_tier_enabled: bool = True warm_tier_enabled: bool = True @dataclass(frozen=True) class EncodingConfig: mode: str = "binary" # "binary" or "float" token_method: str = "bundle" @dataclass(frozen=True) class SynapseConfig: """Configuration for Phase 12.1: Aggressive Synapse Formation""" similarity_threshold: float = 0.5 auto_bind_on_store: bool = True multi_hop_depth: int = 2 @dataclass(frozen=True) class ContextConfig: """Configuration for Phase 12.2: Contextual Awareness""" enabled: bool = True shift_threshold: float = 0.3 rolling_window_size: int = 5 @dataclass(frozen=True) class PreferenceConfig: """Configuration for Phase 12.3: Preference Learning""" enabled: bool = True learning_rate: float = 0.1 history_limit: int = 100 @dataclass(frozen=True) class AnticipatoryConfig: """Configuration for Phase 13.2: Anticipatory Memory""" enabled: bool = True predictive_depth: int = 1 @dataclass(frozen=True) class DreamLoopConfig: """Configuration for the dream loop (subconscious background processing).""" enabled: bool = True frequency_seconds: int = 60 batch_size: int = 10 max_iterations: int = 0 # 0 = unlimited subconscious_queue_maxlen: Optional[int] = None ollama_url: str = "http://localhost:11434/api/generate" model: str = "gemma3:1b" @dataclass(frozen=True) class SubconsciousAIConfig: """ Configuration for the Subconscious AI worker (Phase 4.4). A small LLM (Phi 3.5, Llama 7B) that pulses in the background, performing memory sorting, enhanced dreaming, and micro self-improvement. This is a BETA feature that must be explicitly enabled. """ # Opt-in BETA feature flag (MUST be explicitly enabled) enabled: bool = False beta_mode: bool = True # Extra safety checks when True # Model configuration model_provider: str = "ollama" # "ollama" | "lm_studio" | "openai_api" | "anthropic_api" model_name: str = "phi3.5:3.8b" # Default: Phi 3.5 (small, fast) model_url: str = "http://localhost:11434" api_key: Optional[str] = None # For API providers api_base_url: Optional[str] = None # Override base URL for API providers # Pulse configuration pulse_interval_seconds: int = 120 # Default: 2 minutes between pulses pulse_backoff_enabled: bool = True # Increase interval on errors pulse_backoff_max_seconds: int = 600 # Max backoff: 10 minutes # Resource management max_cpu_percent: float = 30.0 # Skip pulse if CPU > this cycle_timeout_seconds: int = 30 # Max time per LLM call rate_limit_per_hour: int = 50 # Max LLM calls per hour # Operations (all can be toggled independently) memory_sorting_enabled: bool = True # Categorize and tag memories enhanced_dreaming_enabled: bool = True # LLM-assisted consolidation micro_self_improvement_enabled: bool = False # Pattern analysis (disabled by default) # Safety settings dry_run: bool = True # When True, only log suggestions without applying log_all_decisions: bool = True # Full audit trail audit_trail_path: Optional[str] = "./data/subconscious_audit.jsonl" max_memories_per_cycle: int = 10 # Process at most N memories per pulse @dataclass(frozen=True) class PulseConfig: """Configuration for Phase 5 AGI Pulse Loop orchestrator.""" enabled: bool = True interval_seconds: int = 30 max_agents_per_tick: int = 50 max_episodes_per_tick: int = 200 @dataclass(frozen=True) class HAIMConfig: """Root configuration for the HAIM system.""" version: str = "4.5" dimensionality: int = 16384 encoding: EncodingConfig = field(default_factory=EncodingConfig) tiers_hot: TierConfig = field( default_factory=lambda: TierConfig(max_memories=2000, ltp_threshold_min=0.7) ) tiers_warm: TierConfig = field( default_factory=lambda: TierConfig( max_memories=100000, ltp_threshold_min=0.3, consolidation_interval_hours=1, storage_backend="mmap", ) ) tiers_cold: TierConfig = field( default_factory=lambda: TierConfig( max_memories=0, # unlimited ltp_threshold_min=0.0, storage_backend="filesystem", ) ) ltp: LTPConfig = field(default_factory=LTPConfig) hysteresis: HysteresisConfig = field(default_factory=HysteresisConfig) redis: RedisConfig = field(default_factory=RedisConfig) qdrant: QdrantConfig = field(default_factory=QdrantConfig) gpu: GPUConfig = field(default_factory=GPUConfig) security: SecurityConfig = field(default_factory=SecurityConfig) observability: ObservabilityConfig = field(default_factory=ObservabilityConfig) mcp: MCPConfig = field(default_factory=MCPConfig) paths: PathsConfig = field(default_factory=PathsConfig) consolidation: ConsolidationConfig = field(default_factory=ConsolidationConfig) attention_masking: AttentionMaskingConfig = field(default_factory=AttentionMaskingConfig) synapse: SynapseConfig = field(default_factory=SynapseConfig) context: ContextConfig = field(default_factory=ContextConfig) preference: PreferenceConfig = field(default_factory=PreferenceConfig) anticipatory: AnticipatoryConfig = field(default_factory=AnticipatoryConfig) dream_loop: DreamLoopConfig = field(default_factory=DreamLoopConfig) subconscious_ai: SubconsciousAIConfig = field(default_factory=SubconsciousAIConfig) pulse: PulseConfig = field(default_factory=PulseConfig) def _env_override(key: str, default): """Check for HAIM_ environment variable override.""" env_key = f"HAIM_{key.upper()}" val = os.environ.get(env_key) if val is None: return default # Type coercion based on the default's type if isinstance(default, bool): return val.lower() in ("true", "1", "yes") if isinstance(default, int): return int(val) if isinstance(default, float): return float(val) return val def _build_tier(name: str, raw: dict) -> TierConfig: prefix = f"TIERS_{name.upper()}" return TierConfig( max_memories=_env_override(f"{prefix}_MAX_MEMORIES", raw.get("max_memories", 0)), ltp_threshold_min=_env_override(f"{prefix}_LTP_THRESHOLD_MIN", raw.get("ltp_threshold_min", 0.0)), eviction_policy=raw.get("eviction_policy", "lru"), consolidation_interval_hours=raw.get("consolidation_interval_hours"), storage_backend=raw.get("storage_backend", "memory"), compression=raw.get("compression", "gzip"), archive_threshold_days=raw.get("archive_threshold_days", 30), ) def _parse_optional_positive_int(value: Optional[object]) -> Optional[int]: """Parse positive int values. Non-positive/invalid values become None.""" if value is None: return None try: parsed = int(value) except (TypeError, ValueError): return None return parsed if parsed > 0 else None def load_config(path: Optional[Path] = None) -> HAIMConfig: """ Load configuration from YAML file with environment variable overrides. Priority: ENV > YAML > defaults. Args: path: Path to config.yaml. If None, searches ./config.yaml and ../config.yaml. Returns: Validated HAIMConfig instance. Raises: ConfigurationError: If dimensionality is not a multiple of 64. FileNotFoundError: If no config file is found and path is explicitly set. """ if path is None: # Search common locations candidates = [ Path("config.yaml"), Path(__file__).parent.parent.parent / "config.yaml", ] for candidate in candidates: if candidate.exists(): path = candidate break raw = {} if path is not None and path.exists(): with open(path) as f: loaded = yaml.safe_load(f) or {} raw = loaded.get("haim") or {} # Apply env overrides to top-level scalars dimensionality = _env_override( "DIMENSIONALITY", raw.get("dimensionality", 16384) ) # Validate if dimensionality % 64 != 0: raise ConfigurationError( config_key="dimensionality", reason=f"Dimensionality must be a multiple of 64 for efficient bit packing, got {dimensionality}" ) # Build tier configs tiers_raw = raw.get("tiers") or {} hot_raw = tiers_raw.get("hot", {"max_memories": 2000, "ltp_threshold_min": 0.7}) warm_raw = tiers_raw.get( "warm", { "max_memories": 100000, "ltp_threshold_min": 0.3, "consolidation_interval_hours": 1, "storage_backend": "mmap", }, ) cold_raw = tiers_raw.get( "cold", { "max_memories": 0, "ltp_threshold_min": 0.0, "storage_backend": "filesystem", }, ) # Build encoding config enc_raw = raw.get("encoding") or {} encoding = EncodingConfig( mode=_env_override("ENCODING_MODE", enc_raw.get("mode", "binary")), token_method=enc_raw.get("token_method", "bundle"), ) # Build paths config paths_raw = raw.get("paths") or {} paths = PathsConfig( data_dir=_env_override("DATA_DIR", paths_raw.get("data_dir", "./data")), memory_file=_env_override("MEMORY_FILE", paths_raw.get("memory_file", "./data/memory.jsonl")), codebook_file=_env_override("CODEBOOK_FILE", paths_raw.get("codebook_file", "./data/codebook.json")), concepts_file=_env_override("CONCEPTS_FILE", paths_raw.get("concepts_file", "./data/concepts.json")), synapses_file=_env_override("SYNAPSES_FILE", paths_raw.get("synapses_file", "./data/synapses.json")), warm_mmap_dir=_env_override("WARM_MMAP_DIR", paths_raw.get("warm_mmap_dir", "./data/warm_tier")), cold_archive_dir=_env_override("COLD_ARCHIVE_DIR", paths_raw.get("cold_archive_dir", "./data/cold_archive")), ) # Build redis config redis_raw = raw.get("redis") or {} redis = RedisConfig( url=_env_override("REDIS_URL", redis_raw.get("url", "redis://localhost:6379/0")), stream_key=redis_raw.get("stream_key", "haim:subconscious"), max_connections=redis_raw.get("max_connections", 10), socket_timeout=redis_raw.get("socket_timeout", 5), password=_env_override("REDIS_PASSWORD", redis_raw.get("password")), ) # Build qdrant config qdrant_raw = raw.get("qdrant") or {} qdrant = QdrantConfig( url=_env_override( "QDRANT_URL", qdrant_raw.get("url", "http://localhost:6333") ), collection_hot=qdrant_raw.get("collection_hot", "haim_hot"), collection_warm=qdrant_raw.get("collection_warm", "haim_warm"), binary_quantization=qdrant_raw.get("binary_quantization", True), always_ram=qdrant_raw.get("always_ram", True), hnsw_m=qdrant_raw.get("hnsw_m", 16), hnsw_ef_construct=qdrant_raw.get("hnsw_ef_construct", 100), api_key=_env_override("QDRANT_API_KEY", qdrant_raw.get("api_key")), ) # Build GPU config gpu_raw = raw.get("gpu") or {} gpu = GPUConfig( enabled=_env_override("GPU_ENABLED", gpu_raw.get("enabled", False)), device=gpu_raw.get("device", "cuda:0"), batch_size=gpu_raw.get("batch_size", 1000), fallback_to_cpu=gpu_raw.get("fallback_to_cpu", True), ) # Build observability config obs_raw = raw.get("observability") or {} observability = ObservabilityConfig( metrics_port=obs_raw.get("metrics_port", 9090), log_level=_env_override("LOG_LEVEL", obs_raw.get("log_level", "INFO")), structured_logging=obs_raw.get("structured_logging", True), ) # Build security config sec_raw = raw.get("security") or {} # Parse CORS origins from env (comma-separated) or config cors_env = os.environ.get("HAIM_CORS_ORIGINS") if cors_env: cors_origins = [o.strip() for o in cors_env.split(",")] else: cors_origins = sec_raw.get("cors_origins", ["*"]) security = SecurityConfig( api_key=_env_override("API_KEY", sec_raw.get("api_key")), cors_origins=cors_origins, rate_limit_enabled=_env_override("RATE_LIMIT_ENABLED", sec_raw.get("rate_limit_enabled", True)), rate_limit_requests=_env_override("RATE_LIMIT_REQUESTS", sec_raw.get("rate_limit_requests", 100)), rate_limit_window=_env_override("RATE_LIMIT_WINDOW", sec_raw.get("rate_limit_window", 60)), ) # Build MCP config mcp_raw = raw.get("mcp") or {} allow_tools_default = [ "memory_store", "memory_query", "memory_get", "memory_delete", "memory_stats", "memory_health", ] mcp = MCPConfig( enabled=_env_override("MCP_ENABLED", mcp_raw.get("enabled", False)), transport=_env_override("MCP_TRANSPORT", mcp_raw.get("transport", "stdio")), host=_env_override("MCP_HOST", mcp_raw.get("host", "127.0.0.1")), port=_env_override("MCP_PORT", mcp_raw.get("port", 8110)), api_base_url=_env_override("MCP_API_BASE_URL", mcp_raw.get("api_base_url", "http://localhost:8100")), api_key=_env_override("MCP_API_KEY", mcp_raw.get("api_key", sec_raw.get("api_key"))), timeout_seconds=_env_override("MCP_TIMEOUT_SECONDS", mcp_raw.get("timeout_seconds", 15)), allow_tools=mcp_raw.get("allow_tools", allow_tools_default), ) # Build hysteresis config hyst_raw = raw.get("hysteresis") or {} hysteresis = HysteresisConfig( promote_delta=_env_override("HYSTERESIS_PROMOTE_DELTA", hyst_raw.get("promote_delta", 0.15)), demote_delta=_env_override("HYSTERESIS_DEMOTE_DELTA", hyst_raw.get("demote_delta", 0.10)), ) # Build LTP config ltp_raw = raw.get("ltp") or {} ltp = LTPConfig( initial_importance=_env_override("LTP_INITIAL_IMPORTANCE", ltp_raw.get("initial_importance", 0.5)), decay_lambda=_env_override("LTP_DECAY_LAMBDA", ltp_raw.get("decay_lambda", 0.01)), permanence_threshold=_env_override("LTP_PERMANENCE_THRESHOLD", ltp_raw.get("permanence_threshold", 0.95)), half_life_days=_env_override("LTP_HALF_LIFE_DAYS", ltp_raw.get("half_life_days", 30.0)), ) # Build attention masking config (Phase 4.1) attn_raw = raw.get("attention_masking") or {} attention_masking = AttentionMaskingConfig( enabled=_env_override("ATTENTION_MASKING_ENABLED", attn_raw.get("enabled", True)), ) # Build consolidation config (Phase 4.0+) cons_raw = raw.get("consolidation") or {} consolidation = ConsolidationConfig( enabled=_env_override("CONSOLIDATION_ENABLED", cons_raw.get("enabled", True)), interval_seconds=_env_override("CONSOLIDATION_INTERVAL_SECONDS", cons_raw.get("interval_seconds", 3600)), similarity_threshold=_env_override("CONSOLIDATION_SIMILARITY_THRESHOLD", cons_raw.get("similarity_threshold", 0.85)), min_cluster_size=_env_override("CONSOLIDATION_MIN_CLUSTER_SIZE", cons_raw.get("min_cluster_size", 2)), hot_tier_enabled=_env_override("CONSOLIDATION_HOT_TIER_ENABLED", cons_raw.get("hot_tier_enabled", True)), warm_tier_enabled=_env_override("CONSOLIDATION_WARM_TIER_ENABLED", cons_raw.get("warm_tier_enabled", True)), ) # Build dream loop config dream_raw = raw.get("dream_loop") or {} raw_queue_maxlen = dream_raw.get("subconscious_queue_maxlen") env_queue_maxlen = os.environ.get("HAIM_DREAM_LOOP_SUBCONSCIOUS_QUEUE_MAXLEN") queue_maxlen = _parse_optional_positive_int( env_queue_maxlen if env_queue_maxlen is not None else raw_queue_maxlen ) dream_loop = DreamLoopConfig( enabled=_env_override("DREAM_LOOP_ENABLED", dream_raw.get("enabled", True)), frequency_seconds=_env_override("DREAM_LOOP_FREQUENCY_SECONDS", dream_raw.get("frequency_seconds", 60)), batch_size=_env_override("DREAM_LOOP_BATCH_SIZE", dream_raw.get("batch_size", 10)), max_iterations=_env_override("DREAM_LOOP_MAX_ITERATIONS", dream_raw.get("max_iterations", 0)), subconscious_queue_maxlen=queue_maxlen, ollama_url=_env_override("DREAM_LOOP_OLLAMA_URL", dream_raw.get("ollama_url", "http://localhost:11434/api/generate")), model=_env_override("DREAM_LOOP_MODEL", dream_raw.get("model", "gemma3:1b")), ) # Build synapse config (Phase 12.1) syn_raw = raw.get("synapse") or {} synapse = SynapseConfig( similarity_threshold=_env_override("SYNAPSE_SIMILARITY_THRESHOLD", syn_raw.get("similarity_threshold", 0.5)), auto_bind_on_store=_env_override("SYNAPSE_AUTO_BIND_ON_STORE", syn_raw.get("auto_bind_on_store", True)), multi_hop_depth=_env_override("SYNAPSE_MULTI_HOP_DEPTH", syn_raw.get("multi_hop_depth", 2)), ) # Build context config (Phase 12.2) ctx_raw = raw.get("context") or {} context = ContextConfig( enabled=_env_override("CONTEXT_ENABLED", ctx_raw.get("enabled", True)), shift_threshold=_env_override("CONTEXT_SHIFT_THRESHOLD", ctx_raw.get("shift_threshold", 0.3)), rolling_window_size=_env_override("CONTEXT_ROLLING_WINDOW_SIZE", ctx_raw.get("rolling_window_size", 5)), ) # Build preference config (Phase 12.3) pref_raw = raw.get("preference") or {} preference = PreferenceConfig( enabled=_env_override("PREFERENCE_ENABLED", pref_raw.get("enabled", True)), learning_rate=_env_override("PREFERENCE_LEARNING_RATE", pref_raw.get("learning_rate", 0.1)), history_limit=_env_override("PREFERENCE_HISTORY_LIMIT", pref_raw.get("history_limit", 100)), ) # Build anticipatory config (Phase 13.2) ant_raw = raw.get("anticipatory") or {} anticipatory = AnticipatoryConfig( enabled=_env_override("ANTICIPATORY_ENABLED", ant_raw.get("enabled", True)), predictive_depth=_env_override("ANTICIPATORY_PREDICTIVE_DEPTH", ant_raw.get("predictive_depth", 1)), ) # Build subconscious AI config (Phase 4.4 BETA) sub_raw = raw.get("subconscious_ai") or {} subconscious_ai = SubconsciousAIConfig( enabled=_env_override("SUBCONSCIOUS_AI_ENABLED", sub_raw.get("enabled", False)), beta_mode=_env_override("SUBCONSCIOUS_AI_BETA_MODE", sub_raw.get("beta_mode", True)), model_provider=_env_override("SUBCONSCIOUS_AI_MODEL_PROVIDER", sub_raw.get("model_provider", "ollama")), model_name=_env_override("SUBCONSCIOUS_AI_MODEL_NAME", sub_raw.get("model_name", "phi3.5:3.8b")), model_url=_env_override("SUBCONSCIOUS_AI_MODEL_URL", sub_raw.get("model_url", "http://localhost:11434")), api_key=_env_override("SUBCONSCIOUS_AI_API_KEY", sub_raw.get("api_key")), api_base_url=_env_override("SUBCONSCIOUS_AI_API_BASE_URL", sub_raw.get("api_base_url")), pulse_interval_seconds=_env_override("SUBCONSCIOUS_AI_PULSE_INTERVAL_SECONDS", sub_raw.get("pulse_interval_seconds", 120)), pulse_backoff_enabled=_env_override("SUBCONSCIOUS_AI_PULSE_BACKOFF_ENABLED", sub_raw.get("pulse_backoff_enabled", True)), pulse_backoff_max_seconds=_env_override("SUBCONSCIOUS_AI_PULSE_BACKOFF_MAX_SECONDS", sub_raw.get("pulse_backoff_max_seconds", 600)), max_cpu_percent=_env_override("SUBCONSCIOUS_AI_MAX_CPU_PERCENT", sub_raw.get("max_cpu_percent", 30.0)), cycle_timeout_seconds=_env_override("SUBCONSCIOUS_AI_CYCLE_TIMEOUT_SECONDS", sub_raw.get("cycle_timeout_seconds", 30)), rate_limit_per_hour=_env_override("SUBCONSCIOUS_AI_RATE_LIMIT_PER_HOUR", sub_raw.get("rate_limit_per_hour", 50)), memory_sorting_enabled=_env_override("SUBCONSCIOUS_AI_MEMORY_SORTING_ENABLED", sub_raw.get("memory_sorting_enabled", True)), enhanced_dreaming_enabled=_env_override("SUBCONSCIOUS_AI_ENHANCED_DREAMING_ENABLED", sub_raw.get("enhanced_dreaming_enabled", True)), micro_self_improvement_enabled=_env_override("SUBCONSCIOUS_AI_MICRO_SELF_IMPROVEMENT_ENABLED", sub_raw.get("micro_self_improvement_enabled", False)), dry_run=_env_override("SUBCONSCIOUS_AI_DRY_RUN", sub_raw.get("dry_run", True)), log_all_decisions=_env_override("SUBCONSCIOUS_AI_LOG_ALL_DECISIONS", sub_raw.get("log_all_decisions", True)), audit_trail_path=_env_override("SUBCONSCIOUS_AI_AUDIT_TRAIL_PATH", sub_raw.get("audit_trail_path", "./data/subconscious_audit.jsonl")), max_memories_per_cycle=_env_override("SUBCONSCIOUS_AI_MAX_MEMORIES_PER_CYCLE", sub_raw.get("max_memories_per_cycle", 10)), ) # Build pulse config (Phase 5.0) pulse_raw = raw.get("pulse") or {} pulse = PulseConfig( enabled=_env_override("PULSE_ENABLED", pulse_raw.get("enabled", True)), interval_seconds=_env_override("PULSE_INTERVAL_SECONDS", pulse_raw.get("interval_seconds", 30)), max_agents_per_tick=_env_override("PULSE_MAX_AGENTS_PER_TICK", pulse_raw.get("max_agents_per_tick", 50)), max_episodes_per_tick=_env_override("PULSE_MAX_EPISODES_PER_TICK", pulse_raw.get("max_episodes_per_tick", 200)), ) return HAIMConfig( version=raw.get("version", "4.5"), dimensionality=dimensionality, encoding=encoding, tiers_hot=_build_tier("hot", hot_raw), tiers_warm=_build_tier("warm", warm_raw), tiers_cold=_build_tier("cold", cold_raw), ltp=ltp, hysteresis=hysteresis, redis=redis, qdrant=qdrant, gpu=gpu, security=security, observability=observability, mcp=mcp, paths=paths, consolidation=consolidation, attention_masking=attention_masking, synapse=synapse, context=context, preference=preference, anticipatory=anticipatory, dream_loop=dream_loop, subconscious_ai=subconscious_ai, pulse=pulse, ) # Module-level singleton (lazy-loaded) _CONFIG: Optional[HAIMConfig] = None def get_config() -> HAIMConfig: """Get or initialize the global config singleton.""" global _CONFIG if _CONFIG is None: _CONFIG = load_config() return _CONFIG def reset_config(): """Reset the global config singleton (useful for testing).""" global _CONFIG _CONFIG = None