Skip to main content
Glama
orneryd

M.I.M.I.R - Multi-agent Intelligent Memory & Insight Repository

by orneryd
kalman_adapter_test.go10.8 kB
package decay import ( "context" "testing" "time" "github.com/orneryd/nornicdb/pkg/config" "github.com/orneryd/nornicdb/pkg/temporal" ) func TestDefaultKalmanAdapterConfig(t *testing.T) { cfg := DefaultKalmanAdapterConfig() if !cfg.EnableKalmanSmoothing { t.Error("Expected EnableKalmanSmoothing to be true") } if !cfg.EnableTemporalModifier { t.Error("Expected EnableTemporalModifier to be true") } if cfg.PredictionHorizon != 168 { t.Errorf("Expected PredictionHorizon 168, got %d", cfg.PredictionHorizon) } if cfg.MinScoreChange != 0.001 { t.Errorf("Expected MinScoreChange 0.001, got %f", cfg.MinScoreChange) } } func TestNewKalmanAdapter(t *testing.T) { manager := New(DefaultConfig()) adapter := NewKalmanAdapter(manager, DefaultKalmanAdapterConfig()) if adapter == nil { t.Fatal("Expected non-nil adapter") } if adapter.manager != manager { t.Error("Manager not set correctly") } if adapter.nodeFilters == nil { t.Error("nodeFilters should be initialized") } if adapter.cachedScores == nil { t.Error("cachedScores should be initialized") } } func TestKalmanAdapter_CalculateScore_Basic(t *testing.T) { // Enable Kalman filtering for test cleanup := config.WithKalmanEnabled() defer cleanup() manager := New(DefaultConfig()) adapter := NewKalmanAdapter(manager, DefaultKalmanAdapterConfig()) info := &MemoryInfo{ ID: "test-node-1", Tier: TierSemantic, CreatedAt: time.Now(), LastAccessed: time.Now(), AccessCount: 1, } // First calculation score1 := adapter.CalculateScore(info) if score1 < 0 || score1 > 1 { t.Errorf("Score should be between 0 and 1, got %f", score1) } // Check that stats were updated stats := adapter.GetStats() if stats.TotalCalculations != 1 { t.Errorf("Expected 1 calculation, got %d", stats.TotalCalculations) } if stats.KalmanSmoothed != 1 { t.Errorf("Expected 1 smoothed, got %d", stats.KalmanSmoothed) } // Check cached score cached := adapter.GetSmoothedScore(info.ID) if cached == nil { t.Fatal("Expected cached score") } if cached.Smoothed != score1 { t.Errorf("Cached smoothed %f != returned %f", cached.Smoothed, score1) } } func TestKalmanAdapter_CalculateScore_Smoothing(t *testing.T) { // Enable Kalman filtering cleanup := config.WithKalmanEnabled() defer cleanup() manager := New(DefaultConfig()) adapter := NewKalmanAdapter(manager, DefaultKalmanAdapterConfig()) info := &MemoryInfo{ ID: "test-smooth", Tier: TierSemantic, CreatedAt: time.Now().Add(-24 * time.Hour), LastAccessed: time.Now(), AccessCount: 5, } // Calculate multiple scores to see smoothing effect var scores []float64 for i := 0; i < 5; i++ { // Simulate varying raw scores by adjusting access time info.LastAccessed = time.Now().Add(-time.Duration(i*10) * time.Minute) score := adapter.CalculateScore(info) scores = append(scores, score) } // Verify all scores are valid for i, score := range scores { if score < 0 || score > 1 { t.Errorf("Score %d out of range: %f", i, score) } } // Check stats stats := adapter.GetStats() if stats.TotalCalculations != 5 { t.Errorf("Expected 5 calculations, got %d", stats.TotalCalculations) } } func TestKalmanAdapter_WithTemporalIntegration(t *testing.T) { // Enable Kalman filtering cleanup := config.WithKalmanEnabled() defer cleanup() manager := New(DefaultConfig()) adapter := NewKalmanAdapter(manager, DefaultKalmanAdapterConfig()) // Create temporal integration temporalCfg := temporal.DefaultDecayIntegrationConfig() temporalInteg := temporal.NewDecayIntegration(temporalCfg) adapter.SetTemporal(temporalInteg) info := &MemoryInfo{ ID: "test-temporal", Tier: TierSemantic, CreatedAt: time.Now().Add(-48 * time.Hour), LastAccessed: time.Now().Add(-1 * time.Hour), AccessCount: 3, } // Record some accesses to make node "hot" for i := 0; i < 10; i++ { adapter.RecordAccess(info.ID) time.Sleep(10 * time.Millisecond) } score := adapter.CalculateScore(info) // Score should be valid if score < 0 || score > 1 { t.Errorf("Score out of range: %f", score) } // Check stats show temporal modification stats := adapter.GetStats() if stats.TemporalModified < 1 { t.Error("Expected temporal modification") } } func TestKalmanAdapter_ShouldArchive(t *testing.T) { // Enable Kalman filtering cleanup := config.WithKalmanEnabled() defer cleanup() manager := New(DefaultConfig()) adapter := NewKalmanAdapter(manager, DefaultKalmanAdapterConfig()) // Create old, unused memory oldInfo := &MemoryInfo{ ID: "old-memory", Tier: TierEpisodic, CreatedAt: time.Now().Add(-30 * 24 * time.Hour), // 30 days old LastAccessed: time.Now().Add(-25 * 24 * time.Hour), // Not accessed in 25 days AccessCount: 1, } // Calculate score first to populate cache adapter.CalculateScore(oldInfo) // Should likely archive (episodic with no recent access) shouldArchive := adapter.ShouldArchive(oldInfo) // Create fresh memory freshInfo := &MemoryInfo{ ID: "fresh-memory", Tier: TierSemantic, CreatedAt: time.Now().Add(-1 * time.Hour), LastAccessed: time.Now(), AccessCount: 10, } adapter.CalculateScore(freshInfo) shouldNotArchive := adapter.ShouldArchive(freshInfo) // Fresh memory should not be archived if shouldNotArchive { t.Error("Fresh memory should not be archived") } // Check archive predictions stat stats := adapter.GetStats() if stats.ArchivePredictions != 2 { t.Errorf("Expected 2 archive predictions, got %d", stats.ArchivePredictions) } // Note: old memory MAY or MAY NOT be archived depending on exact timing // Just verify the function runs without error _ = shouldArchive } func TestKalmanAdapter_GetArchivalCandidates(t *testing.T) { // Enable Kalman filtering cleanup := config.WithKalmanEnabled() defer cleanup() manager := New(DefaultConfig()) adapter := NewKalmanAdapter(manager, DefaultKalmanAdapterConfig()) // Create a mix of memories memories := []*MemoryInfo{ { ID: "old-1", Tier: TierEpisodic, CreatedAt: time.Now().Add(-60 * 24 * time.Hour), LastAccessed: time.Now().Add(-50 * 24 * time.Hour), AccessCount: 1, }, { ID: "fresh-1", Tier: TierSemantic, CreatedAt: time.Now().Add(-1 * time.Hour), LastAccessed: time.Now(), AccessCount: 20, }, { ID: "medium-1", Tier: TierSemantic, CreatedAt: time.Now().Add(-10 * 24 * time.Hour), LastAccessed: time.Now().Add(-5 * 24 * time.Hour), AccessCount: 5, }, } // Calculate scores for all for _, info := range memories { adapter.CalculateScore(info) } // Get archival candidates candidates := adapter.GetArchivalCandidates(memories, 10) // Fresh memory should not be in candidates for _, c := range candidates { if c.ID == "fresh-1" { t.Error("Fresh memory should not be archival candidate") } } } func TestKalmanAdapter_Reinforce(t *testing.T) { manager := New(DefaultConfig()) adapter := NewKalmanAdapter(manager, DefaultKalmanAdapterConfig()) info := &MemoryInfo{ ID: "reinforce-test", Tier: TierSemantic, CreatedAt: time.Now().Add(-24 * time.Hour), LastAccessed: time.Now().Add(-1 * time.Hour), AccessCount: 5, } originalCount := info.AccessCount // Reinforce reinforced := adapter.Reinforce(info) // Reinforced should have incremented access count if reinforced.AccessCount != originalCount+1 { t.Errorf("Expected access count %d, got %d", originalCount+1, reinforced.AccessCount) } } func TestKalmanAdapter_RunDecayCycle(t *testing.T) { // Enable Kalman filtering cleanup := config.WithKalmanEnabled() defer cleanup() manager := New(DefaultConfig()) adapter := NewKalmanAdapter(manager, DefaultKalmanAdapterConfig()) memories := []*MemoryInfo{ {ID: "cycle-1", Tier: TierSemantic, CreatedAt: time.Now(), LastAccessed: time.Now(), AccessCount: 1}, {ID: "cycle-2", Tier: TierSemantic, CreatedAt: time.Now(), LastAccessed: time.Now(), AccessCount: 1}, {ID: "cycle-3", Tier: TierSemantic, CreatedAt: time.Now(), LastAccessed: time.Now(), AccessCount: 1}, } ctx := context.Background() err := adapter.RunDecayCycle(ctx, memories) if err != nil { t.Errorf("Unexpected error: %v", err) } // All should be cached now for _, info := range memories { if adapter.GetSmoothedScore(info.ID) == nil { t.Errorf("Memory %s should be cached", info.ID) } } } func TestKalmanAdapter_Reset(t *testing.T) { manager := New(DefaultConfig()) adapter := NewKalmanAdapter(manager, DefaultKalmanAdapterConfig()) info := &MemoryInfo{ ID: "reset-test", Tier: TierSemantic, CreatedAt: time.Now(), LastAccessed: time.Now(), AccessCount: 1, } // Create some state adapter.CalculateScore(info) if len(adapter.nodeFilters) == 0 { t.Error("Expected filters to be created") } // Reset adapter.Reset() if len(adapter.nodeFilters) != 0 { t.Error("Filters should be cleared") } if len(adapter.cachedScores) != 0 { t.Error("Cached scores should be cleared") } if adapter.stats.TotalCalculations != 0 { t.Error("Stats should be reset") } } func TestKalmanAdapter_GetManager(t *testing.T) { manager := New(DefaultConfig()) adapter := NewKalmanAdapter(manager, DefaultKalmanAdapterConfig()) if adapter.GetManager() != manager { t.Error("GetManager should return the underlying manager") } } func TestKalmanAdapter_DisabledSmoothing(t *testing.T) { // Disable Kalman filtering cleanup := config.WithKalmanDisabled() defer cleanup() manager := New(DefaultConfig()) cfg := DefaultKalmanAdapterConfig() cfg.EnableKalmanSmoothing = false adapter := NewKalmanAdapter(manager, cfg) info := &MemoryInfo{ ID: "no-smooth", Tier: TierSemantic, CreatedAt: time.Now(), LastAccessed: time.Now(), AccessCount: 1, } score := adapter.CalculateScore(info) // Should still work but no smoothing applied if score < 0 || score > 1 { t.Errorf("Score out of range: %f", score) } // No Kalman smoothing stats stats := adapter.GetStats() if stats.KalmanSmoothed != 0 { t.Errorf("Expected 0 smoothed with disabled smoothing, got %d", stats.KalmanSmoothed) } } // Benchmark Kalman adapter func BenchmarkKalmanAdapter_CalculateScore(b *testing.B) { config.EnableKalmanFiltering() defer config.DisableKalmanFiltering() manager := New(DefaultConfig()) adapter := NewKalmanAdapter(manager, DefaultKalmanAdapterConfig()) info := &MemoryInfo{ ID: "bench-node", Tier: TierSemantic, CreatedAt: time.Now().Add(-24 * time.Hour), LastAccessed: time.Now(), AccessCount: 10, } b.ResetTimer() for i := 0; i < b.N; i++ { adapter.CalculateScore(info) } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/orneryd/Mimir'

If you have feedback or need assistance with the MCP directory API, please join our Discord server