Documentation
¶
Overview ¶
Integration between GPU k-means clustering and semantic inference.
This file bridges:
- pkg/gpu (ClusterIndex with k-means clustering)
- pkg/inference (semantic inference engine)
Provides cluster-accelerated similarity search for large embedding indices.
Feature Flags:
- NORNICDB_GPU_CLUSTERING_ENABLED: Enable GPU clustering (default: false)
- NORNICDB_GPU_CLUSTERING_AUTO_INTEGRATION_ENABLED: Auto-integrate with inference engine
See pkg/config/feature_flags.go for details.
Package inference provides edge materialization with cooldown protection.
Cooldown logic prevents echo chambers from rapid co-access bursts by enforcing minimum time between materializations of the same edge pair.
Feature flag: NORNICDB_COOLDOWN_ENABLED=true (enabled by default)
Usage Example 1: Basic cooldown check
table := NewCooldownTable()
if table.CanMaterialize("nodeA", "nodeB", "relates_to") {
db.CreateEdge("nodeA", "nodeB", "relates_to")
table.RecordMaterialization("nodeA", "nodeB", "relates_to")
}
Usage Example 2: With custom cooldown durations
customCooldowns := map[string]time.Duration{
"important_link": 30 * time.Minute, // Longer cooldown for important edges
"casual_link": 1 * time.Minute, // Shorter cooldown for casual edges
}
table := NewCooldownTableWithConfig(customCooldowns)
Usage Example 3: Check with reason (for debugging)
canMat, reason := table.CanMaterializeWithReason("nodeA", "nodeB", "relates_to")
if !canMat {
log.Printf("Cannot materialize: %s", reason) // "cooldown active, 3m2s remaining"
}
ELI12 (Explain Like I'm 12):
Imagine you're playing tag at recess. After you tag someone:
- Cooldown prevents you from immediately tagging them again (no "tag-backs")
- You must wait 30 seconds before you can tag that same person
- This prevents annoying rapid-fire tagging (echo chamber)
In NornicDB:
- You suggest edge A→B based on co-access
- Edge gets created
- 5 seconds later, they're accessed together again → same suggestion!
- Cooldown says "no, wait 5 minutes before suggesting A→B again"
- This prevents creating duplicate edges or flip-flopping
Without cooldown, rapid co-access could create hundreds of duplicate suggestions!
Package inference - Edge decay for auto-generated relationships.
Auto-generated edges (SIMILAR_TO, etc.) decay over time if not reinforced. This prevents the graph from accumulating stale relationships.
Decay Model:
confidence_new = confidence_old * decay_rate ^ (days_since_access)
Example:
- Edge created with 0.85 confidence
- Decay rate: 0.95 per day
- After 7 days without access: 0.85 * 0.95^7 = 0.60
- After 30 days: 0.85 * 0.95^30 = 0.18 (below threshold, removed)
Usage:
decay := NewEdgeDecay(config, storage) decay.Start(ctx) // Background worker defer decay.Stop() // Reinforce edge when accessed decay.ReinforceEdge(edgeID)
Package inference provides edge materialization with evidence buffering.
Evidence buffering accumulates signals before materializing edges, reducing false positives by requiring multiple corroborating evidence points.
Feature flag: NORNICDB_EVIDENCE_BUFFERING_ENABLED (enabled by default)
Usage Example 1: Basic evidence accumulation
buffer := NewEvidenceBuffer()
// First co-access (not enough evidence yet)
shouldMat := buffer.AddEvidence("nodeA", "nodeB", "relates_to", 0.8, "coaccess", "session-1")
// → false (need more evidence)
// Second co-access (still accumulating)
shouldMat = buffer.AddEvidence("nodeA", "nodeB", "relates_to", 0.7, "coaccess", "session-2")
// → false (need one more signal)
// Third co-access (threshold met!)
shouldMat = buffer.AddEvidence("nodeA", "nodeB", "relates_to", 0.9, "similarity", "session-3")
// → true (3 signals from 3 sessions, avg score 0.8)
if shouldMat {
db.CreateEdge("nodeA", "nodeB", "relates_to")
}
Usage Example 2: Custom thresholds per edge type
customThresholds := map[string]EvidenceThreshold{
"high_confidence_link": {MinCount: 5, MinScore: 0.9, MinSessions: 3},
"low_confidence_link": {MinCount: 2, MinScore: 0.5, MinSessions: 1},
}
buffer := NewEvidenceBufferWithConfig(customThresholds)
Usage Example 3: Checking current evidence state
canMat, reason := buffer.CheckThreshold("nodeA", "nodeB", "relates_to")
if !canMat {
log.Printf("Not ready: %s", reason) // "need 1 more signal (2/3)"
}
ELI12 (Explain Like I'm 12):
Imagine you're deciding if two kids should be study partners:
- They sit together in class (1 signal)
- You notice them talking about homework (2 signals)
- They're both in the science club (3 signals)
- NOW you're confident they'd be good partners!
Evidence buffering prevents jumping to conclusions based on one coincidence:
- Single co-access? Could be random
- Two co-accesses in same session? Could be a fluke
- Three co-accesses across different sessions? Strong pattern!
Like a detective collecting clues:
- 1 fingerprint = suspicious
- 2 fingerprints + 1 witness = very suspicious
- 3 fingerprints + 2 witnesses + video = confident!
Without evidence buffering, you'd create edges from random noise. With it, you only create edges when you have solid proof of a relationship.
Package inference - Heimdall SLM Hybrid Review for Auto-TLP.
This module provides LLM-based batch review for automatically inferred edges. It operates in hybrid mode:
- TLP algorithms generate fast candidates
- Heimdall reviews batch + can optionally suggest additional edges
Designed for small instruction-tuned models:
- Simple, structured prompts
- Size limits to avoid context overflow
- Batch processing for efficiency
- Graceful degradation when nodes are too large
KV Cache Optimization:
The system prompt is static and cached by the SLM's KV cache. Only the dynamic content (nodes, suggestions) varies per call. Use GetSystemPrompt() to configure your SLM's system message.
Feature Flags:
- NORNICDB_AUTO_TLP_LLM_QC_ENABLED: Enable batch review of TLP suggestions
- NORNICDB_AUTO_TLP_LLM_AUGMENT_ENABLED: Allow Heimdall to add new suggestions
Usage:
qc := inference.NewHeimdallQC(heimdallFunc, nil) engine.SetHeimdallQC(qc)
Package inference provides automatic relationship detection for NornicDB.
This package implements multiple methods for detecting implicit relationships between nodes in the graph:
- Similarity-based: Nodes with similar embeddings are likely related
- Co-access patterns: Nodes accessed together frequently are likely related
- Temporal proximity: Nodes accessed in the same session are likely related
- Transitive inference: If A→B and B→C, then A→C (with confidence)
Example Usage:
// Create inference engine
config := inference.DefaultConfig()
config.SimilarityThreshold = 0.85 // Higher threshold = more confidence
engine := inference.New(config)
// Hook up vector search
engine.SetSimilaritySearch(func(ctx context.Context, embedding []float32, k int) ([]inference.SimilarityResult, error) {
return vectorIndex.Search(ctx, embedding, k)
})
// When storing a new memory
node := createMemoryNode("Remember to buy milk")
suggestions, _ := engine.OnStore(ctx, node.ID, node.Embedding)
fmt.Printf("Found %d suggested relationships:\n", len(suggestions))
for _, sug := range suggestions {
fmt.Printf(" %s -> %s (%.2f confidence): %s\n",
sug.SourceID, sug.TargetID, sug.Confidence, sug.Reason)
if sug.Confidence > 0.7 {
// High confidence - auto-create the edge
createEdge(sug.SourceID, sug.TargetID, sug.Type)
}
}
// When accessing a memory
suggestions = engine.OnAccess(ctx, "memory-123")
for _, sug := range suggestions {
if sug.Method == "co_access" {
fmt.Printf("Frequently accessed with: %s\n", sug.TargetID)
}
}
How Each Method Works:
Similarity-Based Linking: Uses vector embeddings to find semantically similar nodes. Example: "Buy milk" and "Purchase dairy products" have similar embeddings.
Co-Access Patterns: Tracks which nodes are accessed within a short time window. Example: If you always access "Project Plan" and "Budget" together, they're probably related.
Temporal Proximity: Nodes accessed in the same session (within 30 minutes) are linked. Example: All memories from a single conversation thread.
Transitive Inference: If A relates to B and B relates to C, then A might relate to C. Example: "Python" → "Programming" → "Computers" suggests "Python" → "Computers"
ELI12 (Explain Like I'm 12):
Imagine you're organizing your school notebooks:
**Similarity**: Your math and science notebooks go together because they're both about numbers and formulas (similar content).
**Co-access**: Your English notebook and dictionary always get used together, so they should be near each other on your shelf.
**Temporal**: All homework from Monday night was done at the same time, so those papers are related.
**Transitive**: If Math relates to Science, and Science relates to Biology, then Math probably relates to Biology too (they're all STEM subjects).
The inference engine is like a smart librarian who notices these patterns and suggests: "Hey, these two things seem related - want me to connect them?"
Package inference - Kalman filter integration for adaptive relationship detection.
This file provides the KalmanAdapter that enhances the inference engine with:
- Session-aware co-access: Uses temporal.SessionDetector for accurate sessions
- Confidence smoothing: Smooth noisy relationship confidence scores
- Trend detection: Detect strengthening/weakening relationships
- Predictive linking: Predict likely future relationships
Integration Architecture ¶
┌─────────────────────────────────────────────────────────────┐ │ Kalman Inference Adapter │ ├─────────────────────────────────────────────────────────────┤ │ ┌─────────────────┐ ┌─────────────────────────────────┐ │ │ │ Inference Engine│───▶│ Kalman Filter (confidence) │ │ │ │ (raw confidence)│ └─────────────────────────────────┘ │ │ └────────┬────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────┐│ │ │ Session Detector (from temporal package) ││ │ │ • Real session boundaries via velocity changes ││ │ │ • Session-scoped co-access (not time-window) ││ │ │ • Cross-session linking for repeated patterns ││ │ └─────────────────────────────────────────────────────────┘│ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────┐│ │ │ Enhanced Edge Suggestions ││ │ │ • Smoothed confidence scores ││ │ │ • Relationship strength trends ││ │ │ • Predicted future relationships ││ │ └─────────────────────────────────────────────────────────┘│ └─────────────────────────────────────────────────────────────┘
ELI12 (Explain Like I'm 12) ¶
Imagine you're trying to figure out which of your friends hang out together:
**Without Kalman:**
- "Sarah and Mike were at the same place at 3pm" → friends!
- But wait, that was just the cafeteria. Everyone was there.
**With Kalman + Sessions:**
- "Sarah and Mike have been together for the WHOLE AFTERNOON" → probably friends
- "They keep ending up together across MULTIPLE days" → definitely friends!
The Kalman filter also smooths out mistakes:
- Day 1: "70% sure they're friends"
- Day 2: "30% sure" (oops, they argued)
- Day 3: "60% sure"
- Kalman says: "Smoothed: 55% - probably friends but something's up"
This helps find REAL relationships, not just coincidences!
Integration between topological link prediction and semantic inference.
This file bridges the gap between:
- pkg/linkpredict (topological algorithms)
- pkg/inference (semantic/behavioral inference)
Provides unified edge suggestion API that combines both approaches.
OPTIMIZATIONS (v2):
- Uses streaming graph construction (fixes memory spikes)
- Parallel edge fetching (4-8x speedup)
- Disk-based graph caching (avoids rebuilds)
- Incremental updates (delta changes)
- Context cancellation support
- Progress callbacks
Index ¶
- Constants
- Variables
- func EstimatePromptSize(sourceNode NodeSummary, candidates []CandidateSummary) int
- func GetSystemPrompt(augment bool) string
- func ResetGlobalCooldownTable()
- func ResetGlobalEvidenceBuffer()
- type AugmentedEdge
- type CandidateSummary
- type ClusterConfig
- type ClusterIntegration
- func (ci *ClusterIntegration) AddEmbedding(nodeID string, embedding []float32) error
- func (ci *ClusterIntegration) GetClusterIndex() *gpu.ClusterIndex
- func (ci *ClusterIntegration) GetConfig() *ClusterConfig
- func (ci *ClusterIntegration) IsClustered() bool
- func (ci *ClusterIntegration) IsEnabled() bool
- func (ci *ClusterIntegration) OnIndexComplete() error
- func (ci *ClusterIntegration) OnNodeUpdate(nodeID string, embedding []float32) error
- func (ci *ClusterIntegration) Recluster() error
- func (ci *ClusterIntegration) Search(ctx context.Context, query []float32, topK int) ([]gpu.SearchResult, error)
- func (ci *ClusterIntegration) SetConfig(config *ClusterConfig)
- func (ci *ClusterIntegration) ShouldRecluster() bool
- func (ci *ClusterIntegration) ShouldReclusterLocked() bool
- func (ci *ClusterIntegration) Stats() ClusterIntegrationStats
- type ClusterIntegrationStats
- type Config
- type CooldownEntry
- type CooldownStats
- type CooldownTable
- func (ct *CooldownTable) CanMaterialize(src, dst, label string) bool
- func (ct *CooldownTable) CanMaterializeWithReason(src, dst, label string) (bool, string)
- func (ct *CooldownTable) Cleanup() int
- func (ct *CooldownTable) Clear()
- func (ct *CooldownTable) GetEntry(src, dst, label string) *CooldownEntry
- func (ct *CooldownTable) GetLabelCooldown(label string) time.Duration
- func (ct *CooldownTable) RecordMaterialization(src, dst, label string)
- func (ct *CooldownTable) RecordMaterializationAt(src, dst, label string, t time.Time)
- func (ct *CooldownTable) SetLabelCooldown(label string, duration time.Duration)
- func (ct *CooldownTable) Size() int
- func (ct *CooldownTable) Stats() CooldownStats
- func (ct *CooldownTable) TimeUntilAllowed(src, dst, label string) time.Duration
- type CooldownTableOption
- type EdgeDecay
- type EdgeDecayConfig
- type EdgeDecayStats
- type EdgeSuggestion
- type Engine
- func (e *Engine) CleanupTier1() (cooldownRemoved, evidenceRemoved int)
- func (e *Engine) CleanupTier1WithProvenance(provenanceMaxAge time.Duration) (cooldownRemoved, evidenceRemoved, provenanceRemoved int)
- func (e *Engine) GetClusterIntegration() *ClusterIntegration
- func (e *Engine) GetCooldownTable() *CooldownTable
- func (e *Engine) GetEdgeMetaStore() *storage.EdgeMetaStore
- func (e *Engine) GetEvidenceBuffer() *EvidenceBuffer
- func (e *Engine) GetHeimdallQC() *HeimdallQC
- func (e *Engine) GetKalmanAdapter() *KalmanAdapter
- func (e *Engine) GetNodeConfigStore() *storage.NodeConfigStore
- func (e *Engine) GetStats() Stats
- func (e *Engine) GetTopologyIntegration() *TopologyIntegration
- func (e *Engine) OnAccess(ctx context.Context, nodeID string) []EdgeSuggestion
- func (e *Engine) OnStore(ctx context.Context, nodeID string, embedding []float32) ([]EdgeSuggestion, error)
- func (e *Engine) OnStoreBestOfChunks(ctx context.Context, nodeID string, embeddings [][]float32) ([]EdgeSuggestion, error)
- func (e *Engine) ProcessSuggestion(suggestion EdgeSuggestion, sessionID string) ProcessSuggestionResult
- func (e *Engine) RecordMaterialization(sourceID, targetID, edgeType string)
- func (e *Engine) SetClusterIntegration(integration *ClusterIntegration)
- func (e *Engine) SetCooldownTable(table *CooldownTable)
- func (e *Engine) SetEdgeMetaStore(store *storage.EdgeMetaStore)
- func (e *Engine) SetEvidenceBuffer(buffer *EvidenceBuffer)
- func (e *Engine) SetHeimdallQC(qc *HeimdallQC)
- func (e *Engine) SetKalmanAdapter(adapter *KalmanAdapter)
- func (e *Engine) SetNodeConfigStore(store *storage.NodeConfigStore)
- func (e *Engine) SetSimilaritySearch(...)
- func (e *Engine) SetTopologyIntegration(integration *TopologyIntegration)
- func (e *Engine) SuggestTransitive(ctx context.Context, edges []ExistingEdge) []EdgeSuggestion
- type Evidence
- type EvidenceBuffer
- func (eb *EvidenceBuffer) AddEvidence(src, dst, label string, score float64, signalType, sessionID string) bool
- func (eb *EvidenceBuffer) AddEvidenceWithMetadata(src, dst, label string, score float64, signalType, sessionID string, ...) bool
- func (eb *EvidenceBuffer) CheckThreshold(src, dst, label string) (bool, string)
- func (eb *EvidenceBuffer) Cleanup() int
- func (eb *EvidenceBuffer) Clear()
- func (eb *EvidenceBuffer) ClearEntry(src, dst, label string)
- func (eb *EvidenceBuffer) GetEvidence(src, dst, label string) *Evidence
- func (eb *EvidenceBuffer) GetPendingEdges(minProgress float64) []Evidence
- func (eb *EvidenceBuffer) GetThreshold(label string) EvidenceThreshold
- func (eb *EvidenceBuffer) SetThreshold(label string, threshold EvidenceThreshold)
- func (eb *EvidenceBuffer) Size() int
- func (eb *EvidenceBuffer) Stats() EvidenceStats
- type EvidenceBufferOption
- type EvidenceKey
- type EvidenceStats
- type EvidenceThreshold
- type ExistingEdge
- type HeimdallBatchRequest
- type HeimdallBatchResponse
- type HeimdallFunc
- type HeimdallQC
- type HeimdallQCConfig
- type HeimdallQCStats
- type InferenceAdapterStats
- type KalmanAdapter
- func (ka *KalmanAdapter) GetEngine() *Engine
- func (ka *KalmanAdapter) GetRelationshipStrength(source, target string) *smoothedConfidence
- func (ka *KalmanAdapter) GetStats() InferenceAdapterStats
- func (ka *KalmanAdapter) GetStrengtheningRelationships(minVelocity float64) []EdgeSuggestion
- func (ka *KalmanAdapter) GetWeakeningRelationships(maxVelocity float64) []EdgeSuggestion
- func (ka *KalmanAdapter) OnAccess(ctx context.Context, nodeID string) ([]EdgeSuggestion, error)
- func (ka *KalmanAdapter) OnStore(ctx context.Context, nodeID string, embedding []float32) ([]EdgeSuggestion, error)
- func (ka *KalmanAdapter) PredictFutureRelationships(threshold float64) []EdgeSuggestion
- func (ka *KalmanAdapter) Reset()
- func (ka *KalmanAdapter) SetSessionDetector(s *temporal.SessionDetector)
- func (ka *KalmanAdapter) SetTracker(t *temporal.Tracker)
- type KalmanAdapterConfig
- type NodeSummary
- type ProcessSuggestionResult
- type SimilarityResult
- type Stats
- type TopologyConfig
- type TopologyIntegration
- func (t *TopologyIntegration) CombinedSuggestions(semantic, topological []EdgeSuggestion) []EdgeSuggestion
- func (t *TopologyIntegration) InvalidateCache()
- func (t *TopologyIntegration) OnEdgeAdded(from, to storage.NodeID)
- func (t *TopologyIntegration) OnEdgeRemoved(from, to storage.NodeID)
- func (t *TopologyIntegration) OnNodeAdded(nodeID storage.NodeID)
- func (t *TopologyIntegration) OnNodeRemoved(nodeID storage.NodeID)
- func (t *TopologyIntegration) Stats() TopologyStats
- func (t *TopologyIntegration) SuggestTopological(ctx context.Context, sourceID string) ([]EdgeSuggestion, error)
- type TopologyStats
Constants ¶
const DefaultCooldown = 5 * time.Minute
DefaultCooldown is used when no label-specific cooldown is configured.
const HeimdallAugmentSystemPrompt = `` /* 257-byte string literal not displayed */
HeimdallAugmentSystemPrompt includes augmentation capability.
const HeimdallSystemPrompt = `` /* 174-byte string literal not displayed */
HeimdallSystemPrompt is the static system prompt for Heimdall QC. This is cached in KV alongside Bifrost's command definitions. Ultra-concise for one-shot completion - no multi-turn conversation.
Variables ¶
var DefaultCooldowns = map[string]time.Duration{ "relates_to": 5 * time.Minute, "similar_to": 10 * time.Minute, "coaccess": 1 * time.Minute, "topology": 15 * time.Minute, "depends_on": 30 * time.Minute, "references": 5 * time.Minute, "semantic_link": 10 * time.Minute, }
DefaultCooldowns defines standard cooldown durations per edge label. These can be overridden per-table or globally.
var DefaultEvidenceThreshold = EvidenceThreshold{ MinCount: 3, MinScore: 0.5, MinSessions: 2, MaxAge: 24 * time.Hour, }
DefaultEvidenceThreshold is used when no label-specific threshold is configured.
var DefaultThresholds = map[string]EvidenceThreshold{ "relates_to": { MinCount: 3, MinScore: 0.5, MinSessions: 2, MaxAge: 24 * time.Hour, }, "similar_to": { MinCount: 2, MinScore: 0.7, MinSessions: 1, MaxAge: 48 * time.Hour, }, "coaccess": { MinCount: 5, MinScore: 0.3, MinSessions: 3, MaxAge: 12 * time.Hour, }, "topology": { MinCount: 2, MinScore: 0.6, MinSessions: 1, MaxAge: 72 * time.Hour, }, "depends_on": { MinCount: 3, MinScore: 0.6, MinSessions: 2, MaxAge: 168 * time.Hour, }, }
DefaultThresholds defines standard evidence thresholds per edge label.
Functions ¶
func EstimatePromptSize ¶
func EstimatePromptSize(sourceNode NodeSummary, candidates []CandidateSummary) int
EstimatePromptSize estimates the byte size of a batch prompt.
func GetSystemPrompt ¶
GetSystemPrompt returns the appropriate static system prompt. Configure your SLM to cache this in KV cache - it never changes. Use augment=true when NORNICDB_AUTO_TLP_LLM_AUGMENT_ENABLED is set.
func ResetGlobalCooldownTable ¶
func ResetGlobalCooldownTable()
ResetGlobalCooldownTable resets the global cooldown table. Primarily for testing.
func ResetGlobalEvidenceBuffer ¶
func ResetGlobalEvidenceBuffer()
ResetGlobalEvidenceBuffer resets the global evidence buffer. Primarily for testing.
Types ¶
type AugmentedEdge ¶
type AugmentedEdge struct {
TargetID string `json:"target_id"`
Type string `json:"type"`
Confidence float64 `json:"conf"`
Reason string `json:"reason"`
}
AugmentedEdge is a new edge suggested by Heimdall.
type CandidateSummary ¶
type CandidateSummary struct {
TargetID string `json:"target_id"`
Labels []string `json:"labels"`
Props map[string]string `json:"props"`
Type string `json:"type"`
Confidence float64 `json:"conf"`
Method string `json:"method"`
}
CandidateSummary represents a TLP suggestion for review.
type ClusterConfig ¶
type ClusterConfig struct {
// Enable cluster-accelerated search
Enabled bool
// Number of clusters to search during similarity lookup
// Higher = better recall, slower; Lower = faster, may miss results
// Default: 3
NumClustersSearch int
// Automatically recluster when drift threshold is exceeded
AutoRecluster bool
// ReclusterThreshold: trigger re-clustering when this fraction
// of embeddings have been updated since last cluster (0.0-1.0)
// Default: 0.1 (10%)
ReclusterThreshold float64
// MinEmbeddingsForClustering: minimum embeddings before clustering is used
// Below this threshold, brute-force search is used
// Default: 1000
MinEmbeddingsForClustering int
}
ClusterConfig controls k-means clustering integration with inference.
This allows the inference engine to use cluster-accelerated search for faster semantic similarity lookup on large embedding sets.
Example:
config := &inference.ClusterConfig{
Enabled: true,
NumClustersSearch: 3, // Search 3 nearest clusters
AutoRecluster: true,
ReclusterThreshold: 0.1, // 10% drift triggers recluster
}
func DefaultClusterConfig ¶
func DefaultClusterConfig() *ClusterConfig
DefaultClusterConfig returns sensible defaults for cluster integration.
The Enabled field is set based on the NORNICDB_GPU_CLUSTERING_ENABLED environment variable (default: false).
type ClusterIntegration ¶
type ClusterIntegration struct {
// contains filtered or unexported fields
}
ClusterIntegration adds GPU k-means clustering to the inference engine.
This is an optional extension that can be enabled to accelerate semantic similarity search on large embedding indices. When enabled:
- Embeddings are organized into clusters using k-means
- Search queries first find nearest clusters, then search within them
- Provides 10-50x speedup on indices with 10K+ embeddings
Thread Safety: All methods are thread-safe.
Architecture:
┌────────────────────────────────────────────────────────┐ │ ClusterIntegration │ ├────────────────────────────────────────────────────────┤ │ config *ClusterConfig <- search/recluster config │ │ clusterIndex *gpu.ClusterIndex <- GPU-accelerated │ │ mu sync.RWMutex <- thread safety │ ├────────────────────────────────────────────────────────┤ │ Methods: │ │ OnIndexComplete() <- trigger clustering │ │ Search() <- cluster-accelerated search │ │ OnNodeUpdate() <- real-time embedding updates │ │ Stats() <- clustering statistics │ └────────────────────────────────────────────────────────┘
Example:
// Create integration with GPU manager
gpuManager, _ := gpu.NewManager(&gpu.Config{Enabled: true})
clusterConfig := inference.DefaultClusterConfig()
clusterConfig.Enabled = true
ci := inference.NewClusterIntegration(gpuManager, clusterConfig, nil)
engine.SetClusterIntegration(ci)
// After indexing complete, trigger clustering
ci.OnIndexComplete()
// Searches now use cluster acceleration
results, _ := engine.SimilaritySearch(ctx, embedding, 10)
func NewClusterIntegration ¶
func NewClusterIntegration(manager *gpu.Manager, config *ClusterConfig, kmeansConfig *gpu.KMeansConfig, embConfig *gpu.EmbeddingIndexConfig) *ClusterIntegration
NewClusterIntegration creates a new cluster integration.
Parameters:
- manager: GPU manager for acceleration (can be nil for CPU-only)
- config: Cluster configuration (nil uses defaults)
- kmeansConfig: K-means configuration (nil uses defaults)
- embConfig: Embedding index config (nil uses defaults with 1024 dims)
Returns ready-to-use integration that can be attached to inference engine.
Example:
// Basic setup
ci := inference.NewClusterIntegration(nil, nil, nil, nil)
// With GPU acceleration
gpuMgr, _ := gpu.NewManager(&gpu.Config{Enabled: true})
ci = inference.NewClusterIntegration(gpuMgr, nil, nil, nil)
// With custom config
config := &inference.ClusterConfig{
Enabled: true,
NumClustersSearch: 5,
}
kmeansConfig := &gpu.KMeansConfig{
NumClusters: 100,
MaxIterations: 50,
}
embConfig := gpu.DefaultEmbeddingIndexConfig(768)
ci = inference.NewClusterIntegration(gpuMgr, config, kmeansConfig, embConfig)
func (*ClusterIntegration) AddEmbedding ¶
func (ci *ClusterIntegration) AddEmbedding(nodeID string, embedding []float32) error
AddEmbedding adds an embedding to the cluster index.
Call this during index building phase, before OnIndexComplete(). After clustering, use OnNodeUpdate() for incremental updates.
Parameters:
- nodeID: Unique identifier for the node
- embedding: Vector embedding
Returns error if dimensions mismatch.
Example:
for _, node := range nodes {
err := ci.AddEmbedding(node.ID, node.Embedding)
if err != nil {
log.Printf("Failed to add %s: %v", node.ID, err)
}
}
func (*ClusterIntegration) GetClusterIndex ¶
func (ci *ClusterIntegration) GetClusterIndex() *gpu.ClusterIndex
GetClusterIndex returns the underlying ClusterIndex for advanced usage.
Use with caution - direct manipulation may cause inconsistencies.
func (*ClusterIntegration) GetConfig ¶
func (ci *ClusterIntegration) GetConfig() *ClusterConfig
GetConfig returns the current configuration.
func (*ClusterIntegration) IsClustered ¶
func (ci *ClusterIntegration) IsClustered() bool
IsClustered returns whether clustering has been performed.
func (*ClusterIntegration) IsEnabled ¶
func (ci *ClusterIntegration) IsEnabled() bool
IsEnabled returns whether clustering is enabled.
func (*ClusterIntegration) OnIndexComplete ¶
func (ci *ClusterIntegration) OnIndexComplete() error
OnIndexComplete triggers k-means clustering after initial indexing.
Call this method after all embeddings have been added via AddEmbedding(). Clustering organizes embeddings into groups for fast approximate search.
This is typically called:
- After initial bulk loading
- After periodic re-indexing
- When ShouldRecluster() returns true
Returns error if clustering fails.
Example:
// After loading all embeddings
for _, emb := range embeddings {
ci.AddEmbedding(emb.ID, emb.Vector)
}
// Trigger clustering
if err := ci.OnIndexComplete(); err != nil {
log.Printf("Clustering failed: %v", err)
}
stats := ci.Stats()
fmt.Printf("Created %d clusters in %v\n",
stats.NumClusters, stats.ClusteringTime)
func (*ClusterIntegration) OnNodeUpdate ¶
func (ci *ClusterIntegration) OnNodeUpdate(nodeID string, embedding []float32) error
OnNodeUpdate handles real-time embedding updates.
Call this when a node's embedding changes after initial indexing. The embedding is reassigned to its nearest cluster without full re-clustering.
For batch updates, consider calling Recluster() after the batch completes.
Parameters:
- nodeID: Node identifier
- embedding: New embedding vector
Returns error if update fails.
Example:
// Node embedding changed
if err := ci.OnNodeUpdate("node-123", newEmbedding); err != nil {
log.Printf("Update failed: %v", err)
}
// Check if reclustering is recommended
if ci.ShouldRecluster() {
ci.Recluster()
}
func (*ClusterIntegration) Recluster ¶
func (ci *ClusterIntegration) Recluster() error
Recluster performs a full re-clustering.
This recomputes all clusters from scratch. Use when:
- ShouldRecluster() returns true
- After significant batch updates
- Cluster quality has degraded
Returns error if clustering fails.
Example:
if ci.ShouldRecluster() {
if err := ci.Recluster(); err != nil {
log.Printf("Recluster failed: %v", err)
}
}
func (*ClusterIntegration) Search ¶
func (ci *ClusterIntegration) Search(ctx context.Context, query []float32, topK int) ([]gpu.SearchResult, error)
Search performs cluster-accelerated similarity search.
If clustering is enabled and has been performed:
- Finds the k nearest clusters to the query
- Searches only within those clusters
- Returns top results from the candidate set
Falls back to brute-force search if:
- Clustering is disabled
- Not yet clustered
- Too few embeddings
Parameters:
- ctx: Context for cancellation
- query: Query embedding vector
- topK: Number of results to return
Returns: SearchResult slice sorted by similarity (descending)
Example:
results, err := ci.Search(ctx, queryEmbedding, 10)
for _, r := range results {
fmt.Printf("%s: %.3f\n", r.ID, r.Score)
}
func (*ClusterIntegration) SetConfig ¶
func (ci *ClusterIntegration) SetConfig(config *ClusterConfig)
SetConfig updates the cluster configuration.
Changes take effect immediately for future operations. Does not trigger reclustering - call Recluster() if needed.
func (*ClusterIntegration) ShouldRecluster ¶
func (ci *ClusterIntegration) ShouldRecluster() bool
ShouldRecluster checks if re-clustering is recommended.
Returns true if:
- Too many updates since last cluster (>threshold)
- Centroid drift exceeds threshold
- Too much time has passed
Use this to decide when to call Recluster() for batch operations.
func (*ClusterIntegration) ShouldReclusterLocked ¶
func (ci *ClusterIntegration) ShouldReclusterLocked() bool
ShouldReclusterLocked checks without acquiring lock. Caller must hold ci.mu.
func (*ClusterIntegration) Stats ¶
func (ci *ClusterIntegration) Stats() ClusterIntegrationStats
Stats returns clustering statistics.
Example:
stats := ci.Stats()
fmt.Printf("Clusters: %d, Avg Size: %.1f\n",
stats.NumClusters, stats.AvgClusterSize)
fmt.Printf("Search hit rate: %.1f%%\n",
float64(stats.SearchesClustered)/float64(stats.SearchesTotal)*100)
type ClusterIntegrationStats ¶
type ClusterIntegrationStats struct {
gpu.ClusterStats
// Inference-specific stats
SearchesTotal int64
SearchesClustered int64
Enabled bool
}
ClusterIntegrationStats holds statistics about cluster integration.
type Config ¶
type Config struct {
// Similarity-based linking
SimilarityThreshold float64 // Default: 0.82
SimilarityTopK int // How many similar nodes to check
// Co-access pattern detection
CoAccessEnabled bool
CoAccessWindow time.Duration // Time window for co-access
CoAccessMinCount int // Minimum co-accesses to suggest edge
// Temporal proximity
TemporalEnabled bool
TemporalWindow time.Duration // Window for "same session"
// Transitive inference
TransitiveEnabled bool
TransitiveMinConf float64 // Minimum confidence for transitive edges
}
Config holds inference engine configuration options.
All thresholds and parameters can be tuned based on your use case:
- Higher thresholds = fewer but more confident suggestions
- Lower thresholds = more suggestions but potentially noisier
Example:
// Conservative: Only suggest very confident relationships
config := &inference.Config{
SimilarityThreshold: 0.90, // Very high bar
SimilarityTopK: 5, // Only check top 5
CoAccessMinCount: 5, // Need 5 co-accesses
TransitiveMinConf: 0.7, // High confidence for transitive
}
// Aggressive: Suggest many potential relationships
config = &inference.Config{
SimilarityThreshold: 0.75, // Lower bar
SimilarityTopK: 20, // Check top 20
CoAccessMinCount: 2, // Just 2 co-accesses
TransitiveMinConf: 0.3, // Lower confidence OK
}
func DefaultConfig ¶
func DefaultConfig() *Config
DefaultConfig returns balanced default configuration suitable for most use cases.
Defaults:
- SimilarityThreshold: 0.82 (fairly confident)
- SimilarityTopK: 10 (check 10 most similar)
- CoAccessWindow: 30 seconds
- CoAccessMinCount: 3 (need 3 co-accesses before suggesting)
- TemporalWindow: 30 minutes (same "session")
- TransitiveMinConf: 0.5 (moderate confidence)
Example:
config := inference.DefaultConfig() engine := inference.New(config) // Or customize config = inference.DefaultConfig() config.SimilarityThreshold = 0.90 // Stricter engine = inference.New(config)
type CooldownEntry ¶
type CooldownEntry struct {
LastMaterialized time.Time
Count int64 // Total materializations for this pair
}
CooldownEntry tracks the last materialization time for an edge pair.
type CooldownStats ¶
type CooldownStats struct {
TotalEntries int64
TotalChecks int64
TotalBlocked int64
TotalAllowed int64
BlockRate float64 // Blocked / Checks (0.0 - 1.0)
}
CooldownStats provides observability into cooldown behavior.
type CooldownTable ¶
type CooldownTable struct {
// contains filtered or unexported fields
}
CooldownTable tracks when edges were last materialized to prevent spam. Thread-safe for concurrent access.
func GlobalCooldownTable ¶
func GlobalCooldownTable() *CooldownTable
GlobalCooldownTable returns the global cooldown table singleton. Lazily initialized on first call.
func NewCooldownTable ¶
func NewCooldownTable() *CooldownTable
NewCooldownTable creates a new cooldown table with default settings.
func NewCooldownTableWithConfig ¶
func NewCooldownTableWithConfig(labelCooldowns map[string]time.Duration) *CooldownTable
NewCooldownTableWithConfig creates a cooldown table with custom defaults.
func NewCooldownTableWithOptions ¶
func NewCooldownTableWithOptions(opts ...CooldownTableOption) *CooldownTable
NewCooldownTableWithOptions creates a table with functional options.
func (*CooldownTable) CanMaterialize ¶
func (ct *CooldownTable) CanMaterialize(src, dst, label string) bool
CanMaterialize checks if an edge can be materialized without violating cooldown. Returns true if cooldown period has passed or if cooldown feature is disabled.
func (*CooldownTable) CanMaterializeWithReason ¶
func (ct *CooldownTable) CanMaterializeWithReason(src, dst, label string) (bool, string)
CanMaterializeWithReason returns whether materialization is allowed and the reason. Useful for debugging and audit logs.
func (*CooldownTable) Cleanup ¶
func (ct *CooldownTable) Cleanup() int
Cleanup removes expired entries to prevent memory growth. Should be called periodically (e.g., every 10 minutes).
func (*CooldownTable) Clear ¶
func (ct *CooldownTable) Clear()
Clear removes all cooldown entries. Useful for testing or resetting state.
func (*CooldownTable) GetEntry ¶
func (ct *CooldownTable) GetEntry(src, dst, label string) *CooldownEntry
GetEntry returns the cooldown entry for an edge pair. Returns nil if no entry exists.
func (*CooldownTable) GetLabelCooldown ¶
func (ct *CooldownTable) GetLabelCooldown(label string) time.Duration
GetLabelCooldown returns the cooldown duration for a label.
func (*CooldownTable) RecordMaterialization ¶
func (ct *CooldownTable) RecordMaterialization(src, dst, label string)
RecordMaterialization records that an edge was materialized. Should be called after successfully creating an edge.
func (*CooldownTable) RecordMaterializationAt ¶
func (ct *CooldownTable) RecordMaterializationAt(src, dst, label string, t time.Time)
RecordMaterializationAt records a materialization at a specific time. Useful for replaying events or testing.
func (*CooldownTable) SetLabelCooldown ¶
func (ct *CooldownTable) SetLabelCooldown(label string, duration time.Duration)
SetLabelCooldown sets the cooldown duration for a specific label.
func (*CooldownTable) Size ¶
func (ct *CooldownTable) Size() int
Size returns the number of tracked edge pairs.
func (*CooldownTable) Stats ¶
func (ct *CooldownTable) Stats() CooldownStats
Stats returns current cooldown table statistics.
func (*CooldownTable) TimeUntilAllowed ¶
func (ct *CooldownTable) TimeUntilAllowed(src, dst, label string) time.Duration
TimeUntilAllowed returns how long until a materialization will be allowed. Returns 0 if already allowed.
type CooldownTableOption ¶
type CooldownTableOption func(*CooldownTable)
CooldownTableOption configures a CooldownTable.
func WithDefaultCooldown ¶
func WithDefaultCooldown(duration time.Duration) CooldownTableOption
WithDefaultCooldown sets the fallback cooldown for unknown labels.
func WithLabelCooldown ¶
func WithLabelCooldown(label string, duration time.Duration) CooldownTableOption
WithLabelCooldown sets a specific label cooldown during initialization.
type EdgeDecay ¶
type EdgeDecay struct {
// contains filtered or unexported fields
}
EdgeDecay manages automatic decay and removal of stale edges.
func NewEdgeDecay ¶
func NewEdgeDecay(config *EdgeDecayConfig, store storage.Engine) *EdgeDecay
NewEdgeDecay creates a new edge decay manager.
func (*EdgeDecay) GetStats ¶
func (ed *EdgeDecay) GetStats() EdgeDecayStats
GetStats returns current decay statistics.
func (*EdgeDecay) ReinforceEdge ¶
ReinforceEdge marks an edge as recently accessed, resetting its decay. Call this when an edge is traversed or otherwise used.
type EdgeDecayConfig ¶
type EdgeDecayConfig struct {
// Enabled controls whether decay runs
Enabled bool
// DecayRate is the daily decay multiplier (0.0-1.0)
// Lower = faster decay. Default: 0.95 (5% per day)
DecayRate float64
// MinConfidence is the threshold below which edges are deleted
// Default: 0.3 (30%)
MinConfidence float64
// GracePeriod is how long to wait before applying decay to new edges
// Default: 7 days
GracePeriod time.Duration
// ScanInterval is how often to scan for decayed edges
// Default: 1 hour
ScanInterval time.Duration
// MaxEdgesPerScan limits edges processed per cycle (0 = unlimited)
// Default: 1000
MaxEdgesPerScan int
// OnlyAutoGenerated if true, only decays auto-generated edges
// Default: true (don't decay user-created edges)
OnlyAutoGenerated bool
// DryRun if true, logs what would be deleted without actually deleting
// Default: false
DryRun bool
}
EdgeDecayConfig configures the edge decay system.
func DefaultEdgeDecayConfig ¶
func DefaultEdgeDecayConfig() *EdgeDecayConfig
DefaultEdgeDecayConfig returns sensible defaults.
type EdgeDecayStats ¶
type EdgeDecayStats struct {
ScansCompleted int64
EdgesScanned int64
EdgesDecayed int64
EdgesDeleted int64
LastScanTime time.Time
LastScanDuration time.Duration
// contains filtered or unexported fields
}
EdgeDecayStats tracks decay operations.
type EdgeSuggestion ¶
type EdgeSuggestion struct {
SourceID string
TargetID string
Type string
Confidence float64
Reason string
Method string // similarity, co_access, temporal, transitive
}
EdgeSuggestion represents a suggested edge.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine handles automatic relationship inference using multiple detection methods.
The Engine is thread-safe and can be used concurrently. It maintains internal state for co-access tracking and temporal pattern detection.
Lifecycle:
- Create with New()
- Configure similarity search with SetSimilaritySearch()
- Call OnStore() when creating nodes
- Call OnAccess() when accessing nodes
- Periodically call SuggestTransitive() to find indirect relationships
Example:
engine := inference.New(inference.DefaultConfig())
// Connect to vector index
engine.SetSimilaritySearch(vectorIndex.Search)
// Use in your storage layer
func StoreNode(node *storage.Node) error {
if err := db.CreateNode(node); err != nil {
return err
}
// Get relationship suggestions
suggestions, _ := engine.OnStore(ctx, mem.ID, mem.Embedding)
// Auto-create high-confidence edges
for _, sug := range suggestions {
if sug.Confidence >= 0.7 {
db.CreateEdge(sug.SourceID, sug.TargetID, sug.Type, sug.Confidence)
}
}
return nil
}
func New ¶
New creates a new inference Engine with the given configuration.
If config is nil, DefaultConfig() is used.
The engine starts with empty co-access tracking. Call SetSimilaritySearch() to enable similarity-based inference.
Example:
// With defaults
engine := inference.New(nil)
// With custom config
config := &inference.Config{
SimilarityThreshold: 0.85,
SimilarityTopK: 15,
CoAccessEnabled: true,
}
engine = inference.New(config)
Returns a new Engine ready for use.
func (*Engine) CleanupTier1 ¶
CleanupTier1 runs cleanup on Tier 1 data structures. Should be called periodically (e.g., every 10 minutes). provenanceMaxAge specifies the max age for provenance records (0 = don't cleanup).
func (*Engine) CleanupTier1WithProvenance ¶
func (e *Engine) CleanupTier1WithProvenance(provenanceMaxAge time.Duration) (cooldownRemoved, evidenceRemoved, provenanceRemoved int)
CleanupTier1WithProvenance runs cleanup including provenance records. provenanceMaxAge specifies the max age for provenance records.
func (*Engine) GetClusterIntegration ¶
func (e *Engine) GetClusterIntegration() *ClusterIntegration
GetClusterIntegration returns the current cluster integration (or nil).
func (*Engine) GetCooldownTable ¶
func (e *Engine) GetCooldownTable() *CooldownTable
GetCooldownTable returns the cooldown table for direct access. The table is always available regardless of auto-integration setting.
func (*Engine) GetEdgeMetaStore ¶
func (e *Engine) GetEdgeMetaStore() *storage.EdgeMetaStore
GetEdgeMetaStore returns the edge meta store for direct access. The store is always available regardless of auto-integration setting.
func (*Engine) GetEvidenceBuffer ¶
func (e *Engine) GetEvidenceBuffer() *EvidenceBuffer
GetEvidenceBuffer returns the evidence buffer for direct access. The buffer is always available regardless of auto-integration setting.
func (*Engine) GetHeimdallQC ¶
func (e *Engine) GetHeimdallQC() *HeimdallQC
GetHeimdallQC returns the current Heimdall QC (or nil if not configured).
func (*Engine) GetKalmanAdapter ¶
func (e *Engine) GetKalmanAdapter() *KalmanAdapter
GetKalmanAdapter returns the current Kalman adapter (or nil if not configured).
func (*Engine) GetNodeConfigStore ¶
func (e *Engine) GetNodeConfigStore() *storage.NodeConfigStore
GetNodeConfigStore returns the node config store for direct access. The store is always available regardless of auto-integration setting.
func (*Engine) GetTopologyIntegration ¶
func (e *Engine) GetTopologyIntegration() *TopologyIntegration
GetTopologyIntegration returns the current topology integration (or nil).
func (*Engine) OnAccess ¶
func (e *Engine) OnAccess(ctx context.Context, nodeID string) []EdgeSuggestion
OnAccess is called when a node is accessed (read).
This method tracks co-access patterns - nodes that are accessed close together in time are likely related. After seeing the same pair accessed together multiple times, it suggests creating a relationship.
Parameters:
- ctx: Context (currently unused, reserved for future)
- nodeID: ID of the accessed node
Returns:
- Slice of EdgeSuggestion based on co-access patterns
Example:
func GetMemory(id string) (*Memory, error) {
// Retrieve memory
mem, err := db.Get(id)
if err != nil {
return nil, err
}
// Track access for inference
suggestions := engine.OnAccess(ctx, id)
// Log co-access patterns
for _, sug := range suggestions {
if sug.Method == "co_access" {
log.Printf("Co-accessed with %s (%d times)",
sug.TargetID, sug.Confidence*10)
// Create edge if frequently co-accessed
if sug.Confidence >= 0.6 {
createEdge(sug)
}
}
}
return mem, nil
}
How It Works:
The engine maintains a sliding window of recent accesses. When you access node A, it checks what other nodes were accessed in the last 30 seconds (configurable). If the same pair appears multiple times, it suggests they're related.
Use Case:
In a note-taking app, if you always view "Project Plan" and "Budget" together, the engine suggests: "These seem related - want to link them?"
func (*Engine) OnStore ¶
func (e *Engine) OnStore(ctx context.Context, nodeID string, embedding []float32) ([]EdgeSuggestion, error)
OnStore is called when a new node is stored in the graph.
This method analyzes the new node and suggests relationships based on vector similarity. High-confidence suggestions can be automatically created as edges.
Parameters:
- ctx: Context for cancellation
- nodeID: ID of the newly created node
- embedding: Vector embedding of the node's content
Returns:
- Slice of EdgeSuggestion with confidence scores and reasons
- Error if similarity search fails
Example:
// User creates a new note
note := &Note{
ID: "note-456",
Content: "Machine learning algorithms",
Embedding: embedder.Embed("Machine learning algorithms"),
}
// Get suggestions
suggestions, err := engine.OnStore(ctx, note.ID, note.Embedding)
if err != nil {
return err
}
fmt.Printf("Found %d related notes:\n", len(suggestions))
for _, sug := range suggestions {
relatedNote := getNote(sug.TargetID)
fmt.Printf(" - %s (%.0f%% confident): %s\n",
relatedNote.Title, sug.Confidence*100, sug.Reason)
// Auto-link if very confident
if sug.Confidence >= 0.8 {
createEdge(sug)
log.Printf("Auto-linked: %s -> %s", note.ID, relatedNote.ID)
}
}
Typical confidence levels:
- 0.9+: Very confident, safe to auto-create
- 0.7-0.9: Confident, suggest to user
- 0.5-0.7: Possible, show as "related"
- <0.5: Weak, ignore
func (*Engine) OnStoreBestOfChunks ¶
func (e *Engine) OnStoreBestOfChunks(ctx context.Context, nodeID string, embeddings [][]float32) ([]EdgeSuggestion, error)
OnStoreBestOfChunks is called when a new node is stored (or later embedded) and has multiple chunk embeddings available.
It runs similarity search once per chunk embedding, then collapses candidates to unique node IDs and keeps the best (highest) similarity score per node ("best-of-chunks"). The merged semantic suggestions are then combined with topology integration (if enabled), and optionally sent through Heimdall QC.
func (*Engine) ProcessSuggestion ¶
func (e *Engine) ProcessSuggestion(suggestion EdgeSuggestion, sessionID string) ProcessSuggestionResult
ProcessSuggestion processes an edge suggestion through cooldown and evidence buffering.
This method applies Tier 1 safety features (when auto-integration is enabled):
- Cooldown: Prevents rapid re-materialization of the same edge pair
- Evidence Buffering: Requires multiple signals before materializing
Feature flags:
- NORNICDB_COOLDOWN_AUTO_INTEGRATION_ENABLED (default: true)
- NORNICDB_EVIDENCE_AUTO_INTEGRATION_ENABLED (default: true)
Parameters:
- suggestion: The edge suggestion to process
- sessionID: Current session ID for evidence tracking
Returns ProcessSuggestionResult indicating whether to create the edge.
Example:
suggestions, _ := engine.OnStore(ctx, nodeID, embedding)
for _, sug := range suggestions {
result := engine.ProcessSuggestion(sug, "session-123")
if result.ShouldMaterialize {
db.CreateEdge(sug.SourceID, sug.TargetID, sug.Type)
engine.RecordMaterialization(sug.SourceID, sug.TargetID, sug.Type)
}
}
func (*Engine) RecordMaterialization ¶
RecordMaterialization records that an edge was materialized.
Call this after successfully creating an edge to update:
- Cooldown tracking (prevents immediate re-creation)
- Provenance logs (audit trail of why edge was created)
- Node config edge counts (enforces per-node limits)
This is the "clean up after yourself" function - ALWAYS call it after creating an edge to keep internal state synchronized.
Example 1: Basic usage after edge creation
result := engine.ProcessSuggestion(suggestion, "session-123")
if result.ShouldMaterialize {
db.CreateEdge(suggestion.SourceID, suggestion.TargetID, suggestion.Type)
engine.RecordMaterialization(suggestion.SourceID, suggestion.TargetID, suggestion.Type)
}
Example 2: Batch edge creation
for _, sug := range suggestions {
if sug.Confidence > 0.8 {
db.CreateEdge(sug.SourceID, sug.TargetID, sug.Type)
engine.RecordMaterialization(sug.SourceID, sug.TargetID, sug.Type)
}
}
Example 3: Manual edge creation (bypassing ProcessSuggestion)
// User explicitly creates an edge
db.CreateEdge("user-123", "doc-456", "bookmarked")
// Still record it to prevent suggestions for same edge
engine.RecordMaterialization("user-123", "doc-456", "bookmarked")
ELI12 (Explain Like I'm 12):
Think of this like checking out a library book. When you return it:
- Cooldown: "You just returned this book, wait 5 minutes before checking it out again"
- Provenance: "Record that you borrowed this book on June 15th"
- Node Config: "Update your total books borrowed count (you're at 9/10 limit now)"
If you forget to call this, it's like never returning the book - the library thinks you still have it and might suggest you borrow it again (duplicate!).
func (*Engine) SetClusterIntegration ¶
func (e *Engine) SetClusterIntegration(integration *ClusterIntegration)
SetClusterIntegration enables GPU-accelerated k-means clustering for similarity search.
When enabled, similarity searches are accelerated using cluster-based approximate nearest neighbor search. This provides significant speedup for large embedding indices (10K+ embeddings).
Parameters:
- integration: ClusterIntegration instance (nil to disable)
Example:
engine := inference.New(inference.DefaultConfig())
// Enable clustering
gpuManager, _ := gpu.NewManager(&gpu.Config{Enabled: true})
clusterConfig := inference.DefaultClusterConfig()
clusterConfig.Enabled = true
clusterConfig.NumClustersSearch = 5
ci := inference.NewClusterIntegration(gpuManager, clusterConfig, nil, nil)
engine.SetClusterIntegration(ci)
// Add embeddings during indexing
ci.AddEmbedding(nodeID, embedding)
// Trigger clustering after bulk load
ci.OnIndexComplete()
// Searches now use cluster acceleration
results, _ := ci.Search(ctx, queryEmbedding, 10)
func (*Engine) SetCooldownTable ¶
func (e *Engine) SetCooldownTable(table *CooldownTable)
SetCooldownTable sets a custom cooldown table.
func (*Engine) SetEdgeMetaStore ¶
func (e *Engine) SetEdgeMetaStore(store *storage.EdgeMetaStore)
SetEdgeMetaStore sets a custom edge meta store.
func (*Engine) SetEvidenceBuffer ¶
func (e *Engine) SetEvidenceBuffer(buffer *EvidenceBuffer)
SetEvidenceBuffer sets a custom evidence buffer.
func (*Engine) SetHeimdallQC ¶
func (e *Engine) SetHeimdallQC(qc *HeimdallQC)
SetHeimdallQC sets the Heimdall SLM quality control for edge validation.
When set and enabled (via NORNICDB_AUTO_TLP_LLM_QC_ENABLED=true), each edge suggestion from OnStore() and OnAccess() will be validated by the Heimdall SLM before being returned. The SLM can approve, reject, or modify the suggested relationship type.
Example:
// Create Heimdall QC with your SLM function
qc := inference.NewHeimdallQC(func(ctx context.Context, prompt string) (string, error) {
return myOllamaClient.Complete(ctx, prompt)
}, nil)
engine.SetHeimdallQC(qc)
// Now suggestions are validated by Heimdall
suggestions, _ := engine.OnStore(ctx, nodeID, embedding)
// Only returns approved suggestions
func (*Engine) SetKalmanAdapter ¶
func (e *Engine) SetKalmanAdapter(adapter *KalmanAdapter)
SetKalmanAdapter configures the Kalman-enhanced inference adapter.
The KalmanAdapter provides:
- Smoothed confidence scores using Kalman filtering
- Temporal access pattern tracking
- Session-aware co-access detection
- Relationship strength trend analysis
This is an OPTIONAL enhancement - if not set, base inference works normally. Enable via NORNICDB_KALMAN_ENABLED=true environment variable.
Example:
if config.IsKalmanEnabled() {
adapter := inference.NewKalmanAdapter(engine, inference.DefaultKalmanAdapterConfig())
tracker := temporal.NewTracker(temporal.DefaultConfig())
adapter.SetTracker(tracker)
engine.SetKalmanAdapter(adapter)
}
func (*Engine) SetNodeConfigStore ¶
func (e *Engine) SetNodeConfigStore(store *storage.NodeConfigStore)
SetNodeConfigStore sets a custom node config store.
func (*Engine) SetSimilaritySearch ¶
func (e *Engine) SetSimilaritySearch(fn func(ctx context.Context, embedding []float32, k int) ([]SimilarityResult, error))
SetSimilaritySearch sets the similarity search function.
func (*Engine) SetTopologyIntegration ¶
func (e *Engine) SetTopologyIntegration(integration *TopologyIntegration)
SetTopologyIntegration enables topological link prediction.
This adds graph structure analysis to edge suggestions, combining topology with semantic/behavioral signals for more robust predictions.
Parameters:
- integration: TopologyIntegration instance (nil to disable)
Example:
engine := inference.New(inference.DefaultConfig()) // Enable topology topoConfig := inference.DefaultTopologyConfig() topoConfig.Enabled = true topoConfig.Weight = 0.4 // 40% topology, 60% semantic topo := inference.NewTopologyIntegration(storage, topoConfig) engine.SetTopologyIntegration(topo) // Now suggestions include topology signals suggestions, _ := engine.OnStore(ctx, nodeID, embedding)
func (*Engine) SuggestTransitive ¶
func (e *Engine) SuggestTransitive(ctx context.Context, edges []ExistingEdge) []EdgeSuggestion
SuggestTransitive suggests edges based on transitive relationships. If A->B and B->C with sufficient confidence, suggest A->C.
type Evidence ¶
type Evidence struct {
Key EvidenceKey
Count int // Total evidence count
ScoreSum float64 // Cumulative score
ScoreAvg float64 // Average score (updated on add)
FirstTs time.Time // When first evidence was added
LastTs time.Time // When last evidence was added
Sessions map[string]bool // Unique session IDs
Signals []string // Signal types seen (coaccess, similarity, etc.)
Metadata map[string]interface{} // Additional context
}
Evidence accumulates signals for a potential edge.
type EvidenceBuffer ¶
type EvidenceBuffer struct {
// contains filtered or unexported fields
}
EvidenceBuffer accumulates signals before materialization. Thread-safe for concurrent access.
func GlobalEvidenceBuffer ¶
func GlobalEvidenceBuffer() *EvidenceBuffer
GlobalEvidenceBuffer returns the global evidence buffer singleton.
func NewEvidenceBuffer ¶
func NewEvidenceBuffer() *EvidenceBuffer
NewEvidenceBuffer creates a new evidence buffer with default thresholds.
The evidence buffer accumulates "signals" about potential relationships before materializing them as actual edges in the graph. This prevents creating edges from single weak signals and ensures only well-supported relationships exist.
Returns:
- *EvidenceBuffer with default thresholds for all edge types
Default Thresholds:
- RELATED_TO: 3 occurrences, score 0.3, 2 sessions
- SIMILAR_TO: 2 occurrences, score 0.5, 1 session
- REFERENCES: 2 occurrences, score 0.4, 1 session
Example 1 - Basic Usage:
buffer := inference.NewEvidenceBuffer()
// Add signals as they occur
buffer.AddEvidence("doc-1", "doc-2", "RELATED_TO", 0.8, "cosine_similarity", "session-123")
buffer.AddEvidence("doc-1", "doc-2", "RELATED_TO", 0.7, "co_occurrence", "session-123")
// Third signal crosses threshold
shouldCreate := buffer.AddEvidence("doc-1", "doc-2", "RELATED_TO", 0.9, "user_link", "session-456")
if shouldCreate {
// Create actual edge in graph
createEdge("doc-1", "doc-2", "RELATED_TO")
}
Example 2 - Integration with Inference Engine:
buffer := inference.NewEvidenceBuffer()
engine := inference.New(inference.DefaultConfig())
engine.SetEvidenceBuffer(buffer)
// Engine automatically accumulates evidence
engine.OnAccess("doc-1", "doc-2", 0.85, "access_pattern")
engine.OnAccess("doc-1", "doc-2", 0.90, "semantic_similarity")
engine.OnAccess("doc-1", "doc-2", 0.75, "temporal_proximity")
// Periodically check for materializable edges
ready := buffer.GetReadyToMaterialize()
for _, evidence := range ready {
createEdge(evidence.Key.Src, evidence.Key.Dst, evidence.Key.Label)
}
Example 3 - Custom Thresholds:
// For stricter requirements
buffer := inference.NewEvidenceBuffer()
buffer.SetThreshold("COLLABORATES_WITH", inference.EvidenceThreshold{
MinCount: 5, // Need 5 signals
MinScore: 2.5, // Total score >= 2.5
MinSessions: 3, // Across 3+ sessions
})
ELI12:
Think of the evidence buffer like a "voting box" for potential friendships:
- Alice and Bob work together → +1 vote, "coworker" ballot
- They chat during lunch → +1 vote, "social" ballot
- They're in same project → +1 vote, "project" ballot
Once they get 3 votes (threshold), we officially mark them as friends! This prevents marking people as friends after just ONE interaction.
Real-world Use Cases:
- Document similarity (don't link after one keyword match)
- User behavior patterns (need repeated evidence)
- Recommendation engines (confidence from multiple signals)
- Knowledge graph construction (verify relationships)
Performance:
- O(1) evidence addition
- Memory: ~100-200 bytes per evidence entry
- Automatic cleanup of expired/materialized entries
Thread Safety:
All methods are thread-safe for concurrent access.
func NewEvidenceBufferWithConfig ¶
func NewEvidenceBufferWithConfig(labelThresholds map[string]EvidenceThreshold) *EvidenceBuffer
NewEvidenceBufferWithConfig creates an evidence buffer with custom thresholds.
func NewEvidenceBufferWithOptions ¶
func NewEvidenceBufferWithOptions(opts ...EvidenceBufferOption) *EvidenceBuffer
NewEvidenceBufferWithOptions creates a buffer with functional options.
func (*EvidenceBuffer) AddEvidence ¶
func (eb *EvidenceBuffer) AddEvidence(src, dst, label string, score float64, signalType, sessionID string) bool
AddEvidence adds a new evidence point for an edge pair. Returns true if the evidence threshold is now met (edge should be materialized). The signal is added regardless of feature flag; the flag only affects the return value.
func (*EvidenceBuffer) AddEvidenceWithMetadata ¶
func (eb *EvidenceBuffer) AddEvidenceWithMetadata(src, dst, label string, score float64, signalType, sessionID string, metadata map[string]interface{}) bool
AddEvidenceWithMetadata adds evidence with additional metadata.
func (*EvidenceBuffer) CheckThreshold ¶
func (eb *EvidenceBuffer) CheckThreshold(src, dst, label string) (bool, string)
CheckThreshold checks if evidence meets threshold without adding new evidence.
func (*EvidenceBuffer) Cleanup ¶
func (eb *EvidenceBuffer) Cleanup() int
Cleanup removes expired evidence entries to prevent memory growth. Should be called periodically (e.g., every hour). Returns the number of entries removed.
func (*EvidenceBuffer) Clear ¶
func (eb *EvidenceBuffer) Clear()
Clear removes all evidence entries.
func (*EvidenceBuffer) ClearEntry ¶
func (eb *EvidenceBuffer) ClearEntry(src, dst, label string)
ClearEntry removes evidence for a specific edge pair.
func (*EvidenceBuffer) GetEvidence ¶
func (eb *EvidenceBuffer) GetEvidence(src, dst, label string) *Evidence
GetEvidence returns the current evidence for an edge pair. Returns nil if no evidence exists.
func (*EvidenceBuffer) GetPendingEdges ¶
func (eb *EvidenceBuffer) GetPendingEdges(minProgress float64) []Evidence
GetPendingEdges returns evidence entries that are close to threshold. Useful for monitoring and proactive materialization.
func (*EvidenceBuffer) GetThreshold ¶
func (eb *EvidenceBuffer) GetThreshold(label string) EvidenceThreshold
GetThreshold returns the threshold for a label.
func (*EvidenceBuffer) SetThreshold ¶
func (eb *EvidenceBuffer) SetThreshold(label string, threshold EvidenceThreshold)
SetThreshold sets the threshold for a specific label.
func (*EvidenceBuffer) Size ¶
func (eb *EvidenceBuffer) Size() int
Size returns the number of tracked evidence entries.
func (*EvidenceBuffer) Stats ¶
func (eb *EvidenceBuffer) Stats() EvidenceStats
Stats returns current evidence buffer statistics.
type EvidenceBufferOption ¶
type EvidenceBufferOption func(*EvidenceBuffer)
EvidenceBufferOption configures an EvidenceBuffer.
func WithThreshold ¶
func WithThreshold(label string, threshold EvidenceThreshold) EvidenceBufferOption
WithThreshold sets a specific label threshold during initialization.
type EvidenceKey ¶
EvidenceKey uniquely identifies an edge pair.
func (EvidenceKey) String ¶
func (k EvidenceKey) String() string
String returns a string representation of the key.
type EvidenceStats ¶
type EvidenceStats struct {
TotalEntries int64
TotalAdded int64
TotalMaterialized int64
TotalExpired int64
MaterializeRate float64 // Materialized / Added (0.0 - 1.0)
}
EvidenceStats provides observability into evidence buffer behavior.
type EvidenceThreshold ¶
type EvidenceThreshold struct {
MinCount int // Minimum evidence count required
MinScore float64 // Minimum cumulative score required
MinSessions int // Minimum unique sessions required
MaxAge time.Duration // Evidence expires after this duration
}
EvidenceThreshold defines when evidence is sufficient for materialization.
type ExistingEdge ¶
ExistingEdge represents an edge in the graph.
type HeimdallBatchRequest ¶
type HeimdallBatchRequest struct {
// SourceNode is the newly created/accessed node
SourceNode NodeSummary `json:"source"`
// Candidates are TLP suggestions to review
Candidates []CandidateSummary `json:"candidates"`
// AllowAugment indicates if Heimdall can suggest additional edges
AllowAugment bool `json:"allow_augment"`
// CandidatePool are other nearby nodes Heimdall can consider (for augment)
// Only populated if AllowAugment is true
CandidatePool []NodeSummary `json:"candidate_pool,omitempty"`
}
HeimdallBatchRequest contains a batch of suggestions for review.
type HeimdallBatchResponse ¶
type HeimdallBatchResponse struct {
// Approved are indices of candidates to keep (0-based)
Approved []int `json:"approved"`
// Rejected are indices of candidates to skip (optional, for logging)
Rejected []int `json:"rejected,omitempty"`
// TypeOverrides maps candidate index to suggested type override
TypeOverrides map[int]string `json:"type_overrides,omitempty"`
// Additional are NEW edges Heimdall suggests (only if augment enabled)
Additional []AugmentedEdge `json:"additional,omitempty"`
// Reasoning is brief overall explanation
Reasoning string `json:"reasoning,omitempty"`
}
HeimdallBatchResponse is the SLM's batch decision.
type HeimdallFunc ¶
HeimdallFunc is the function signature for calling the SLM.
IMPORTANT: Each call is STATELESS. No context accumulates. Uses the SAME heimdall.Generator as Bifrost commands (in-memory llama.cpp). System prompt is cached in KV, only user content varies per call.
Example (using shared heimdall.Generator):
heimdallFunc := func(ctx context.Context, userContent string) (string, error) {
prompt := inference.GetSystemPrompt(augmentEnabled) + "\n\n" + userContent
return generator.Generate(ctx, prompt, heimdall.GenerateParams{
MaxTokens: 256, Temperature: 0.1,
})
}
type HeimdallQC ¶
type HeimdallQC struct {
// contains filtered or unexported fields
}
HeimdallQC manages LLM-based hybrid review for Auto-TLP.
func NewHeimdallQC ¶
func NewHeimdallQC(heimdallFunc HeimdallFunc, cfg *HeimdallQCConfig) *HeimdallQC
NewHeimdallQC creates a new Heimdall QC manager.
func (*HeimdallQC) ClearCache ¶
func (h *HeimdallQC) ClearCache()
ClearCache clears the decision cache.
func (*HeimdallQC) GetStats ¶
func (h *HeimdallQC) GetStats() HeimdallQCStats
GetStats returns current QC statistics.
func (*HeimdallQC) ReviewBatch ¶
func (h *HeimdallQC) ReviewBatch( ctx context.Context, sourceNode NodeSummary, suggestions []EdgeSuggestion, candidatePool []NodeSummary, ) (approved []EdgeSuggestion, augmented []EdgeSuggestion, err error)
ReviewBatch reviews a batch of TLP suggestions with Heimdall. Returns approved suggestions (possibly with type overrides) + any augmented edges.
type HeimdallQCConfig ¶
type HeimdallQCConfig struct {
// Enabled controls whether QC is active (also requires feature flag)
Enabled bool
// Timeout for SLM response (default: 10s for batch)
Timeout time.Duration
// MaxContextBytes is the maximum prompt size in bytes
// If a batch exceeds this, nodes are summarized or skipped
// Default: 4096 (safe for most small models)
MaxContextBytes int
// MaxBatchSize limits how many suggestions per SLM call
// Default: 5 (balance between efficiency and model capacity)
MaxBatchSize int
// MaxNodeSummaryLen truncates node properties to this length
// Default: 200 characters per property
MaxNodeSummaryLen int
// MinConfidenceToReview skips TLP suggestions below this threshold
// Default: 0.5 (don't waste SLM time on weak candidates)
MinConfidenceToReview float64
// CacheDecisions caches Heimdall decisions
// Default: true
CacheDecisions bool
// CacheTTL is how long to cache decisions
// Default: 1 hour
CacheTTL time.Duration
}
HeimdallQCConfig configures the Heimdall hybrid review system.
func DefaultHeimdallQCConfig ¶
func DefaultHeimdallQCConfig() *HeimdallQCConfig
DefaultHeimdallQCConfig returns sensible defaults for small models.
type HeimdallQCStats ¶
type HeimdallQCStats struct {
BatchesProcessed int64
SuggestionsIn int64
SuggestionsOut int64
Augmented int64
Skipped int64 // Too large or below threshold
Errors int64
CacheHits int64
AvgLatencyMs float64
// contains filtered or unexported fields
}
HeimdallQCStats tracks QC operations.
type InferenceAdapterStats ¶
type InferenceAdapterStats struct {
TotalSuggestions int64
KalmanSmoothed int64
SessionEnhanced int64
CrossSessionBoosted int64
RelationshipsStrengthened int64
RelationshipsWeakened int64
}
InferenceAdapterStats holds statistics about the adapter's operation.
type KalmanAdapter ¶
type KalmanAdapter struct {
// contains filtered or unexported fields
}
KalmanAdapter wraps an inference Engine with Kalman filtering and session awareness.
func NewKalmanAdapter ¶
func NewKalmanAdapter(engine *Engine, config KalmanAdapterConfig) *KalmanAdapter
NewKalmanAdapter creates a new Kalman-enhanced inference adapter.
Example:
engine := inference.New(inference.DefaultConfig()) adapter := inference.NewKalmanAdapter(engine, inference.DefaultKalmanAdapterConfig()) // Connect temporal session detector session := temporal.NewSessionDetector(temporal.DefaultSessionConfig()) adapter.SetSessionDetector(session) // Use enhanced suggestions suggestions := adapter.OnAccess(ctx, nodeID)
func (*KalmanAdapter) GetEngine ¶
func (ka *KalmanAdapter) GetEngine() *Engine
GetEngine returns the underlying inference engine.
func (*KalmanAdapter) GetRelationshipStrength ¶
func (ka *KalmanAdapter) GetRelationshipStrength(source, target string) *smoothedConfidence
GetRelationshipStrength returns the Kalman-smoothed relationship strength.
func (*KalmanAdapter) GetStats ¶
func (ka *KalmanAdapter) GetStats() InferenceAdapterStats
GetStats returns adapter statistics.
func (*KalmanAdapter) GetStrengtheningRelationships ¶
func (ka *KalmanAdapter) GetStrengtheningRelationships(minVelocity float64) []EdgeSuggestion
GetStrentheningRelationships returns relationships that are getting stronger.
func (*KalmanAdapter) GetWeakeningRelationships ¶
func (ka *KalmanAdapter) GetWeakeningRelationships(maxVelocity float64) []EdgeSuggestion
GetWeakeningRelationships returns relationships that are getting weaker.
func (*KalmanAdapter) OnAccess ¶
func (ka *KalmanAdapter) OnAccess(ctx context.Context, nodeID string) ([]EdgeSuggestion, error)
OnAccess processes a node access and returns enhanced edge suggestions.
This method:
- Records access in the temporal tracker (if set)
- Gets base suggestions from the inference engine
- Enhances with session-based co-access
- Smooths confidence scores with Kalman filter
- Detects cross-session patterns
func (*KalmanAdapter) OnStore ¶
func (ka *KalmanAdapter) OnStore(ctx context.Context, nodeID string, embedding []float32) ([]EdgeSuggestion, error)
OnStore processes a new node and returns enhanced suggestions.
func (*KalmanAdapter) PredictFutureRelationships ¶
func (ka *KalmanAdapter) PredictFutureRelationships(threshold float64) []EdgeSuggestion
PredictFutureRelationships predicts which relationships will likely form.
Returns suggestions for node pairs that are trending toward each other based on their co-access velocity.
func (*KalmanAdapter) Reset ¶
func (ka *KalmanAdapter) Reset()
Reset clears all cached data and filters.
func (*KalmanAdapter) SetSessionDetector ¶
func (ka *KalmanAdapter) SetSessionDetector(s *temporal.SessionDetector)
SetSessionDetector connects a temporal session detector.
func (*KalmanAdapter) SetTracker ¶
func (ka *KalmanAdapter) SetTracker(t *temporal.Tracker)
SetTracker connects a temporal access tracker.
type KalmanAdapterConfig ¶
type KalmanAdapterConfig struct {
// EnableConfidenceSmoothing enables Kalman filtering of confidence scores
EnableConfidenceSmoothing bool
// EnableSessionTracking uses temporal.SessionDetector for session-aware co-access
EnableSessionTracking bool
// EnableStrengthTracking tracks relationship strength changes over time
EnableStrengthTracking bool
// CoAccessConfig for the co-access confidence filter
CoAccessConfig filter.Config
// MinConfidenceChange is the minimum change to trigger an update
MinConfidenceChange float64
// SessionCoAccessWeight is how much session-based co-access contributes
SessionCoAccessWeight float64
// CrossSessionBoost boosts relationships that appear across multiple sessions
CrossSessionBoost float64
}
KalmanAdapterConfig holds configuration for the Kalman-enhanced inference adapter.
func DefaultKalmanAdapterConfig ¶
func DefaultKalmanAdapterConfig() KalmanAdapterConfig
DefaultKalmanAdapterConfig returns sensible defaults.
type NodeSummary ¶
type NodeSummary struct {
ID string `json:"id"`
Labels []string `json:"labels"`
Props map[string]string `json:"props"` // Summarized string props only
}
NodeSummary is a compact representation of a node for the prompt.
func SummarizeNode ¶
func SummarizeNode(id string, labels []string, props map[string]interface{}, maxPropLen int) NodeSummary
SummarizeNode creates a compact summary of a node for prompts. Truncates large properties and filters to string values only.
type ProcessSuggestionResult ¶
type ProcessSuggestionResult struct {
ShouldMaterialize bool // True if edge should be created
Reason string // Why or why not
CooldownBlocked bool // True if blocked by cooldown
EvidencePending bool // True if waiting for more evidence
NodeConfigBlocked bool // True if blocked by per-node config (deny list, caps, etc.)
}
ProcessSuggestionResult contains the result of processing a suggestion.
type SimilarityResult ¶
SimilarityResult from vector search.
type Stats ¶
type Stats struct {
TotalSuggestions int64
BySimilarity int64
ByCoAccess int64
ByTransitive int64
TrackedCoAccesses int
}
Stats returns inference statistics.
type TopologyConfig ¶
type TopologyConfig struct {
// Enable topological link prediction
Enabled bool
// Algorithm to use: "adamic_adar", "jaccard", "common_neighbors",
// "resource_allocation", "preferential_attachment", or "ensemble"
Algorithm string
// TopK results to consider from topological algorithm
TopK int
// Minimum score threshold for topology predictions
MinScore float64
// Weight for topology score in hybrid mode (0.0-1.0)
// Semantic weight is (1.0 - Weight)
Weight float64
// GraphRefreshInterval: how often to rebuild graph from storage
// Zero means rebuild on every prediction (safe but slow)
GraphRefreshInterval int // number of predictions before refresh
// CachePath is the directory for persisting cached graphs.
// Empty string disables disk caching.
CachePath string
// CacheTTL is how long a cached graph is valid.
// Default: 1 hour
CacheTTL time.Duration
// BuildTimeout is the maximum time for graph building.
// Default: 5 minutes
BuildTimeout time.Duration
// WorkerCount controls parallel edge fetching.
// Default: runtime.NumCPU()
WorkerCount int
// ChunkSize controls streaming construction.
// Default: 1000
ChunkSize int
// ProgressCallback is called during graph building.
// Optional.
ProgressCallback func(processed, total int, elapsed time.Duration)
}
TopologyConfig controls topological link prediction integration.
This allows the inference engine to incorporate graph structure signals alongside semantic similarity, co-access, and temporal patterns.
Example:
config := &inference.TopologyConfig{
Enabled: true,
Algorithm: "adamic_adar",
TopK: 10,
MinScore: 0.3,
Weight: 0.4, // 40% weight vs 60% semantic
}
func DefaultTopologyConfig ¶
func DefaultTopologyConfig() *TopologyConfig
DefaultTopologyConfig returns sensible defaults for topology integration.
type TopologyIntegration ¶
type TopologyIntegration struct {
// contains filtered or unexported fields
}
TopologyIntegration adds topological link prediction to the inference engine.
This is an optional extension that can be enabled to incorporate graph structure signals into edge suggestions. When enabled, suggestions combine:
- Semantic similarity (embeddings)
- Co-access patterns
- Temporal proximity
- Graph topology (NEW)
OPTIMIZATIONS:
- Streaming graph construction with chunked processing
- Parallel edge fetching with worker pool
- Disk-based caching (gob serialization)
- Incremental updates via delta changes
- Context cancellation and timeout support
Example:
engine := inference.New(inference.DefaultConfig()) // Enable topology integration with caching topoConfig := inference.DefaultTopologyConfig() topoConfig.Enabled = true topoConfig.CachePath = "/tmp/nornicdb/cache" topoConfig.Weight = 0.5 // Equal weight topo := inference.NewTopologyIntegration(storageEngine, topoConfig) engine.SetTopologyIntegration(topo) // Now OnStore() suggestions include topology signals suggestions, _ := engine.OnStore(ctx, nodeID, embedding)
func NewTopologyIntegration ¶
func NewTopologyIntegration(storage storage.Engine, config *TopologyConfig) *TopologyIntegration
NewTopologyIntegration creates a new topology integration.
Parameters:
- storage: Storage engine to build graph from
- config: Topology configuration (nil uses defaults)
Returns ready-to-use integration that can be attached to inference engine.
func (*TopologyIntegration) CombinedSuggestions ¶
func (t *TopologyIntegration) CombinedSuggestions(semantic, topological []EdgeSuggestion) []EdgeSuggestion
CombinedSuggestions blends semantic and topological suggestions.
This method:
- Gets semantic suggestions (from existing inference engine)
- Gets topological suggestions (from this integration)
- Merges and ranks by weighted score
- Removes duplicates (keeping highest scored)
Parameters:
- semantic: Suggestions from semantic inference
- topological: Suggestions from topology integration
Returns merged and ranked suggestions.
Example:
semantic := engine.OnStore(ctx, nodeID, embedding) // existing
topological, _ := topo.SuggestTopological(ctx, nodeID) // new
combined := topo.CombinedSuggestions(semantic, topological)
for _, sug := range combined {
if sug.Confidence >= 0.7 {
createEdge(sug)
}
}
func (*TopologyIntegration) InvalidateCache ¶
func (t *TopologyIntegration) InvalidateCache()
InvalidateCache forces graph rebuild on next prediction.
Call this when the graph structure changes significantly (e.g., batch import, node/edge deletion, schema changes).
func (*TopologyIntegration) OnEdgeAdded ¶
func (t *TopologyIntegration) OnEdgeAdded(from, to storage.NodeID)
OnEdgeAdded notifies the integration of a new edge.
func (*TopologyIntegration) OnEdgeRemoved ¶
func (t *TopologyIntegration) OnEdgeRemoved(from, to storage.NodeID)
OnEdgeRemoved notifies the integration of a removed edge.
func (*TopologyIntegration) OnNodeAdded ¶
func (t *TopologyIntegration) OnNodeAdded(nodeID storage.NodeID)
OnNodeAdded notifies the integration of a new node. Call this when a node is created to enable incremental updates.
func (*TopologyIntegration) OnNodeRemoved ¶
func (t *TopologyIntegration) OnNodeRemoved(nodeID storage.NodeID)
OnNodeRemoved notifies the integration of a removed node.
func (*TopologyIntegration) Stats ¶
func (t *TopologyIntegration) Stats() TopologyStats
Stats returns topology integration statistics.
func (*TopologyIntegration) SuggestTopological ¶
func (t *TopologyIntegration) SuggestTopological(ctx context.Context, sourceID string) ([]EdgeSuggestion, error)
SuggestTopological generates edge suggestions using graph topology.
This method:
- Builds/refreshes graph from storage if needed (with caching)
- Runs configured topological algorithm
- Converts results to EdgeSuggestion format
- Returns suggestions compatible with inference engine
Parameters:
- ctx: Context for cancellation
- sourceID: Node to predict edges from
Returns:
- Slice of EdgeSuggestion with Method="topology_*"
- Error if graph building or prediction fails
Example:
topo := NewTopologyIntegration(storage, config)
suggestions, err := topo.SuggestTopological(ctx, "node-123")
for _, sug := range suggestions {
fmt.Printf("%s: %.3f (%s)\n", sug.TargetID, sug.Confidence, sug.Method)
}
type TopologyStats ¶
type TopologyStats struct {
GraphNodeCount int
GraphEdgeCount int
PredictionsRun int64
BuildsCompleted int64
LastBuildTime time.Duration
TotalBuildTime time.Duration
PendingChanges int
CacheHits int64
CacheMisses int64
}
TopologyStats contains statistics about the topology integration.