inference

package
v1.2.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 20, 2026 License: MIT Imports: 16 Imported by: 0

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:

  1. TLP algorithms generate fast candidates
  2. 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:

  1. Similarity-Based Linking: Uses vector embeddings to find semantically similar nodes. Example: "Buy milk" and "Purchase dairy products" have similar embeddings.

  2. 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.

  3. Temporal Proximity: Nodes accessed in the same session (within 30 minutes) are linked. Example: All memories from a single conversation thread.

  4. 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:

  1. **Similarity**: Your math and science notebooks go together because they're both about numbers and formulas (similar content).

  2. **Co-access**: Your English notebook and dictionary always get used together, so they should be near each other on your shelf.

  3. **Temporal**: All homework from Monday night was done at the same time, so those papers are related.

  4. **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

View Source
const DefaultCooldown = 5 * time.Minute

DefaultCooldown is used when no label-specific cooldown is configured.

View Source
const HeimdallAugmentSystemPrompt = `` /* 257-byte string literal not displayed */

HeimdallAugmentSystemPrompt includes augmentation capability.

View Source
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

View Source
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.

View Source
var DefaultEvidenceThreshold = EvidenceThreshold{
	MinCount:    3,
	MinScore:    0.5,
	MinSessions: 2,
	MaxAge:      24 * time.Hour,
}

DefaultEvidenceThreshold is used when no label-specific threshold is configured.

View Source
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

func GetSystemPrompt(augment bool) string

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:

  1. After initial bulk loading
  2. After periodic re-indexing
  3. 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:

  1. Finds the k nearest clusters to the query
  2. Searches only within those clusters
  3. 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

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

func (ed *EdgeDecay) ReinforceEdge(edgeID storage.EdgeID)

ReinforceEdge marks an edge as recently accessed, resetting its decay. Call this when an edge is traversed or otherwise used.

func (*EdgeDecay) Start

func (ed *EdgeDecay) Start(ctx context.Context)

Start begins the background decay worker.

func (*EdgeDecay) Stop

func (ed *EdgeDecay) Stop()

Stop halts the background worker.

func (*EdgeDecay) TriggerScan

func (ed *EdgeDecay) TriggerScan(ctx context.Context) (scanned, decayed, deleted int, err error)

TriggerScan manually triggers a decay scan (useful for testing).

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:

  1. Create with New()
  2. Configure similarity search with SetSimilaritySearch()
  3. Call OnStore() when creating nodes
  4. Call OnAccess() when accessing nodes
  5. 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

func New(config *Config) *Engine

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

func (e *Engine) CleanupTier1() (cooldownRemoved, evidenceRemoved int)

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) GetStats

func (e *Engine) GetStats() Stats

GetStats returns current inference statistics.

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

func (e *Engine) RecordMaterialization(sourceID, targetID, edgeType string)

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

type EvidenceKey struct {
	Src   string
	Dst   string
	Label string
}

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

type ExistingEdge struct {
	SourceID   string
	TargetID   string
	Confidence float64
}

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

type HeimdallFunc func(ctx context.Context, prompt string) (string, error)

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:

  1. Records access in the temporal tracker (if set)
  2. Gets base suggestions from the inference engine
  3. Enhances with session-based co-access
  4. Smooths confidence scores with Kalman filter
  5. 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

type SimilarityResult struct {
	ID    string
	Score float64
}

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:

  1. Gets semantic suggestions (from existing inference engine)
  2. Gets topological suggestions (from this integration)
  3. Merges and ranks by weighted score
  4. 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

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:

  1. Builds/refreshes graph from storage if needed (with caching)
  2. Runs configured topological algorithm
  3. Converts results to EdgeSuggestion format
  4. 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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL