Skip to main content

holographic_memory/core/
config.rs

1// Copyright 2024-2026 WritersLogic Contributors
2// SPDX-License-Identifier: Apache-2.0
3
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Debug, Default)]
7pub struct HmsConfig {
8    pub text_encoder: super::encoding::TextEncoder,
9    pub ivf: IVFConfig,
10    pub nsg: NSGConfig,
11    pub shard: ShardConfig,
12    pub query: QueryConfig,
13    pub concepts: ConceptsConfig,
14    pub diffusion: DiffusionConfig,
15    pub security: SecurityConfig,
16    pub provenance: ProvenanceConfig,
17    pub privacy: PrivacyConfig,
18    pub meaning: MeaningConfig,
19    pub cognition: CognitionConfig,
20    pub hopfield: super::hopfield::HopfieldConfig,
21    pub embedding_space: Option<super::schema::EmbeddingSpace>,
22    /// Options of the local embedder `TextEncoder::LocalEmbedV1` loads (default options when
23    /// absent). Ignored by the other encoders.
24    #[cfg(feature = "local-embed")]
25    pub local_embed: Option<super::embed::EmbedOptions>,
26}
27
28#[derive(Clone, Debug)]
29pub struct MeaningConfig {
30    pub enabled: bool,
31    pub beta: f64,
32    pub algebraic_max_fanout: usize,
33    pub auto_decompose: bool,
34    pub max_hop_depth: usize,
35    pub max_rule_depth: usize,
36    pub idf_clip_factor: f64,
37}
38
39impl Default for MeaningConfig {
40    fn default() -> Self {
41        Self {
42            enabled: false,
43            beta: 24.0,
44            algebraic_max_fanout: 40,
45            auto_decompose: false,
46            max_hop_depth: 10,
47            max_rule_depth: 10,
48            idf_clip_factor: 3.0,
49        }
50    }
51}
52
53#[derive(Clone, Debug, Default)]
54pub struct SecurityConfig {
55    pub signing_enabled: bool,
56    pub key_path: Option<String>,
57    pub encryption_enabled: bool,
58    pub encryption_passphrase: Option<String>,
59    /// Name of an environment variable containing the encryption passphrase.
60    /// Prefer this over embedding a secret in application configuration.
61    pub encryption_passphrase_env: Option<String>,
62    pub audit_enabled: bool,
63}
64
65#[derive(Clone, Debug, Default)]
66pub struct ProvenanceConfig {
67    pub enabled: bool,
68    pub key_path: Option<String>,
69    pub auto_sign: bool,
70    #[cfg(feature = "provenance-scitt")]
71    pub scitt_endpoint: Option<String>,
72}
73
74#[derive(Clone, Debug)]
75pub struct PrivacyConfig {
76    /// Enable epsilon-differential privacy in bundle operations.
77    pub dp_enabled: bool,
78    /// Privacy budget epsilon. Smaller = more private, noisier.
79    /// Typical range: 0.1 (strong) to 10.0 (weak).
80    pub epsilon: f64,
81}
82
83impl Default for PrivacyConfig {
84    fn default() -> Self {
85        Self {
86            dp_enabled: false,
87            epsilon: 1.0,
88        }
89    }
90}
91
92#[derive(Clone, Debug)]
93pub struct QueryConfig {
94    pub component_similarity_threshold: f64,
95    pub component_max_neighbors: u32,
96}
97
98impl Default for QueryConfig {
99    fn default() -> Self {
100        Self {
101            component_similarity_threshold: 0.05,
102            component_max_neighbors: 20,
103        }
104    }
105}
106
107#[derive(Clone, Debug)]
108pub struct ConceptsConfig {
109    pub similarity_threshold: f64,
110    pub min_cluster_size: usize,
111}
112
113impl Default for ConceptsConfig {
114    fn default() -> Self {
115        Self {
116            similarity_threshold: 0.3,
117            min_cluster_size: 3,
118        }
119    }
120}
121
122#[derive(Clone, Debug)]
123pub struct DiffusionConfig {
124    pub steps: usize,
125    pub sigma_max: f64,
126    pub sigma_min: f64,
127    pub step_size: f64,
128    pub n_langevin: usize,
129}
130
131impl Default for DiffusionConfig {
132    fn default() -> Self {
133        Self {
134            steps: 10,
135            sigma_max: 0.5,
136            sigma_min: 0.01,
137            step_size: 0.1,
138            n_langevin: 5,
139        }
140    }
141}
142
143#[derive(Clone, Debug)]
144pub struct ShardConfig {
145    pub enabled: bool,
146    pub shard_count: usize,
147    pub auto_threshold: usize,
148    pub target_shard_size: usize,
149}
150
151impl Default for ShardConfig {
152    fn default() -> Self {
153        Self {
154            enabled: false,
155            shard_count: 0,
156            auto_threshold: 1_000_000,
157            target_shard_size: 250_000,
158        }
159    }
160}
161
162#[derive(Clone, Debug, Serialize, Deserialize)]
163pub struct NSGConfig {
164    pub max_degree: usize,
165    pub ef_construction: usize,
166    pub auto_threshold: usize,
167    pub seed: u64,
168}
169
170impl Default for NSGConfig {
171    fn default() -> Self {
172        Self {
173            max_degree: 32,
174            ef_construction: 128,
175            auto_threshold: 10_000,
176            seed: 42,
177        }
178    }
179}
180
181#[derive(Clone, Debug)]
182pub struct IVFConfig {
183    /// Controls auto-training only. Manual `train_ivf()` works regardless.
184    pub enabled: bool,
185    pub n_clusters: usize,
186    pub n_landmarks: usize,
187    pub d_reduced: usize,
188    pub n_probe: usize,
189    pub auto_threshold: usize,
190}
191
192impl Default for IVFConfig {
193    fn default() -> Self {
194        Self {
195            enabled: false,
196            n_clusters: 256,
197            n_landmarks: 1024,
198            d_reduced: 128,
199            n_probe: 8,
200            auto_threshold: 10_000,
201        }
202    }
203}
204
205#[derive(Clone, Debug)]
206pub struct CognitionConfig {
207    pub enabled: bool,
208    pub interval_secs: u64,
209    pub min_pattern_freq: usize,
210    pub min_abstraction_members: usize,
211    pub min_shared_relations: usize,
212    pub min_peer_coverage: f64,
213    pub hypothesis_beta: f64,
214    pub min_hypothesis_confidence: f64,
215    pub min_analogy_relations: usize,
216    pub governor_duplicate_threshold: f64,
217    pub governor_max_scan_size: usize,
218    pub governor_forget_unreferenced: bool,
219    pub refine_atoms: bool,
220}
221
222impl Default for CognitionConfig {
223    fn default() -> Self {
224        Self {
225            enabled: false,
226            interval_secs: 60,
227            min_pattern_freq: 3,
228            min_abstraction_members: 3,
229            min_shared_relations: 2,
230            min_peer_coverage: 0.5,
231            hypothesis_beta: 24.0,
232            min_hypothesis_confidence: 0.3,
233            min_analogy_relations: 2,
234            governor_duplicate_threshold: 0.95,
235            governor_max_scan_size: 1000,
236            governor_forget_unreferenced: false,
237            refine_atoms: false,
238        }
239    }
240}
241
242impl HmsConfig {
243    pub(crate) fn validate(&self) -> anyhow::Result<()> {
244        anyhow::ensure!(
245            self.text_encoder != super::encoding::TextEncoder::MultiscaleCappedUnionV1
246                || cfg!(feature = "experimental"),
247            "capped-union text encoding requires a build with the experimental feature"
248        );
249        anyhow::ensure!(
250            self.text_encoder != super::encoding::TextEncoder::LocalEmbedV1
251                || cfg!(feature = "local-embed"),
252            "the local-embed-v1 text encoder requires a build with the local-embed feature"
253        );
254        anyhow::ensure!(
255            cfg!(feature = "security")
256                || !(self.security.signing_enabled || self.security.encryption_enabled),
257            "signing/encryption requires a build with the security feature"
258        );
259        anyhow::ensure!(
260            cfg!(feature = "provenance") || !self.provenance.enabled,
261            "provenance requires a build with the provenance feature"
262        );
263        anyhow::ensure!(
264            self.privacy.epsilon.is_finite() && self.privacy.epsilon > 0.0,
265            "privacy epsilon must be finite and positive"
266        );
267        anyhow::ensure!(
268            self.shard.target_shard_size > 0 && self.shard.shard_count <= 1024,
269            "invalid shard configuration"
270        );
271        anyhow::ensure!(
272            self.meaning.beta.is_finite() && self.meaning.beta > 0.0,
273            "meaning beta must be finite and positive"
274        );
275        if let Some(space) = &self.embedding_space {
276            anyhow::ensure!(!space.model.is_empty() && !space.revision.is_empty() && (1..=65536).contains(&space.dimensions)
277                && space.metric == "cosine" && space.normalization == "l2",
278                "embedding space requires model, revision, dimensions 1..=65536, normalization l2 and metric cosine");
279        }
280        Ok(())
281    }
282}