Class: ClaudeMemory::Sweep::Sweeper

Inherits:
Object
  • Object
show all
Defined in:
lib/claude_memory/sweep/sweeper.rb

Constant Summary collapse

DEFAULT_CONFIG =
{
  proposed_fact_ttl_days: 14,
  disputed_fact_ttl_days: 30,
  content_retention_days: 30,
  mcp_tool_call_retention_days: 90,
  default_budget_seconds: 5
}.freeze
ESCALATION_LEVELS =

Three-level escalation ensures sweep always makes progress. Source: lossless-claw three-level escalation pattern

%i[normal aggressive fallback].freeze

Instance Method Summary collapse

Constructor Details

#initialize(store, config: {}) ⇒ Sweeper

Returns a new instance of Sweeper.



18
19
20
21
22
23
# File 'lib/claude_memory/sweep/sweeper.rb', line 18

def initialize(store, config: {})
  @store = store
  @config = DEFAULT_CONFIG.merge(config)
  @start_time = nil
  @stats = nil
end

Instance Method Details

#run!(budget_seconds: nil) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/claude_memory/sweep/sweeper.rb', line 25

def run!(budget_seconds: nil)
  budget = budget_seconds || @config[:default_budget_seconds]
  @start_time = Time.now
  @stats = {
    proposed_facts_expired: 0,
    disputed_facts_expired: 0,
    orphaned_provenance_deleted: 0,
    old_content_pruned: 0,
    mcp_tool_calls_pruned: 0,
    escalation_level: :normal
  }

  maintenance = build_maintenance(:normal)

  run_if_within_budget { @stats[:proposed_facts_expired] = maintenance.expire_proposed_facts }
  run_if_within_budget { @stats[:disputed_facts_expired] = maintenance.expire_disputed_facts }
  run_if_within_budget { @stats[:orphaned_provenance_deleted] = maintenance.prune_orphaned_provenance }
  run_if_within_budget { @stats[:old_content_pruned] = maintenance.prune_old_content }
  run_if_within_budget { @stats[:mcp_tool_calls_pruned] = maintenance.prune_old_mcp_tool_calls }
  run_if_within_budget { @stats[:vec_backfilled] = maintenance.backfill_vec_index }
  run_if_within_budget { @stats[:vec_cleaned] = maintenance.cleanup_vec_expired }
  run_if_within_budget { @stats[:wal_checkpointed] = maintenance.checkpoint_wal }

  @stats[:elapsed_seconds] = Time.now - @start_time
  @stats[:budget_honored] = @stats[:elapsed_seconds] <= budget
  ClaudeMemory.logger.info("sweep",
    message: "Sweep complete",
    elapsed_seconds: @stats[:elapsed_seconds].round(3),
    budget_honored: @stats[:budget_honored],
    escalation_level: @stats[:escalation_level],
    proposed_expired: @stats[:proposed_facts_expired],
    disputed_expired: @stats[:disputed_facts_expired])
  @stats
end

#run_with_escalation!(budget_seconds: nil) ⇒ Object

Run sweep with escalation: if normal sweep makes no progress, escalate to aggressive (halved TTLs), then fallback (force-expire oldest). Returns stats hash with :escalation_level indicating which level succeeded.

Source: lossless-claw three-level escalation pattern



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/claude_memory/sweep/sweeper.rb', line 65

def run_with_escalation!(budget_seconds: nil)
  stats = run!(budget_seconds: budget_seconds)
  total = stats[:proposed_facts_expired] + stats[:disputed_facts_expired] +
    stats[:orphaned_provenance_deleted] + stats[:old_content_pruned]

  if total == 0
    # Escalate to aggressive — halved TTLs
    aggressive_maintenance = build_maintenance(:aggressive)
    added = 0
    added += aggressive_maintenance.expire_proposed_facts
    added += aggressive_maintenance.expire_disputed_facts
    added += aggressive_maintenance.prune_old_content

    if added > 0
      stats[:proposed_facts_expired] += added
      stats[:escalation_level] = :aggressive
    else
      # Fallback — force-expire oldest proposed/disputed facts
      fallback_count = force_expire_oldest
      stats[:proposed_facts_expired] += fallback_count
      stats[:escalation_level] = :fallback if fallback_count > 0
    end

    stats[:elapsed_seconds] = Time.now - @start_time
  end

  stats
end