Module: Legion::Extensions::Agentic::Memory::Trace::Helpers::Trace

Defined in:
lib/legion/extensions/agentic/memory/trace/helpers/trace.rb

Constant Summary collapse

TRACE_TYPES =
%i[firmware identity procedural trust semantic episodic sensory].freeze
ORIGINS =
%i[firmware direct_experience mesh_transfer imprint].freeze
STORAGE_TIERS =
%i[hot warm cold erased].freeze
BASE_DECAY_RATES =
{
  firmware:   0.000,
  identity:   0.001,
  procedural: 0.005,
  trust:      0.008,
  semantic:   0.010,
  episodic:   0.020,
  sensory:    0.100
}.freeze
STARTING_STRENGTHS =
{
  firmware:   1.000,
  identity:   1.000,
  procedural: 0.400,
  trust:      0.300,
  semantic:   0.500,
  episodic:   0.600,
  sensory:    0.400
}.freeze
E_WEIGHT =

Tuning constants from spec Section 4.5

0.3
R_AMOUNT =

emotional intensity weight on decay

0.10
IMPRINT_MULTIPLIER =

base reinforcement amount

3.0
AUTO_FIRE_THRESHOLD =

reinforcement boost during imprint window

0.85
ARCHIVE_THRESHOLD =

procedural auto-fire strength threshold

0.05
PRUNE_THRESHOLD =

below this, trace moves to cold storage

0.01
HOT_TIER_WINDOW =

below this, trace eligible for removal

86_400
WARM_TIER_WINDOW =

24 hours in seconds

7_776_000
RETRIEVAL_RECENCY_HALF =

90 days in seconds

3600
ASSOCIATION_BONUS =

half-life for recency scoring (1 hour)

0.15
MAX_ASSOCIATIONS =

bonus for Hebbian-associated traces

20
COACTIVATION_THRESHOLD =

max Hebbian links per trace

3
VALENCE_SCALAR_KEYS =

co-activations before Hebbian link forms

%i[valence emotional_valence sentiment polarity score].freeze

Class Method Summary collapse

Class Method Details

.default_partition_idObject



122
123
124
125
126
# File 'lib/legion/extensions/agentic/memory/trace/helpers/trace.rb', line 122

def default_partition_id
  Legion::Settings.dig(:agent, :id) || 'default'
rescue StandardError => _e
  'default'
end

.new_trace(type:, content_payload:, content_embedding: nil, emotional_valence: 0.0, emotional_intensity: 0.0, domain_tags: [], origin: :direct_experience, source_agent_id: nil, partition_id: nil, imprint_active: false, unresolved: false, consolidation_candidate: false, confidence: nil) ⇒ Object

rubocop:disable Metrics/ParameterLists

Raises:

  • (ArgumentError)


56
57
58
59
60
61
62
63
64
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
93
94
95
96
# File 'lib/legion/extensions/agentic/memory/trace/helpers/trace.rb', line 56

def new_trace(type:, content_payload:, content_embedding: nil, emotional_valence: 0.0, # rubocop:disable Metrics/ParameterLists
              emotional_intensity: 0.0, domain_tags: [], origin: :direct_experience,
              source_agent_id: nil, partition_id: nil, imprint_active: false,
              unresolved: false, consolidation_candidate: false, confidence: nil, **)
  raise ArgumentError, "invalid trace type: #{type}" unless TRACE_TYPES.include?(type)
  raise ArgumentError, "invalid origin: #{origin}" unless ORIGINS.include?(origin)

  now = Time.now.utc
  emotional_context = normalize_trace_affect(
    emotional_valence:   emotional_valence,
    emotional_intensity: emotional_intensity
  )

  {
    trace_id:                SecureRandom.uuid,
    trace_type:              type,
    content_embedding:       content_embedding,
    content_payload:         content_payload,
    strength:                STARTING_STRENGTHS[type],
    peak_strength:           STARTING_STRENGTHS[type],
    base_decay_rate:         BASE_DECAY_RATES[type],
    emotional_valence:       emotional_context[:emotional_valence],
    emotional_intensity:     emotional_context[:emotional_intensity],
    domain_tags:             Array(domain_tags),
    origin:                  origin,
    source_agent_id:         source_agent_id,
    created_at:              now,
    last_reinforced:         now,
    last_decayed:            now,
    reinforcement_count:     imprint_active ? 1 : 0,
    confidence:              confidence || (type == :firmware ? 1.0 : 0.5),
    storage_tier:            :hot,
    partition_id:            partition_id || default_partition_id,
    encryption_key_id:       nil,
    associated_traces:       [],
    parent_trace_id:         nil,
    child_trace_ids:         [],
    unresolved:              unresolved,
    consolidation_candidate: consolidation_candidate
  }
end

.normalize_emotional_intensity(value) ⇒ Object



118
119
120
# File 'lib/legion/extensions/agentic/memory/trace/helpers/trace.rb', line 118

def normalize_emotional_intensity(value)
  normalize_scalar(value, min: 0.0, max: 1.0)
end

.normalize_emotional_valence(value) ⇒ Object



114
115
116
# File 'lib/legion/extensions/agentic/memory/trace/helpers/trace.rb', line 114

def normalize_emotional_valence(value)
  normalize_scalar(value, min: -1.0, max: 1.0, hash_keys: VALENCE_SCALAR_KEYS)
end

.normalize_hash_scalar(value, min:, max:, hash_keys:) ⇒ Object



154
155
156
157
158
159
160
161
162
163
# File 'lib/legion/extensions/agentic/memory/trace/helpers/trace.rb', line 154

def normalize_hash_scalar(value, min:, max:, hash_keys:)
  symbolized = symbolize_keys(value)
  scalar_value = hash_keys.lazy.map { |key| symbolized[key] }.find { |candidate| scalar_candidate?(candidate) }
  return normalize_scalar(scalar_value, min: min, max: max, hash_keys: hash_keys) if scalar_candidate?(scalar_value)

  numeric_values = symbolized.values.select { |candidate| scalar_candidate?(candidate) }
  return normalize_scalar(numeric_values.first, min: min, max: max, hash_keys: hash_keys) if numeric_values.one?

  0.0
end

.normalize_scalar(value, min:, max:, hash_keys: []) ⇒ Object



128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/legion/extensions/agentic/memory/trace/helpers/trace.rb', line 128

def normalize_scalar(value, min:, max:, hash_keys: [])
  case value
  when Numeric
    value.to_f.clamp(min, max)
  when String
    normalize_string_scalar(value, min: min, max: max, hash_keys: hash_keys)
  when Hash
    normalize_hash_scalar(value, min: min, max: max, hash_keys: hash_keys)
  else
    0.0
  end
end

.normalize_string_scalar(value, min:, max:, hash_keys:) ⇒ Object



141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/legion/extensions/agentic/memory/trace/helpers/trace.rb', line 141

def normalize_string_scalar(value, min:, max:, hash_keys:)
  stripped = value.strip
  return 0.0 if stripped.empty?

  Float(stripped).clamp(min, max)
rescue ArgumentError, TypeError => e
  Legion::Logging.debug("[memory][trace] normalize_string_scalar fallback: #{e.message}")
  parsed = parse_structured_scalar(stripped)
  return normalize_scalar(parsed, min: min, max: max, hash_keys: hash_keys) if parsed

  0.0
end

.normalize_trace_affect(trace) ⇒ Object



107
108
109
110
111
112
# File 'lib/legion/extensions/agentic/memory/trace/helpers/trace.rb', line 107

def normalize_trace_affect(trace)
  normalized = trace.dup
  normalized[:emotional_valence] = normalize_emotional_valence(normalized[:emotional_valence])
  normalized[:emotional_intensity] = normalize_emotional_intensity(normalized[:emotional_intensity])
  normalized
end

.parse_structured_scalar(value) ⇒ Object



165
166
167
168
169
170
171
172
# File 'lib/legion/extensions/agentic/memory/trace/helpers/trace.rb', line 165

def parse_structured_scalar(value)
  return unless value.start_with?('{', '[')

  ::JSON.parse(value)
rescue ::JSON::ParserError => e
  Legion::Logging.debug("[memory][trace] parse_structured_scalar ignored: #{e.message}")
  nil
end

.scalar_candidate?(value) ⇒ Boolean

Returns:

  • (Boolean)


174
175
176
# File 'lib/legion/extensions/agentic/memory/trace/helpers/trace.rb', line 174

def scalar_candidate?(value)
  value.is_a?(Numeric) || value.is_a?(String)
end

.symbolize_keys(value) ⇒ Object



178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/legion/extensions/agentic/memory/trace/helpers/trace.rb', line 178

def symbolize_keys(value)
  case value
  when Hash
    value.each_with_object({}) do |(key, nested), memo|
      memo[key.to_sym] = symbolize_keys(nested)
    end
  when Array
    value.map { |nested| symbolize_keys(nested) }
  else
    value
  end
end

.valid_trace?(trace) ⇒ Boolean

Returns:

  • (Boolean)


98
99
100
101
102
103
104
105
# File 'lib/legion/extensions/agentic/memory/trace/helpers/trace.rb', line 98

def valid_trace?(trace)
  return false unless trace.is_a?(Hash)
  return false unless TRACE_TYPES.include?(trace[:trace_type])
  return false unless trace[:strength].is_a?(Numeric)
  return false unless trace[:strength].between?(0.0, 1.0)

  true
end