pub mod memory;
pub use memory::{HirnMemory, MemoryRecallBuilder, MemoryThinkBuilder};
pub type Hirn = hirn_engine::HirnDB;
pub use hirn_core::ConflictResolutionPolicy;
pub use hirn_core::ConflictResolutionPolicyOverrides;
pub use hirn_core::EmbedderCircuitBreakerRuntimeConfig;
pub use hirn_core::EmbedderPersistentCacheRuntimeConfig;
pub use hirn_core::EmbedderRetryConfig;
pub use hirn_core::EmbedderRuntimeConfig;
pub use hirn_core::EstimatingTokenizer;
pub use hirn_core::HirnConfig;
pub use hirn_core::HirnError;
pub use hirn_core::HirnResult;
pub use hirn_core::MemoryId;
pub use hirn_core::RecallSnapshot;
pub use hirn_core::RevisionId;
pub use hirn_core::Timestamp;
pub use hirn_core::Tokenizer;
pub use hirn_core::embed::{
CharEstimateCounter, ChatMessage, Embedder, Embedding, EntityExtractor, ExtractedEntity,
ExtractedRelation, LlmChunk, LlmOptions, LlmProvider, LlmResponse, LlmStream, NoopReranker,
RerankResult, Reranker, ResponseFormat, TokenCounter, TokenUsage,
};
pub use hirn_engine::AgentContext;
pub use hirn_engine::DbStats;
pub use hirn_engine::HirnDB;
pub use hirn_engine::IntegrityIssue;
pub use hirn_engine::IntegrityReport;
pub use hirn_engine::IssueKind;
pub use hirn_engine::LayerCounts;
pub use hirn_engine::MemoryEvent;
pub use hirn_engine::RepairReport;
pub use hirn_engine::SemanticRevisionIntegrityIssue;
pub use hirn_engine::SemanticRevisionIntegrityReport;
pub use hirn_engine::SemanticRevisionIssueKind;
pub use hirn_engine::SemanticRevisionRepairReport;
pub use hirn_engine::StoreError;
pub use hirn_engine::{
ApiKeySource, DefaultsConfig, EmbedderConfig, LlmConfig, ProviderConfig, ProvidersSection,
RerankerConfig, TokenizerConfig,
};
pub use hirn_engine::{ProviderDefaults, ProviderRegistry};
pub use hirn_engine::{inspected_result_to_json, trace_result_to_json, traced_result_to_json};
pub mod agent {
pub use hirn_core::agent::AgentRecord;
pub use hirn_core::namespace::NamespaceRecord;
pub use hirn_core::types::{AgentId, Namespace, NamespaceKind};
pub use hirn_engine::AgentContext;
pub use hirn_engine::CrossAgentConsolidationResult;
}
pub mod episodic {
pub use hirn_core::episodic::{EpisodicRecord, EpisodicRecordBuilder};
pub use hirn_core::types::EventType;
pub use hirn_engine::EpisodicFilter;
}
pub mod semantic {
pub use hirn_core::semantic::{ConceptEdge, SemanticRecord, SemanticRecordBuilder};
pub use hirn_core::types::KnowledgeType;
pub use hirn_engine::{
SemanticFilter, SemanticMerge, SemanticMergeOutcome, SemanticOverride, SemanticRetraction,
SemanticSupersession, SemanticUpdate,
};
}
pub mod working {
pub use hirn_core::types::Priority;
pub use hirn_core::working::{WorkingMemoryEntry, WorkingMemoryEntryBuilder};
}
pub mod procedural {
pub use hirn_core::procedural::{ActionStep, ProceduralRecord, ProceduralRecordBuilder};
}
pub mod record {
pub use hirn_core::record::MemoryRecord;
pub use hirn_core::types::{Layer, MemoryRef};
}
pub mod graph {
pub use hirn_core::types::EdgeRelation;
pub use hirn_engine::{EdgeId, GraphEdge, GraphNodeData};
}
pub mod activation {
pub use hirn_engine::activation::{
ActivationConfig, ActivationMode, ActivationResult, ActivationTrace,
};
}
pub mod causal {
pub use hirn_engine::{
CausalChain, CausalChainResult, CausalLink, ContradictionDetection, Counterfactual,
CounterfactualConstraint, TraceReport,
};
}
pub mod consolidation {
pub use hirn_engine::{
ConsolidateBuilder, ConsolidationConfig, ConsolidationResult, ConsolidationScheduler,
ConsolidationStatus, DetectedPatterns, EpisodeSegment, ForgettingResult, NarrativeThread,
Pattern, ReconsolidationTracker, ReconsolidationUpdate,
};
}
pub mod scoring {
pub use hirn_engine::ScoringWeights;
}
pub mod hebbian {
pub use hirn_engine::{HebbianConfig, HebbianUpdateResult};
}
pub mod ql {
pub use hirn_engine::ql::ast;
pub use hirn_engine::ql::context::{
ConflictArbitrationStatus, ConflictGroup, ConflictMember, ConflictMemberStatus,
ConflictPair, ContextConfig, ContextFormat, ThinkResult,
};
pub use hirn_engine::ql::revision_query_result_to_json;
pub use hirn_engine::{ParseError, QueryPlan, QueryResult, Statement};
}
pub mod provenance {
pub use hirn_core::audit::{AuditAction, AuditEntry};
pub use hirn_core::provenance::{EvidenceRef, Mutation, Provenance};
pub use hirn_core::types::{MutationTrigger, Origin};
}
pub mod security {
pub use hirn_core::QuarantinedRecordKind;
pub use hirn_engine::{QuarantineEntry, QuarantineStatus};
}
pub mod query {
pub use hirn_engine::recall::{LayerFilter, RecallResult};
pub use hirn_engine::{RecallBuilder, ThinkBuilder, TraceBuilder, TraceResult};
}
pub mod metadata {
pub use hirn_core::metadata::{Metadata, MetadataValue};
}
pub mod content {
pub use hirn_core::content::{
CompositeEmbeddingPolicy, CompositeModalityWeights, ExternalFetchPolicy, MemoryContent,
};
}
pub mod resource {
pub use hirn_core::resource::{
DerivedArtifact, DerivedArtifactBuilder, DerivedArtifactId, DerivedArtifactIndexPolicy,
DerivedArtifactIndexRule, DerivedArtifactKind, EvidenceLink, EvidenceProvenance,
EvidenceRole, HydrationMode, LogicalResourceId, ModalityProfile, ResourceGovernanceState,
ResourceId, ResourceIndexPolicy, ResourceIndexRule, ResourceLocation, ResourceObject,
ResourceObjectBuilder, ResourceQuotaPolicy, ResourceQuotaRule, ResourceQuotaScope,
ResourceRetentionAction, ResourceRetentionPolicy, ResourceRetentionRule,
ResourceRevisionId, SecondaryIndexType,
};
}
pub mod prelude {
pub use crate::Hirn;
pub use crate::{HirnConfig, HirnError, HirnResult};
pub use crate::{HirnMemory, MemoryRecallBuilder, MemoryThinkBuilder};
pub use crate::{MemoryId, RecallSnapshot, RevisionId, Timestamp};
pub use hirn_core::episodic::EpisodicRecord;
pub use hirn_core::procedural::ProceduralRecord;
pub use hirn_core::record::MemoryRecord;
pub use hirn_core::semantic::SemanticRecord;
pub use hirn_core::working::WorkingMemoryEntry;
pub use hirn_core::types::{
AgentId, EdgeRelation, EventType, KnowledgeType, Layer, Namespace, Origin, Priority,
};
pub use hirn_engine::AgentContext;
pub use crate::MemoryEvent;
pub use hirn_engine::ActivationMode;
pub use hirn_engine::QueryResult;
pub use hirn_engine::ql::context::ThinkResult;
pub use hirn_engine::recall::RecallResult;
pub use hirn_core::metadata::Metadata;
pub use crate::content::MemoryContent;
pub use crate::resource::{DerivedArtifactKind, EvidenceRole, HydrationMode, ModalityProfile};
}
#[cfg(test)]
mod tests {
use super::prelude::*;
use hirn_storage::memory_store::MemoryStore;
use std::sync::Arc;
fn null_storage() -> Arc<dyn hirn_storage::PhysicalStore> {
Arc::new(MemoryStore::new())
}
async fn test_storage(base: &std::path::Path) -> Arc<dyn hirn_storage::PhysicalStore> {
let lance_path = base.join("lance");
hirn_storage::HirnDb::open(hirn_storage::HirnDbConfig::local(
lance_path.to_str().unwrap(),
))
.await
.unwrap()
.store_arc()
}
fn write_dev_hirn_toml(dir: &std::path::Path, extra: &str) {
std::fs::write(
dir.join("hirn.toml"),
format!("allow_pseudo_embedder_fallback = true\n{extra}"),
)
.unwrap();
}
#[tokio::test(flavor = "multi_thread")]
async fn open_and_close() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("test");
let config = HirnConfig::builder().db_path(&path).build().unwrap();
let brain = Hirn::open_with_config(config, null_storage())
.await
.unwrap();
let stats = brain.admin().stats().await.unwrap();
assert_eq!(stats.total_count, 0);
}
#[tokio::test(flavor = "multi_thread")]
async fn agent_lifecycle() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("test");
let config = HirnConfig::builder()
.db_path(&path)
.embedding_dimensions(3)
.build()
.unwrap();
let brain = Hirn::open_with_config(config, test_storage(dir.path()).await)
.await
.unwrap();
let agent = AgentId::new("test_agent").unwrap();
brain.register_agent(&agent, "Test Agent").await.unwrap();
let ctx = brain.as_agent(&agent).await.unwrap();
let episode = EpisodicRecord::builder()
.content("hirn is a cognitive memory engine")
.event_type(EventType::Observation)
.agent_id(agent.clone())
.build()
.unwrap();
let id = ctx.remember(episode).await.unwrap();
let inspected = ctx.inspect(id).await;
assert!(inspected.is_ok());
}
#[tokio::test(flavor = "multi_thread")]
async fn multi_agent_isolation() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("test");
let config = HirnConfig::builder()
.db_path(&path)
.embedding_dimensions(3)
.build()
.unwrap();
let brain = Hirn::open_with_config(config, test_storage(dir.path()).await)
.await
.unwrap();
let a = AgentId::new("agent_a").unwrap();
let b = AgentId::new("agent_b").unwrap();
brain.register_agent(&a, "A").await.unwrap();
brain.register_agent(&b, "B").await.unwrap();
let ctx_a = brain.as_agent(&a).await.unwrap();
let ctx_b = brain.as_agent(&b).await.unwrap();
let ep = EpisodicRecord::builder()
.content("secret from agent A")
.event_type(EventType::Observation)
.agent_id(a.clone())
.build()
.unwrap();
let id = ctx_a.remember(ep).await.unwrap();
assert!(ctx_b.inspect(id).await.is_err());
}
#[tokio::test(flavor = "multi_thread")]
async fn shared_namespace_visible_to_all() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("test");
let config = HirnConfig::builder()
.db_path(&path)
.embedding_dimensions(3)
.build()
.unwrap();
let brain = Hirn::open_with_config(config, test_storage(dir.path()).await)
.await
.unwrap();
let a = AgentId::new("agent_a").unwrap();
let b = AgentId::new("agent_b").unwrap();
brain.register_agent(&a, "A").await.unwrap();
brain.register_agent(&b, "B").await.unwrap();
let ctx_a = brain.as_agent(&a).await.unwrap();
let ctx_b = brain.as_agent(&b).await.unwrap();
let mut ep = EpisodicRecord::builder()
.content("shared knowledge")
.event_type(EventType::Observation)
.agent_id(a.clone())
.build()
.unwrap();
ep.namespace = Namespace::shared();
let id = ctx_a.remember(ep).await.unwrap();
assert!(ctx_a.inspect(id).await.is_ok());
assert!(ctx_b.inspect(id).await.is_ok());
}
#[tokio::test(flavor = "multi_thread")]
async fn semantic_store_and_retrieve() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("test");
let config = HirnConfig::builder()
.db_path(&path)
.embedding_dimensions(3)
.build()
.unwrap();
let brain = Hirn::open_with_config(config, test_storage(dir.path()).await)
.await
.unwrap();
let agent = AgentId::new("learner").unwrap();
brain.register_agent(&agent, "Learner").await.unwrap();
let sem = SemanticRecord::builder()
.concept("rust_ownership")
.description("Rust uses ownership and borrowing for memory safety")
.knowledge_type(KnowledgeType::Propositional)
.confidence(0.95)
.agent_id(agent)
.build()
.unwrap();
let id = brain.semantic().store(sem).await.unwrap();
let retrieved = brain.semantic().get(id).await.unwrap();
assert_eq!(retrieved.concept, "rust_ownership");
assert!((retrieved.confidence - 0.95).abs() < f32::EPSILON);
}
#[tokio::test(flavor = "multi_thread")]
async fn hirnql_execution() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("test");
let config = HirnConfig::builder().db_path(&path).build().unwrap();
let brain = Hirn::open_with_config(config, null_storage())
.await
.unwrap();
let result = brain
.ql()
.execute(r#"RECALL episodic ABOUT "test" LIMIT 5"#)
.await;
assert!(result.is_ok());
}
#[tokio::test(flavor = "multi_thread")]
async fn hirn_type_alias_is_hirn_storage() {
fn accepts_hirn(_brain: &Hirn) {}
fn accepts_hirn_storage(_brain: &crate::HirnDB) {}
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("test");
let config = HirnConfig::builder().db_path(&path).build().unwrap();
let brain = Hirn::open_with_config(config, null_storage())
.await
.unwrap();
accepts_hirn(&brain);
accepts_hirn_storage(&brain);
}
mod zero_config {
use super::*;
#[tokio::test(flavor = "multi_thread")]
async fn open_remember_think_e2e() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("brain");
std::fs::write(
dir.path().join("hirn.toml"),
"allow_pseudo_embedder_fallback = true\n",
)
.unwrap();
let memory = HirnMemory::open(&path).await.unwrap();
memory.remember("User prefers dark mode").await.unwrap();
memory.remember("User likes Vim keybindings").await.unwrap();
memory.remember("User's timezone is UTC+1").await.unwrap();
let ctx = memory
.think("What are the user's preferences?", 2048)
.await
.unwrap();
assert!(!ctx.context.is_empty(), "think context should be non-empty");
}
#[tokio::test(flavor = "multi_thread")]
async fn explicit_pseudo_embedder_fallback_works() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("brain");
let mut config = HirnConfig::builder()
.db_path(&path)
.allow_pseudo_embedder_fallback(true)
.build()
.unwrap();
config.admission_enabled = true;
let memory = HirnMemory::open_with_config(config).await.unwrap();
let id = memory
.remember("Testing with pseudo embedder")
.await
.unwrap();
let record = memory.db().admin().get_memory(id).await.unwrap();
match record {
hirn_core::record::MemoryRecord::Episodic(ep) => {
assert_eq!(ep.content, "Testing with pseudo embedder");
}
_ => panic!("expected episodic record"),
}
}
#[tokio::test(flavor = "multi_thread")]
async fn open_fails_closed_without_provider_or_explicit_pseudo_opt_in() {
if crate::ProviderRegistry::from_env_strict()
.embedder()
.is_some()
{
return;
}
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("brain");
let error = HirnMemory::open(&path).await.err().unwrap();
assert!(matches!(
error,
HirnError::InvalidConfig { ref field, .. }
if field == "allow_pseudo_embedder_fallback"
));
}
#[tokio::test(flavor = "multi_thread")]
async fn five_line_example() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("brain");
std::fs::write(
dir.path().join("hirn.toml"),
"allow_pseudo_embedder_fallback = true\n",
)
.unwrap();
let memory = HirnMemory::open(&path).await.unwrap();
memory.remember("User prefers dark mode").await.unwrap();
let ctx = memory
.think("What are the user's UI preferences?", 2048)
.await
.unwrap();
assert!(!ctx.context.is_empty());
}
#[tokio::test(flavor = "multi_thread")]
async fn auto_entity_extraction() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("brain");
std::fs::write(
dir.path().join("hirn.toml"),
"allow_pseudo_embedder_fallback = true\n",
)
.unwrap();
let memory = HirnMemory::open(&path).await.unwrap();
let id = memory
.remember("User prefers Dark Mode in Visual Studio Code")
.await
.unwrap();
let record = memory.db().admin().get_memory(id).await.unwrap();
match record {
hirn_core::record::MemoryRecord::Episodic(ep) => {
assert!(
!ep.entities.is_empty(),
"entities should be auto-extracted; got none"
);
let names: Vec<&str> = ep.entities.iter().map(|e| e.name.as_str()).collect();
assert!(
names
.iter()
.any(|n| n.contains("Dark Mode") || n.contains("Visual Studio")),
"expected entity like 'Dark Mode' or 'Visual Studio Code', got {names:?}"
);
}
_ => panic!("expected episodic record"),
}
}
#[tokio::test(flavor = "multi_thread")]
async fn prelude_exports_hirn_memory() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("brain");
std::fs::write(
dir.path().join("hirn.toml"),
"allow_pseudo_embedder_fallback = true\n",
)
.unwrap();
let _memory: HirnMemory = HirnMemory::open(&path).await.unwrap();
}
}
mod hirnql_api {
use super::*;
#[tokio::test(flavor = "multi_thread")]
async fn recall_query_returns_results() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("brain");
write_dev_hirn_toml(dir.path(), "");
let memory = HirnMemory::open(&path).await.unwrap();
memory
.remember("JWT token should expire after 15 minutes")
.await
.unwrap();
memory
.remember("Auth uses OAuth2 with PKCE flow")
.await
.unwrap();
let result = memory
.query(r#"RECALL episodic ABOUT "auth" LIMIT 10"#)
.await
.unwrap();
match result {
hirn_engine::ql::QueryResult::Records(rr) => {
assert!(rr.records_returned > 0, "expected some records");
assert!(rr.query_time_ms >= 0.0);
}
other => panic!("expected Records, got {other:?}"),
}
}
#[tokio::test(flavor = "multi_thread")]
async fn remember_via_hirnql_is_rejected() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("brain");
write_dev_hirn_toml(dir.path(), "");
let memory = HirnMemory::open(&path).await.unwrap();
let error = memory
.query(r#"REMEMBER episode CONTENT "Database uses connection pooling""#)
.await
.unwrap_err();
assert!(
error
.to_string()
.contains("REMEMBER is not supported via embedded HirnQL anymore")
);
}
#[tokio::test(flavor = "multi_thread")]
async fn think_via_hirnql() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("brain");
write_dev_hirn_toml(dir.path(), "");
let memory = HirnMemory::open(&path).await.unwrap();
memory
.remember("User uses Neovim with LSP support")
.await
.unwrap();
let result = memory
.query(r#"THINK ABOUT "editor setup" BUDGET 1024"#)
.await
.unwrap();
match result {
hirn_engine::ql::QueryResult::Records(rr) => {
assert!(rr.context.is_some());
}
other => panic!("expected Records with context, got {other:?}"),
}
}
#[tokio::test(flavor = "multi_thread")]
async fn forget_via_hirnql_is_rejected() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("brain");
write_dev_hirn_toml(dir.path(), "");
let memory = HirnMemory::open(&path).await.unwrap();
let id = memory.remember("Temporary note to delete").await.unwrap();
let error = memory.query(&format!("FORGET \"{id}\"")).await.unwrap_err();
assert!(
error
.to_string()
.contains("FORGET is not supported via embedded HirnQL anymore")
);
}
#[tokio::test(flavor = "multi_thread")]
async fn invalid_hirnql_error_position() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("brain");
write_dev_hirn_toml(dir.path(), "");
let memory = HirnMemory::open(&path).await.unwrap();
let err = memory.query("INVALID SYNTAX HERE").await.unwrap_err();
let msg = err.to_string();
assert!(
msg.contains(':'),
"error should contain position info, got: {msg}"
);
}
}
mod builder_api {
use super::*;
#[tokio::test(flavor = "multi_thread")]
async fn recall_builder_works() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("brain");
write_dev_hirn_toml(dir.path(), "");
let memory = HirnMemory::open(&path).await.unwrap();
memory
.remember("JWT tokens expire after 15 minutes")
.await
.unwrap();
memory
.remember("OAuth2 PKCE flow for authentication")
.await
.unwrap();
let results = memory
.recall_builder("auth tokens")
.limit(5)
.episodic_only()
.execute()
.await
.unwrap();
assert!(!results.is_empty(), "builder recall should return results");
}
#[tokio::test(flavor = "multi_thread")]
async fn think_builder_with_budget() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("brain");
write_dev_hirn_toml(dir.path(), "");
let memory = HirnMemory::open(&path).await.unwrap();
memory
.remember("User prefers dark mode in all editors")
.await
.unwrap();
memory.remember("UI theme is Gruvbox Dark").await.unwrap();
let ctx = memory
.think_builder("editor theme preferences")
.budget(2048)
.execute()
.await
.unwrap();
assert!(
!ctx.context.is_empty(),
"think builder should produce context"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn builder_matches_hirnql() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("brain");
write_dev_hirn_toml(dir.path(), "");
let memory = HirnMemory::open(&path).await.unwrap();
memory
.remember("Kubernetes pods use resource limits")
.await
.unwrap();
let builder_results = memory
.recall_builder("kubernetes")
.limit(10)
.execute()
.await
.unwrap();
let ql_result = memory
.query(r#"RECALL episodic ABOUT "kubernetes" LIMIT 10"#)
.await
.unwrap();
match ql_result {
hirn_engine::ql::QueryResult::Records(rr) => {
assert!(!builder_results.is_empty());
assert!(rr.records_returned > 0);
}
other => panic!("expected Records, got {other:?}"),
}
}
#[tokio::test(flavor = "multi_thread")]
async fn chain_all_options() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("brain");
write_dev_hirn_toml(dir.path(), "");
let memory = HirnMemory::open(&path).await.unwrap();
memory
.remember("System uses Redis for caching")
.await
.unwrap();
let results = memory
.recall_builder("caching")
.limit(5)
.episodic_only()
.activation(hirn_engine::ActivationMode::Spreading)
.depth(2)
.execute()
.await
.unwrap();
let _ = results;
}
}
mod auto_config {
use super::*;
#[tokio::test(flavor = "multi_thread")]
async fn open_no_config_defaults_applied() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("brain");
write_dev_hirn_toml(dir.path(), "");
let memory = HirnMemory::open(&path).await.unwrap();
assert!(
memory.db().admission_pipeline().is_some(),
"default admission pipeline should be active"
);
let cfg = memory.db().config();
assert!(
cfg.admission_enabled,
"admission should be enabled by default"
);
assert!((cfg.admission_surprise_threshold - 0.3).abs() < f32::EPSILON);
assert!((cfg.admission_duplicate_threshold - 0.95).abs() < f32::EPSILON);
assert_eq!(cfg.admission_token_budget_limit, 500_000);
assert_eq!(cfg.consolidation_interval_secs, 3600);
memory.remember("test memory").await.unwrap();
let ctx = memory.think("test", 2048).await.unwrap();
assert!(!ctx.context.is_empty());
}
#[tokio::test(flavor = "multi_thread")]
async fn open_hirn_toml_overrides_defaults() {
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("brain");
let toml_content = r#"
token_budget = 8192
consolidation_interval_secs = 1800
admission_surprise_threshold = 0.5
"#;
write_dev_hirn_toml(dir.path(), toml_content);
let memory = HirnMemory::open(&db_path).await.unwrap();
let cfg = memory.db().config();
assert_eq!(cfg.token_budget, 8192);
assert_eq!(cfg.consolidation_interval_secs, 1800);
assert!((cfg.admission_surprise_threshold - 0.5).abs() < f32::EPSILON);
assert!((cfg.admission_duplicate_threshold - 0.95).abs() < f32::EPSILON);
}
#[tokio::test(flavor = "multi_thread")]
async fn open_with_hirnconfig_programmatic() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("brain");
let config = HirnConfig::builder()
.db_path(&path)
.token_budget(16384)
.consolidation_interval_secs(7200)
.allow_pseudo_embedder_fallback(true)
.build()
.unwrap();
let memory = HirnMemory::open_with_config(config).await.unwrap();
let cfg = memory.db().config();
assert_eq!(cfg.token_budget, 16384);
assert_eq!(cfg.consolidation_interval_secs, 7200);
assert!(!cfg.admission_enabled);
}
#[tokio::test(flavor = "multi_thread")]
async fn config_precedence() {
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("brain");
let toml_content = r#"
token_budget = 8192
consolidation_interval_secs = 1800
"#;
write_dev_hirn_toml(dir.path(), toml_content);
let memory = HirnMemory::open(&db_path).await.unwrap();
let cfg = memory.db().config();
assert_eq!(cfg.token_budget, 8192, "hirn.toml > defaults");
assert_eq!(
cfg.consolidation_interval_secs, 1800,
"hirn.toml > defaults"
);
let dir2 = tempfile::tempdir().unwrap();
let db_path2 = dir2.path().join("brain");
write_dev_hirn_toml(dir2.path(), toml_content);
let explicit = HirnConfig::builder()
.db_path(&db_path2)
.token_budget(32768)
.allow_pseudo_embedder_fallback(true)
.build()
.unwrap();
let memory2 = HirnMemory::open_with_config(explicit).await.unwrap();
let cfg2 = memory2.db().config();
assert_eq!(cfg2.token_budget, 32768, "HirnConfig > hirn.toml");
assert_eq!(
cfg2.consolidation_interval_secs, 3600,
"HirnConfig defaults, not hirn.toml"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn invalid_config_error_at_open() {
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("brain");
let toml_content = "hnsw_m = 0\n";
std::fs::write(dir.path().join("hirn.toml"), toml_content).unwrap();
let result = HirnMemory::open(&db_path).await;
assert!(
result.is_err(),
"invalid hirn.toml should cause open to fail"
);
let err = match result {
Err(e) => e.to_string(),
Ok(_) => panic!("expected error for invalid hirn.toml"),
};
assert!(
err.contains("hnsw_m"),
"error should mention the invalid field: {err}"
);
}
}
}