Class: Llmemory::LongTerm::Episodic::Memory

Inherits:
Object
  • Object
show all
Includes:
MemoryModule
Defined in:
lib/llmemory/long_term/episodic/memory.rb

Overview

Episodic long-term memory: records agent trajectories and retrieves them by recency, importance and relevance. Designed to coexist with semantic memory (file/graph), not replace it, and to feed reflection (P2), which distills episodes into semantic knowledge.

Deliberately LLM-free: recording and retrieval are deterministic. Higher order summarization belongs to reflection.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from MemoryModule

#forget_log, #read

Constructor Details

#initialize(user_id:, storage: nil) ⇒ Memory

Returns a new instance of Memory.



22
23
24
25
# File 'lib/llmemory/long_term/episodic/memory.rb', line 22

def initialize(user_id:, storage: nil)
  @user_id = user_id
  @storage = storage || Storages.build
end

Instance Attribute Details

#storageObject (readonly)

Returns the value of attribute storage.



20
21
22
# File 'lib/llmemory/long_term/episodic/memory.rb', line 20

def storage
  @storage
end

#user_idObject (readonly)

Returns the value of attribute user_id.



20
21
22
# File 'lib/llmemory/long_term/episodic/memory.rb', line 20

def user_id
  @user_id
end

Instance Method Details

#countObject



58
59
60
# File 'lib/llmemory/long_term/episodic/memory.rb', line 58

def count
  @storage.count_episodes(@user_id)
end

#episodes(limit: nil) ⇒ Object



49
50
51
# File 'lib/llmemory/long_term/episodic/memory.rb', line 49

def episodes(limit: nil)
  @storage.list_episodes(@user_id, limit: limit).map { |e| Episode.from_h(e) }
end

#find_episode(id) ⇒ Object



53
54
55
56
# File 'lib/llmemory/long_term/episodic/memory.rb', line 53

def find_episode(id)
  raw = @storage.get_episode(@user_id, id)
  raw && Episode.from_h(raw)
end

#forget(ids:, reason: nil) ⇒ Object



97
98
99
100
101
102
103
104
# File 'lib/llmemory/long_term/episodic/memory.rb', line 97

def forget(ids:, reason: nil)
  requested = Array(ids).map(&:to_s)
  existing = @storage.list_episodes(@user_id).map { |e| (e[:id] || e["id"]).to_s }
  removed = requested & existing
  @storage.delete_episodes(@user_id, removed)
  forget_log.record(@user_id, memory_type: "episodic", ids: removed, reason: reason)
  removed.size
end

#list(user_id: nil, limit: nil) ⇒ Object



89
90
91
# File 'lib/llmemory/long_term/episodic/memory.rb', line 89

def list(user_id: nil, limit: nil)
  episodes(limit: limit)
end

#recent_episodes(limit: 10) ⇒ Object



45
46
47
# File 'lib/llmemory/long_term/episodic/memory.rb', line 45

def recent_episodes(limit: 10)
  @storage.list_episodes(@user_id, limit: limit).map { |e| Episode.from_h(e) }
end

#record_episode(steps:, summary: nil, outcome: nil, importance: 0.5) ⇒ Object

Records a trajectory. ‘steps` is an array of hashes with any of :observation, :action, :result, :timestamp. Returns the episode id.



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/llmemory/long_term/episodic/memory.rb', line 29

def record_episode(steps:, summary: nil, outcome: nil, importance: 0.5)
  episode = Episode.new(
    id: nil,
    user_id: @user_id,
    steps: steps,
    summary: summary || derive_summary(steps),
    outcome: outcome,
    importance: importance
  )
  provenance = Llmemory::Provenance.from_text_fingerprint(
    episode.searchable_text, method: "episode_recording", confidence: episode.importance
  )
  record = episode.to_h.merge(provenance: provenance)
  @storage.save_episode(@user_id, record)
end

#search_candidates(query, user_id: nil, top_k: 20) ⇒ Object

Retrieval Engine integration. Returns candidates shaped like the other long-term memories so the Engine can rank episodes by relevance, recency (temporal decay) and importance (P3), with provenance (P10).



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/llmemory/long_term/episodic/memory.rb', line 65

def search_candidates(query, user_id: nil, top_k: 20)
  uid = user_id || @user_id
  return [] unless uid == @user_id

  @storage.search_episodes(uid, query).first(top_k).map do |e|
    episode = Episode.from_h(e)
    {
      id: episode.id,
      text: episode.summary.to_s.empty? ? episode.searchable_text : episode.summary,
      timestamp: episode.created_at,
      score: 1.0,
      importance: episode.importance,
      evergreen: false,
      provenance: e[:provenance] || e["provenance"]
    }
  end
end

#stats(user_id: nil) ⇒ Object



93
94
95
# File 'lib/llmemory/long_term/episodic/memory.rb', line 93

def stats(user_id: nil)
  { episodes: count }
end

#write(steps:, summary: nil, outcome: nil, importance: 0.5, **_meta) ⇒ Object

— MemoryModule uniform interface —



85
86
87
# File 'lib/llmemory/long_term/episodic/memory.rb', line 85

def write(steps:, summary: nil, outcome: nil, importance: 0.5, **_meta)
  record_episode(steps: steps, summary: summary, outcome: outcome, importance: importance)
end