Module: Legion::Extensions::Agentic::Attention::Surprise::Runners::Surprise

Includes:
Helpers::Lex
Included in:
Client
Defined in:
lib/legion/extensions/agentic/attention/surprise/runners/surprise.rb

Instance Method Summary collapse

Instance Method Details

#domain_sensitivity(domain:) ⇒ Object



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/legion/extensions/agentic/attention/surprise/runners/surprise.rb', line 95

def domain_sensitivity(domain:, **)
  sensitivity = habituation_model.sensitivity_for(domain)
  baseline    = store.baseline_for(domain)
  domain_events = store.by_domain(domain)

  log.debug("[surprise] domain_sensitivity: domain=#{domain} sensitivity=#{sensitivity.round(3)}")

  {
    success:     true,
    domain:      domain,
    sensitivity: sensitivity.round(4),
    baseline:    baseline.round(4),
    event_count: domain_events.size
  }
end

#evaluate_surprise(domain:, predicted:, actual:, valence: :neutral) ⇒ Object

Evaluate a single prediction-outcome pair and compute surprise magnitude. magnitude = |predicted - actual| * sensitivity * valence_weight, clamped to [0,1]



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/legion/extensions/agentic/attention/surprise/runners/surprise.rb', line 15

def evaluate_surprise(domain:, predicted:, actual:, valence: :neutral, **)
  sensitivity = habituation_model.sensitivity_for(domain)
  valence_weight = Helpers::Constants::VALENCE_WEIGHTS.fetch(valence, 0.3)
  raw_diff       = (predicted.to_f - actual.to_f).abs
  magnitude      = (raw_diff * sensitivity * valence_weight).clamp(0.0, 1.0)

  threshold    = Helpers::Constants::SURPRISE_THRESHOLD
  orienting    = should_orient?(domain, magnitude, threshold)

  event = Helpers::SurpriseEvent.new(
    domain:    domain,
    predicted: predicted,
    actual:    actual,
    magnitude: magnitude,
    valence:   valence,
    orienting: orienting
  )

  store.record(event)
  habituation_model.habituate(domain)

  if orienting
    record_cooldown(domain)
    log.debug("[surprise] orienting response triggered: domain=#{domain} magnitude=#{magnitude.round(3)}")
  else
    log.debug("[surprise] surprise recorded: domain=#{domain} magnitude=#{magnitude.round(3)} orienting=false")
  end

  { success: true, surprise_event: event.to_h, orienting_triggered: orienting }
end

#recent_surprises(count: 10) ⇒ Object



111
112
113
114
115
# File 'lib/legion/extensions/agentic/attention/surprise/runners/surprise.rb', line 111

def recent_surprises(count: 10, **)
  events = store.recent(count)
  log.debug("[surprise] recent_surprises: count=#{events.size}")
  { success: true, events: events.map(&:to_h), count: events.size }
end

#reset_habituation(domain:) ⇒ Object



117
118
119
120
121
122
123
124
125
126
127
# File 'lib/legion/extensions/agentic/attention/surprise/runners/surprise.rb', line 117

def reset_habituation(domain:, **)
  old_sensitivity = habituation_model.sensitivity_for(domain)
  # Sensitize repeatedly to push back toward 1.0
  steps = ((1.0 - old_sensitivity) / Helpers::Constants::SENSITIZATION_RATE).ceil
  steps.times { habituation_model.sensitize(domain) }
  new_sensitivity = habituation_model.sensitivity_for(domain)

  log.debug("[surprise] reset_habituation: domain=#{domain} #{old_sensitivity.round(3)} -> #{new_sensitivity.round(3)}")

  { success: true, domain: domain, old_sensitivity: old_sensitivity.round(4), new_sensitivity: new_sensitivity.round(4) }
end

#surprise_statsObject



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/legion/extensions/agentic/attention/surprise/runners/surprise.rb', line 78

def surprise_stats(**)
  stats   = store.to_h
  top     = store.most_surprising(1).first
  avg_mag = stats[:average_magnitude]

  log.debug("[surprise] stats: total=#{stats[:total_events]} domains=#{stats[:domain_count]}")

  {
    success:                true,
    total_events:           stats[:total_events],
    domain_count:           stats[:domain_count],
    average_magnitude:      avg_mag,
    most_surprising_domain: stats[:most_surprising_domain],
    top_surprise_magnitude: top&.magnitude&.round(4)
  }
end

#update_surprise(tick_result: {}) ⇒ Object

Per-tick update: extract domain predictions from tick_result, compute surprise for each, decay the store’s baseline tracking, and return a summary.



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/legion/extensions/agentic/attention/surprise/runners/surprise.rb', line 48

def update_surprise(tick_result: {}, **)
  predictions = extract_predictions(tick_result)
  events      = []

  predictions.each do |pred|
    next unless pred[:domain] && !pred[:predicted].nil? && !pred[:actual].nil?

    result = evaluate_surprise(
      domain:    pred[:domain],
      predicted: pred[:predicted],
      actual:    pred[:actual],
      valence:   pred.fetch(:valence, :neutral)
    )
    events << result[:surprise_event] if result[:success]
  end

  habituation_model.decay_all
  tick_cooldowns

  orienting_count = events.count { |e| e[:orienting] }
  log.debug("[surprise] tick update: evaluated=#{events.size} orienting=#{orienting_count}")

  {
    success:         true,
    evaluated:       events.size,
    orienting_count: orienting_count,
    events:          events
  }
end