Class: Silas::Memory

Inherits:
ApplicationRecord show all
Defined in:
app/models/silas/memory.rb

Overview

One remembered fact. Triple-ish (subject · attribute · content) with provenance (which turn wrote it) and supersession: a new fact about the same (agent, scope, subject, attribute) retires the old one — temporal versioning without a temporal store. Edges between memories are a deliberate not-yet: add them when a real agent needs multi-hop.

Constant Summary collapse

SCOPES =
%w[agent app].freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.recall(agent_name:, subjects: [], limit: 10) ⇒ Object

What an agent can see: its own memories + app-shared ones. Subject-matched first (when subjects given), then most recent.



34
35
36
37
38
39
40
41
42
43
44
# File 'app/models/silas/memory.rb', line 34

def self.recall(agent_name:, subjects: [], limit: 10)
  visible = active.where("agent_name = :a OR scope = 'app'", a: agent_name)
  if subjects.any?
    keys = subjects.map { |s| s.to_s.strip.downcase }
    matched = visible.where(subject: keys).order(created_at: :desc).limit(limit).to_a
    rest = visible.where.not(subject: keys).order(created_at: :desc).limit(limit - matched.size)
    matched + rest
  else
    visible.order(created_at: :desc).limit(limit).to_a
  end
end

.remember!(agent_name:, subject:, content:, attribute: nil, scope: "agent", turn: nil) ⇒ Object

Write-through with supersession. attribute nil = free-form note about the subject (accumulates); attribute present = the triple's slot (supersedes).



18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'app/models/silas/memory.rb', line 18

def self.remember!(agent_name:, subject:, content:, attribute: nil, scope: "agent", turn: nil)
  transaction do
    record = create!(agent_name:, scope:, subject: subject.to_s.strip.downcase,
                     attribute_name: attribute.presence&.strip&.downcase, content:,
                     session_id: turn&.session_id, turn_id: turn&.id)
    if record.attribute_name
      active.where(agent_name:, scope:, subject: record.subject, attribute_name: record.attribute_name)
            .where.not(id: record.id)
            .update_all(status: "superseded", superseded_by_id: record.id, updated_at: Time.current)
    end
    record
  end
end

Instance Method Details

#to_lineObject



46
47
48
49
# File 'app/models/silas/memory.rb', line 46

def to_line
  head = attribute_name ? "#{subject} · #{attribute_name}: " : "#{subject}: "
  head + content
end