Class: Llmemory::ShortTerm::SessionLifecycle

Inherits:
Object
  • Object
show all
Defined in:
lib/llmemory/short_term/session_lifecycle.rb

Constant Summary collapse

PSEUDO_SESSION_PATTERNS =

Pseudo-sessions used by ForgetLog, FeedbackStore and WorkingMemory share the short-term K/V store but are not user sessions — they must not be idle-pruned, stale-pruned, or evicted by enforce_max_entries.

[
  /\A__[a-z_]+__\z/,    # e.g. "__forget_log__", "__retrieval_feedback__"
  /:working_memory\z/    # WorkingMemory uses "<session>:working_memory"
].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(store: nil) ⇒ SessionLifecycle

Returns a new instance of SessionLifecycle.



20
21
22
# File 'lib/llmemory/short_term/session_lifecycle.rb', line 20

def initialize(store: nil)
  @store = store || build_store
end

Class Method Details

.pseudo_session?(session_id) ⇒ Boolean

Returns:

  • (Boolean)


16
17
18
# File 'lib/llmemory/short_term/session_lifecycle.rb', line 16

def self.pseudo_session?(session_id)
  PSEUDO_SESSION_PATTERNS.any? { |p| session_id.to_s.match?(p) }
end

Instance Method Details

#cleanup_idle_sessions!(user_id:, idle_minutes: nil) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/llmemory/short_term/session_lifecycle.rb', line 24

def cleanup_idle_sessions!(user_id:, idle_minutes: nil)
  idle_minutes ||= Llmemory.configuration.session_idle_minutes
  cutoff = Time.now - (idle_minutes * 60)
  deleted = 0

  user_sessions(user_id).each do |session_id|
    state = @store.load(user_id, session_id)
    next unless state.is_a?(Hash)

    last_activity = state[:last_activity_at] || state["last_activity_at"]
    next if last_activity.nil?

    last_time = last_activity.is_a?(Time) ? last_activity : Time.parse(last_activity.to_s)
    if last_time < cutoff
      @store.delete(user_id, session_id)
      deleted += 1
    end
  end

  deleted
end

#cleanup_stale_sessions!(user_id:, prune_after_days: nil) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/llmemory/short_term/session_lifecycle.rb', line 46

def cleanup_stale_sessions!(user_id:, prune_after_days: nil)
  prune_after_days ||= Llmemory.configuration.session_prune_after_days
  cutoff = Time.now - (prune_after_days * 86400)
  deleted = 0

  user_sessions(user_id).each do |session_id|
    state = @store.load(user_id, session_id)
    next unless state.is_a?(Hash)

    last_activity = state[:last_activity_at] || state["last_activity_at"]
    next if last_activity.nil?

    last_time = last_activity.is_a?(Time) ? last_activity : Time.parse(last_activity.to_s)
    if last_time < cutoff
      @store.delete(user_id, session_id)
      deleted += 1
    end
  end

  deleted
end

#enforce_max_entries!(user_id:, max_entries: nil) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/llmemory/short_term/session_lifecycle.rb', line 68

def enforce_max_entries!(user_id:, max_entries: nil)
  max_entries ||= Llmemory.configuration.session_max_entries_per_user
  sessions = user_sessions(user_id)
  return 0 if sessions.size <= max_entries

  session_ages = sessions.map do |session_id|
    state = @store.load(user_id, session_id)
    last_activity = state&.dig(:last_activity_at) || state&.dig("last_activity_at")
    last_time = last_activity.is_a?(Time) ? last_activity : (last_activity ? Time.parse(last_activity.to_s) : Time.at(0))
    [session_id, last_time]
  end

  session_ages.sort_by! { |_, t| t }
  to_delete = session_ages.first(session_ages.size - max_entries).map(&:first)
  to_delete.each { |sid| @store.delete(user_id, sid) }
  to_delete.size
end