Changeset 0.30.1 (#200)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit is contained in:
@@ -95,45 +95,81 @@ func (s *Scanner) scan() {
|
||||
// If key is missing (err != nil or val == nil), extraction is enabled.
|
||||
}
|
||||
|
||||
// ── Extraction ──
|
||||
candidates, err := s.findCandidates(ctx)
|
||||
if err != nil {
|
||||
log.Printf("⚠ memory scanner: find candidates: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(candidates) == 0 {
|
||||
if len(candidates) > 0 {
|
||||
sem := make(chan struct{}, s.cfg.Concurrency)
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for _, c := range candidates {
|
||||
key := c.ChannelID + ":" + c.UserID
|
||||
if _, loaded := s.inFlight.LoadOrStore(key, true); loaded {
|
||||
continue
|
||||
}
|
||||
|
||||
sem <- struct{}{}
|
||||
wg.Add(1)
|
||||
|
||||
go func(cand candidate) {
|
||||
defer wg.Done()
|
||||
defer func() { <-sem }()
|
||||
defer s.inFlight.Delete(cand.ChannelID + ":" + cand.UserID)
|
||||
|
||||
extractCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
if err := s.extractor.Extract(extractCtx, cand.ChannelID, cand.UserID, cand.TeamID, cand.PersonaID); err != nil {
|
||||
log.Printf("⚠ memory extraction failed: channel=%s user=%s: %v",
|
||||
cand.ChannelID, cand.UserID, err)
|
||||
}
|
||||
}(c)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// ── v0.30.1: Compaction (decay + prune) ──
|
||||
// Runs after extraction on every tick. Gated by memory_compaction_enabled.
|
||||
s.runCompaction(ctx)
|
||||
}
|
||||
|
||||
// runCompaction applies confidence decay and pruning to stale memories.
|
||||
func (s *Scanner) runCompaction(ctx context.Context) {
|
||||
if s.stores.Memories == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Semaphore for concurrency control
|
||||
sem := make(chan struct{}, s.cfg.Concurrency)
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for _, c := range candidates {
|
||||
key := c.ChannelID + ":" + c.UserID
|
||||
if _, loaded := s.inFlight.LoadOrStore(key, true); loaded {
|
||||
continue // already in progress
|
||||
}
|
||||
|
||||
sem <- struct{}{} // acquire
|
||||
wg.Add(1)
|
||||
|
||||
go func(cand candidate) {
|
||||
defer wg.Done()
|
||||
defer func() { <-sem }() // release
|
||||
defer s.inFlight.Delete(cand.ChannelID + ":" + cand.UserID)
|
||||
|
||||
extractCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
if err := s.extractor.Extract(extractCtx, cand.ChannelID, cand.UserID, cand.TeamID, cand.PersonaID); err != nil {
|
||||
log.Printf("⚠ memory extraction failed: channel=%s user=%s: %v",
|
||||
cand.ChannelID, cand.UserID, err)
|
||||
// Check compaction kill switch (default: enabled)
|
||||
if s.stores.GlobalConfig != nil {
|
||||
val, err := s.stores.GlobalConfig.Get(ctx, "memory_compaction_enabled")
|
||||
if err == nil && val != nil {
|
||||
if enabled, ok := val["value"].(bool); ok && !enabled {
|
||||
return
|
||||
}
|
||||
}(c)
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
// Decay: reduce confidence for memories not updated in 7 days
|
||||
olderThan := time.Now().Add(-7 * 24 * time.Hour).UTC().Format(time.RFC3339)
|
||||
decayed, err := s.stores.Memories.DecayConfidence(ctx, olderThan, 0.9)
|
||||
if err != nil {
|
||||
log.Printf("⚠ memory compaction: decay failed: %v", err)
|
||||
} else if decayed > 0 {
|
||||
log.Printf("🧠 memory compaction: decayed %d memories", decayed)
|
||||
}
|
||||
|
||||
// Prune: archive memories with confidence below 0.15
|
||||
pruned, err := s.stores.Memories.Prune(ctx, 0.15)
|
||||
if err != nil {
|
||||
log.Printf("⚠ memory compaction: prune failed: %v", err)
|
||||
} else if pruned > 0 {
|
||||
log.Printf("🧠 memory compaction: pruned %d memories", pruned)
|
||||
}
|
||||
}
|
||||
|
||||
// findCandidates queries for channels with enough new activity since last extraction.
|
||||
|
||||
Reference in New Issue
Block a user