Module: Legion::Gaia::BehavioralSynapse

Extended by:
Logging::Helper
Defined in:
lib/legion/gaia/behavioral_synapse.rb

Overview

rubocop:disable Metrics/ModuleLength

Defined Under Namespace

Modules: Math Classes: Tracker

Constant Summary collapse

TO_APOLLO_TAGS =
%w[self-knowledge behavior].freeze

Class Method Summary collapse

Class Method Details

.adjust(confidence, event, consecutive_successes: 0) ⇒ Object



72
73
74
75
76
77
78
79
# File 'lib/legion/gaia/behavioral_synapse.rb', line 72

def adjust(confidence, event, consecutive_successes: 0)
  if defined?(Legion::Extensions::Synapse::Helpers::Confidence)
    Legion::Extensions::Synapse::Helpers::Confidence.adjust(confidence, event,
                                                            consecutive_successes: consecutive_successes)
  else
    Math.adjust(confidence, event, consecutive_successes: consecutive_successes)
  end
end

.all_for(identity:) ⇒ Object



169
170
171
172
# File 'lib/legion/gaia/behavioral_synapse.rb', line 169

def all_for(identity:)
  prefix = "#{identity}:"
  @store.select { |key, _| key.start_with?(prefix) }.values
end

.apply_lazy_decay(entry) ⇒ Object



244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/legion/gaia/behavioral_synapse.rb', line 244

def apply_lazy_decay(entry)
  return entry unless entry[:last_reinforced_at]

  hours = (Time.now.utc - entry[:last_reinforced_at]) / 3600.0
  intensity = entry[:emotional_intensity].to_f
  effective_hours = hours / (1 + (intensity * e_weight))
  return entry if effective_hours < 0.001

  new_conf = decay_confidence(entry[:confidence], hours: effective_hours)
  @mutex.synchronize do
    entry[:confidence]         = new_conf
    entry[:last_reinforced_at] = Time.now.utc
    @dirty = true
  end
  entry
end

.apply_pain(entry) ⇒ Object



261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/legion/gaia/behavioral_synapse.rb', line 261

def apply_pain(entry)
  entry[:status]                = 'dampened'
  entry[:confidence]            = 0.29
  entry[:consecutive_failures]  = 0
  entry[:consecutive_successes] = 0
  log.warn("[gaia] synapse pain id=#{entry[:id]} domain=#{entry[:domain]} identity=#{entry[:identity]}")

  if defined?(Legion::Events) && Legion::Events.respond_to?(:emit)
    Legion::Events.emit('gaia.behavior.reverted',
                        id: entry[:id], identity: entry[:identity], domain: entry[:domain])
  end
rescue StandardError => e
  handle_exception(e, level: :warn, operation: 'gaia.behavioral_synapse.apply_pain', id: entry[:id])
end

.crystallize(identity:, domain:, directive:, evidence_trace_ids: [], origin: 'emergent') ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/legion/gaia/behavioral_synapse.rb', line 107

def crystallize(identity:, domain:, directive:, evidence_trace_ids: [], origin: 'emergent')
  key = store_key(identity, domain)
  @mutex.synchronize do
    return @store[key] if @store[key]

    conf   = starting_score(origin.to_sym)
    now    = Time.now.utc
    entry  = {
      id: SecureRandom.uuid,
      identity: identity.to_s,
      domain: domain.to_s,
      origin: origin.to_s,
      confidence: conf,
      emotional_valence: 0.0,
      emotional_intensity: 0.0,
      consecutive_failures: 0,
      consecutive_successes: 0,
      directive: directive,
      evidence_trace_ids: Array(evidence_trace_ids),
      status: 'active',
      last_applied_at: nil,
      last_reinforced_at: now,
      created_at: now
    }
    @store[key] = entry
    @dirty = true
    log.info("[gaia] synapse crystallized identity=#{identity} domain=#{domain} origin=#{origin} conf=#{conf}")
    entry
  end
end

.decay_confidence(confidence, hours: 1) ⇒ Object



81
82
83
84
85
86
87
# File 'lib/legion/gaia/behavioral_synapse.rb', line 81

def decay_confidence(confidence, hours: 1)
  if defined?(Legion::Extensions::Synapse::Helpers::Confidence)
    Legion::Extensions::Synapse::Helpers::Confidence.decay(confidence, hours: hours)
  else
    Math.decay(confidence, hours: hours)
  end
end

.dirty?Boolean

--- TrackerPersistence support ---

Returns:

  • (Boolean)


196
197
198
# File 'lib/legion/gaia/behavioral_synapse.rb', line 196

def dirty?
  @dirty == true
end

.e_weightObject



89
90
91
92
93
94
95
# File 'lib/legion/gaia/behavioral_synapse.rb', line 89

def e_weight
  if defined?(Legion::Extensions::Synapse::Helpers::Confidence)
    Legion::Extensions::Synapse::Helpers::Confidence::E_WEIGHT
  else
    Math::E_WEIGHT
  end
end

.erase_partner!(identity:) ⇒ Object



174
175
176
177
178
179
180
181
# File 'lib/legion/gaia/behavioral_synapse.rb', line 174

def erase_partner!(identity:)
  prefix = "#{identity}:"
  keys = @store.keys.select { |key| key.start_with?(prefix) }
  keys.each { |key| @store.delete(key) }
  @mutex.synchronize { @dirty = true } unless keys.empty?
  log.info("[gaia] BehavioralSynapse erased identity=#{identity} count=#{keys.size}")
  keys.size
end

.find_by_id(id) ⇒ Object



240
241
242
# File 'lib/legion/gaia/behavioral_synapse.rb', line 240

def find_by_id(id)
  @store.values.find { |entry| entry[:id] == id }
end

.for(identity:, domain:) ⇒ Object

--- Public API ---



99
100
101
102
103
104
105
# File 'lib/legion/gaia/behavioral_synapse.rb', line 99

def for(identity:, domain:)
  key = store_key(identity, domain)
  entry = @store[key]
  return nil unless entry

  apply_lazy_decay(entry)
end

.from_apollo(store: nil) ⇒ Object



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/legion/gaia/behavioral_synapse.rb', line 216

def from_apollo(store: nil)
  return unless store

  result = store.query(text: 'behavior', tags: TO_APOLLO_TAGS)
  return unless result.is_a?(Hash) && result[:success]

  count = 0
  result[:results]&.each do |entry|
    hydrate_apollo_entry(entry)
    count += 1
  rescue StandardError => e
    log.debug("[gaia] BehavioralSynapse skipped unparseable Apollo entry: #{e.message}")
  end
  log.info("[gaia] BehavioralSynapse hydrated from Apollo count=#{count}")
rescue StandardError => e
  handle_exception(e, level: :warn, operation: 'gaia.behavioral_synapse.from_apollo')
end

.hydrate_apollo_entry(raw_entry) ⇒ Object



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/legion/gaia/behavioral_synapse.rb', line 276

def hydrate_apollo_entry(raw_entry)
  return unless raw_entry[:content].to_s.start_with?('{')

  parsed = Legion::JSON.load(raw_entry[:content])
  return unless parsed[:id] && parsed[:identity] && parsed[:domain]

  key = store_key(parsed[:identity], parsed[:domain])
  @mutex.synchronize do
    @store[key] = {
      id: parsed[:id],
      identity: parsed[:identity].to_s,
      domain: parsed[:domain].to_s,
      origin: parsed[:origin].to_s,
      confidence: parsed[:confidence].to_f,
      emotional_valence: parsed[:emotional_valence].to_f,
      emotional_intensity: parsed[:emotional_intensity].to_f,
      consecutive_failures: parsed[:consecutive_failures].to_i,
      consecutive_successes: parsed[:consecutive_successes].to_i,
      directive: parsed[:directive],
      evidence_trace_ids: Array(parsed[:evidence_trace_ids]),
      status: parsed[:status].to_s,
      last_applied_at: parsed[:last_applied_at],
      last_reinforced_at: parse_time(parsed[:last_reinforced_at]),
      created_at: parse_time(parsed[:created_at])
    }
  end
end

.mark_clean!Object



200
201
202
# File 'lib/legion/gaia/behavioral_synapse.rb', line 200

def mark_clean!
  @dirty = false
end

.parse_time(value) ⇒ Object



304
305
306
307
308
309
310
311
# File 'lib/legion/gaia/behavioral_synapse.rb', line 304

def parse_time(value)
  return value if value.is_a?(Time)
  return nil if value.nil?

  Time.parse(value.to_s)
rescue ArgumentError, TypeError
  nil
end

.record_outcome(id:, outcome:, multiplier: 1.0) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/legion/gaia/behavioral_synapse.rb', line 138

def record_outcome(id:, outcome:, multiplier: 1.0)
  @mutex.synchronize do
    entry = find_by_id(id)
    return { found: false } unless entry

    conf = entry[:confidence]
    base_event = outcome.to_sym
    new_conf = adjust(conf, base_event, consecutive_successes: entry[:consecutive_successes])

    # Apply multiplier to delta (scale the delta, not the raw confidence)
    raw_delta = new_conf - conf
    scaled_conf = (conf + (raw_delta * multiplier)).clamp(0.0, 1.0)

    if base_event == :success
      entry[:consecutive_successes] = entry[:consecutive_successes].to_i + 1
      entry[:consecutive_failures]  = 0
    elsif base_event == :failure
      entry[:consecutive_failures]  = entry[:consecutive_failures].to_i + 1
      entry[:consecutive_successes] = 0
    end

    entry[:confidence]          = scaled_conf
    entry[:last_reinforced_at]  = Time.now.utc
    @dirty = true

    apply_pain(entry) if entry[:consecutive_failures].to_i >= 3

    entry
  end
end

.reset!Object



187
188
189
190
191
192
# File 'lib/legion/gaia/behavioral_synapse.rb', line 187

def reset!
  @mutex.synchronize do
    @store = Concurrent::Hash.new
    @dirty = false
  end
end

.starting_score(origin) ⇒ Object

--- Confidence math delegation ---



64
65
66
67
68
69
70
# File 'lib/legion/gaia/behavioral_synapse.rb', line 64

def starting_score(origin)
  if defined?(Legion::Extensions::Synapse::Helpers::Confidence)
    Legion::Extensions::Synapse::Helpers::Confidence.starting_score(origin)
  else
    Math.starting_score(origin)
  end
end

.storeObject



183
184
185
# File 'lib/legion/gaia/behavioral_synapse.rb', line 183

def store
  @store
end

.store_key(identity, domain) ⇒ Object



236
237
238
# File 'lib/legion/gaia/behavioral_synapse.rb', line 236

def store_key(identity, domain)
  "#{identity}:#{domain}"
end

.to_apollo_entriesObject



204
205
206
207
208
209
210
211
212
213
214
# File 'lib/legion/gaia/behavioral_synapse.rb', line 204

def to_apollo_entries
  @store.values.map do |entry|
    identity = entry[:identity]
    {
      content: Legion::JSON.dump(entry),
      tags: TO_APOLLO_TAGS + ["partner:#{identity}"],
      confidence: entry[:confidence] || 0.3,
      access_scope: 'local'
    }
  end
end