Class: Legion::Extensions::Agentic::Affect::Interoception::Helpers::BodyBudget

Inherits:
Object
  • Object
show all
Includes:
Constants
Defined in:
lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb

Constant Summary

Constants included from Constants

Constants::BODY_BUDGET_LABELS, Constants::DEFAULT_BASELINE, Constants::DEVIATION_THRESHOLD, Constants::MARKER_DECAY, Constants::MARKER_FLOOR, Constants::MARKER_INFLUENCE, Constants::MARKER_NEGATIVE_THRESHOLD, Constants::MARKER_POSITIVE_THRESHOLD, Constants::MAX_MARKERS, Constants::MAX_VITAL_HISTORY, Constants::VITAL_ALPHA, Constants::VITAL_CHANNELS, Constants::VITAL_LABELS

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeBodyBudget

Returns a new instance of BodyBudget.



14
15
16
17
18
19
# File 'lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb', line 14

def initialize
  @vitals        = {}
  @baselines     = {}
  @markers       = []
  @vital_history = {}
end

Instance Attribute Details

#baselinesObject (readonly)

Returns the value of attribute baselines.



12
13
14
# File 'lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb', line 12

def baselines
  @baselines
end

#markersObject (readonly)

Returns the value of attribute markers.



12
13
14
# File 'lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb', line 12

def markers
  @markers
end

#vital_historyObject (readonly)

Returns the value of attribute vital_history.



12
13
14
# File 'lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb', line 12

def vital_history
  @vital_history
end

#vitalsObject (readonly)

Returns the value of attribute vitals.



12
13
14
# File 'lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb', line 12

def vitals
  @vitals
end

Instance Method Details

#bias_for_action(action:, domain: nil) ⇒ Object



82
83
84
85
86
87
# File 'lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb', line 82

def bias_for_action(action:, domain: nil)
  relevant = markers_for(action: action, domain: domain)
  return 0.0 if relevant.empty?

  relevant.sum { |m| m.bias_for(action) } / relevant.size
end

#body_budget_labelObject



107
108
109
110
111
# File 'lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb', line 107

def body_budget_label
  health = overall_health
  BODY_BUDGET_LABELS.each { |range, lbl| return lbl if range.cover?(health) }
  :comfortable
end

#channel_countObject



113
114
115
# File 'lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb', line 113

def channel_count
  @vitals.size
end

#create_marker(action:, domain:, valence:, strength: 1.0) ⇒ Object

— Somatic Markers —



69
70
71
72
73
74
# File 'lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb', line 69

def create_marker(action:, domain:, valence:, strength: 1.0)
  marker = SomaticMarker.new(action: action, domain: domain, valence: valence, strength: strength)
  @markers << marker
  prune_markers if @markers.size > MAX_MARKERS
  marker
end

#decay_markersObject



93
94
95
96
# File 'lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb', line 93

def decay_markers
  @markers.each(&:decay)
  @markers.reject!(&:faded?)
end

#deviating_channelsObject



62
63
64
65
# File 'lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb', line 62

def deviating_channels
  @vitals.select { |ch, _| deviation_for(ch).abs >= DEVIATION_THRESHOLD }
         .map { |ch, _| { channel: ch, deviation: deviation_for(ch).round(4), label: vital_label(ch) } }
end

#deviation_for(channel) ⇒ Object



43
44
45
46
47
48
# File 'lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb', line 43

def deviation_for(channel)
  channel = channel.to_sym
  current = @vitals.fetch(channel, DEFAULT_BASELINE)
  baseline = @baselines.fetch(channel, DEFAULT_BASELINE)
  current - baseline
end

#marker_countObject



117
118
119
# File 'lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb', line 117

def marker_count
  @markers.size
end

#markers_for(action:, domain: nil) ⇒ Object



76
77
78
79
80
# File 'lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb', line 76

def markers_for(action:, domain: nil)
  results = @markers.select { |m| m.action == action }
  results = results.select { |m| m.domain == domain } if domain
  results
end

#overall_healthObject

— Body Budget Overview —



100
101
102
103
104
105
# File 'lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb', line 100

def overall_health
  return DEFAULT_BASELINE if @vitals.empty?

  healths = @vitals.keys.map { |ch| vital_health(ch) }
  healths.sum / healths.size
end

#reinforce_markers(action:, domain: nil, amount: 0.1) ⇒ Object



89
90
91
# File 'lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb', line 89

def reinforce_markers(action:, domain: nil, amount: 0.1)
  markers_for(action: action, domain: domain).each { |m| m.reinforce(amount: amount) }
end

#report_vital(channel:, value:) ⇒ Object

— Vital Signal Tracking —



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb', line 23

def report_vital(channel:, value:)
  channel = channel.to_sym
  return nil unless VITAL_CHANNELS.include?(channel)

  normalized = value.clamp(0.0, 1.0)
  @baselines[channel] ||= DEFAULT_BASELINE
  @vitals[channel] = if @vitals.key?(channel)
                       ema(@vitals[channel], normalized, VITAL_ALPHA)
                     else
                       normalized
                     end
  record_vital_history(channel, @vitals[channel])
  @baselines[channel] = ema(@baselines[channel], @vitals[channel], VITAL_ALPHA * 0.5)
  @vitals[channel]
end

#to_hObject



121
122
123
124
125
126
127
128
129
130
# File 'lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb', line 121

def to_h
  {
    overall_health:    overall_health.round(4),
    body_budget_label: body_budget_label,
    channels:          channel_count,
    markers:           marker_count,
    vitals:            @vitals.transform_values { |v| v.round(4) },
    deviations:        deviating_channels
  }
end

#vital_for(channel) ⇒ Object



39
40
41
# File 'lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb', line 39

def vital_for(channel)
  @vitals.fetch(channel.to_sym, DEFAULT_BASELINE)
end

#vital_health(channel) ⇒ Object



56
57
58
59
60
# File 'lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb', line 56

def vital_health(channel)
  val = vital_for(channel)
  inverted_channels = %i[cpu_load memory_pressure queue_depth error_rate disk_usage gc_pressure]
  inverted_channels.include?(channel.to_sym) ? 1.0 - val : val
end

#vital_label(channel) ⇒ Object



50
51
52
53
54
# File 'lib/legion/extensions/agentic/affect/interoception/helpers/body_budget.rb', line 50

def vital_label(channel)
  health = vital_health(channel)
  VITAL_LABELS.each { |range, lbl| return lbl if range.cover?(health) }
  :nominal
end