Module: Llmemory::Provenance

Defined in:
lib/llmemory/provenance.rb

Overview

Provenance records the lineage of a long-term memory item: where it came from, how it was produced, and with what confidence. It is stored as a plain JSON-safe Hash so it round-trips through every storage backend (in-memory, JSON files, SQL columns, jsonb properties) without coupling.

Shape: { sources: [{ type:, id: }], method:, confidence:, created_at: }

‘method` identifies the producing process (e.g. “fact_extraction”, “entity_relation_extraction”, and in the future “reflection”), so a semantic datum can always be traced back to its raw source.

Class Method Summary collapse

Class Method Details

.build(method:, sources: [], confidence: nil, created_at: nil) ⇒ Object



19
20
21
22
23
24
25
26
# File 'lib/llmemory/provenance.rb', line 19

def build(method:, sources: [], confidence: nil, created_at: nil)
  {
    sources: Array(sources).filter_map { |s| normalize_source(s) },
    method: method&.to_s,
    confidence: confidence.nil? ? nil : confidence.to_f,
    created_at: normalize_time(created_at)
  }
end

.from_resource(resource_id, method:, confidence: nil, created_at: nil) ⇒ Object

Convenience for the file-based path, where the raw text is persisted as a Resource and referenced by id.



30
31
32
33
# File 'lib/llmemory/provenance.rb', line 30

def from_resource(resource_id, method:, confidence: nil, created_at: nil)
  sources = resource_id ? [{ type: "resource", id: resource_id }] : []
  build(method: method, sources: sources, confidence: confidence, created_at: created_at)
end

.from_text_fingerprint(text, method:, confidence: nil, created_at: nil) ⇒ Object

Convenience for the graph-based path, which does not persist the raw text. We record a stable fingerprint of the source instead of the document itself, keeping lineage verifiable without exposing sensitive content.



38
39
40
41
42
43
44
45
# File 'lib/llmemory/provenance.rb', line 38

def from_text_fingerprint(text, method:, confidence: nil, created_at: nil)
  require "digest"
  sources = []
  unless text.to_s.strip.empty?
    sources = [{ type: "text_sha256", id: Digest::SHA256.hexdigest(text.to_s)[0, 16] }]
  end
  build(method: method, sources: sources, confidence: confidence, created_at: created_at)
end

.normalize_source(source) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
# File 'lib/llmemory/provenance.rb', line 47

def normalize_source(source)
  return nil if source.nil?
  if source.is_a?(Hash)
    type = source[:type] || source["type"]
    id = source[:id] || source["id"]
    return nil if id.nil?
    { type: type.nil? ? "unknown" : type.to_s, id: id }
  else
    { type: "unknown", id: source }
  end
end

.normalize_time(value) ⇒ Object



59
60
61
62
# File 'lib/llmemory/provenance.rb', line 59

def normalize_time(value)
  value ||= Time.now
  value.respond_to?(:iso8601) ? value.iso8601 : value.to_s
end