lex-agentic-self 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c660e26600da2bf1c6e0ec3ff8c8d81a1eabebc3859d3de838e30f22b4ba91d
4
- data.tar.gz: ec3d26a5dd95ad077de1880fd60f48d23ff0a34aa841f82cda7bcbfdad99e5d0
3
+ metadata.gz: 478de6acedf6c72ba3a5c715bc57f3124505486720b473b2db536d4abe48c58c
4
+ data.tar.gz: 8846a168b7d882d7e981a3d87eb87565e25b10ac24530221748382fdda1128cb
5
5
  SHA512:
6
- metadata.gz: ffbe4a9cee92f015c0d167fa98327aceb9641c8dd448d99f0649ff42e87eb531b51eec961729f615e274c2854422cdb0b3e2cc77d02f456b9ceeb944a6088ebb
7
- data.tar.gz: f418467e5090f41939297b8907288f4ef29b95fbc9def510bbc1efb503abd79cdf8a3420b7ec2ce707d5d29d06e0569531da88395658a3901b80a8d7356b53b9
6
+ metadata.gz: 20df44e864e415b127e1f26a0d28bb0ed19c6589b53a40afbace9587c1166be962b6b8305e74c7ad4d4ea6b41eb9091f90328cbc3e23c3ab44ab9172cced706f
7
+ data.tar.gz: c768c0cc1794a6502e37d2c6524feaec104c8d47116e654f7d5dd19330768130212516cc074011b97b4e8e97f21c33931242e4cebf9ce0fba92064919bf7396d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.9] - 2026-03-31
4
+
5
+ ### Added
6
+ - add `PARTNER_SIGNAL_MAP` and `PARTNER_SIGNAL_THRESHOLD` constants to Personality::Helpers::Constants for partner-specific OCEAN nudges (weight 0.2 per signal)
7
+ - add `TraitModel#apply_partner_signals` to nudge extraversion, agreeableness, openness, and conscientiousness from partner engagement patterns; signals below threshold (0.3) are ignored
8
+ - wire partner signal extraction into `PersonalityStore#update` via `tick_results[:social]` reputation data (engagement frequency, direct address ratio, content diversity, consistency)
9
+ - add 16 specs covering PARTNER_SIGNAL_MAP entries, threshold gating, multi-signal application, and no observation_count side-effect
10
+
3
11
  ## [0.1.8] - 2026-03-30
4
12
 
5
13
  ### Fixed
@@ -70,6 +70,20 @@ module Legion
70
70
  mood_stability: [:neuroticism, :negative, 0.3]
71
71
  }.freeze
72
72
 
73
+ # Partner-specific signal map: partner interaction patterns that slowly nudge OCEAN traits.
74
+ # Lower weight (0.2) than general signals — personality should drift very slowly from
75
+ # partner engagement alone.
76
+ # Each entry: [trait, direction, weight]
77
+ PARTNER_SIGNAL_MAP = {
78
+ partner_engagement_frequency: [:extraversion, :positive, 0.2],
79
+ partner_direct_address_ratio: [:agreeableness, :positive, 0.2],
80
+ partner_content_diversity: [:openness, :positive, 0.2],
81
+ partner_consistency: [:conscientiousness, :positive, 0.2]
82
+ }.freeze
83
+
84
+ # Minimum signal value required to apply a partner nudge (0.0–1.0)
85
+ PARTNER_SIGNAL_THRESHOLD = 0.3
86
+
73
87
  # Threshold for "high" trait descriptor
74
88
  HIGH_THRESHOLD = 0.65
75
89
 
@@ -16,6 +16,8 @@ module Legion
16
16
  def update(tick_results)
17
17
  signals = extract_signals(tick_results)
18
18
  @model.update(signals)
19
+ partner_signals = extract_partner_signals(tick_results)
20
+ @model.apply_partner_signals(partner_signals) unless partner_signals.empty?
19
21
  end
20
22
 
21
23
  def full_description
@@ -41,6 +43,42 @@ module Legion
41
43
 
42
44
  private
43
45
 
46
+ def extract_partner_signals(tick_results)
47
+ counts = partner_records(tick_results)
48
+ return {} if counts.empty?
49
+
50
+ {
51
+ partner_engagement_frequency: partner_engagement_frequency(counts),
52
+ partner_direct_address_ratio: avg_partner_field(counts, :direct_address_ratio),
53
+ partner_content_diversity: avg_partner_diversity(counts),
54
+ partner_consistency: avg_partner_field(counts, :consistency)
55
+ }.compact
56
+ end
57
+
58
+ def partner_records(tick_results)
59
+ social = tick_results[:social] || tick_results[:social_cognition] || {}
60
+ return [] unless social.is_a?(Hash)
61
+
62
+ reputation = social[:reputation_updates] || social[:partners] || {}
63
+ return [] unless reputation.is_a?(Hash) && reputation.any?
64
+
65
+ reputation.values.grep(Hash)
66
+ end
67
+
68
+ def partner_engagement_frequency(counts)
69
+ (counts.count { |p| p[:message_count].to_i.positive? } / counts.size.to_f).clamp(0.0, 1.0)
70
+ end
71
+
72
+ def avg_partner_field(counts, key)
73
+ vals = counts.filter_map { |p| p[key] }.grep(Numeric)
74
+ vals.any? ? (vals.sum / vals.size.to_f).clamp(0.0, 1.0) : nil
75
+ end
76
+
77
+ def avg_partner_diversity(counts)
78
+ vals = counts.filter_map { |p| p[:topic_diversity] || p[:content_diversity] }.grep(Numeric)
79
+ vals.any? ? (vals.sum / vals.size.to_f).clamp(0.0, 1.0) : nil
80
+ end
81
+
44
82
  def extract_signals(tick_results)
45
83
  signals = {}
46
84
 
@@ -30,6 +30,14 @@ module Legion
30
30
  @traits[name.to_sym]
31
31
  end
32
32
 
33
+ def apply_partner_signals(signals)
34
+ observations = extract_partner_observations(signals)
35
+ return if observations.empty?
36
+
37
+ apply_observations(observations)
38
+ record_snapshot
39
+ end
40
+
33
41
  def formed?
34
42
  @observation_count >= Constants::FORMATION_THRESHOLD
35
43
  end
@@ -105,6 +113,21 @@ module Legion
105
113
 
106
114
  private
107
115
 
116
+ def extract_partner_observations(signals)
117
+ observations = Hash.new { |h, k| h[k] = [] }
118
+
119
+ Constants::PARTNER_SIGNAL_MAP.each do |signal_key, (trait, direction, weight)|
120
+ value = signals[signal_key]
121
+ next unless value.is_a?(Numeric)
122
+ next if value <= Constants::PARTNER_SIGNAL_THRESHOLD
123
+
124
+ effective = direction == :positive ? value : 1.0 - value
125
+ observations[trait] << { value: effective, weight: weight }
126
+ end
127
+
128
+ observations
129
+ end
130
+
108
131
  def extract_observations(signals)
109
132
  observations = Hash.new { |h, k| h[k] = [] }
110
133
 
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Agentic
6
6
  module Self
7
- VERSION = '0.1.8'
7
+ VERSION = '0.1.9'
8
8
  end
9
9
  end
10
10
  end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::Personality::Helpers::TraitModel do
4
+ subject(:model) { described_class.new }
5
+
6
+ let(:constants) { Legion::Extensions::Agentic::Self::Personality::Helpers::Constants }
7
+
8
+ describe 'PARTNER_SIGNAL_MAP constant' do
9
+ it 'defines partner_engagement_frequency nudging extraversion positively' do
10
+ entry = constants::PARTNER_SIGNAL_MAP[:partner_engagement_frequency]
11
+ expect(entry).to eq([:extraversion, :positive, 0.2])
12
+ end
13
+
14
+ it 'defines partner_direct_address_ratio nudging agreeableness positively' do
15
+ entry = constants::PARTNER_SIGNAL_MAP[:partner_direct_address_ratio]
16
+ expect(entry).to eq([:agreeableness, :positive, 0.2])
17
+ end
18
+
19
+ it 'defines partner_content_diversity nudging openness positively' do
20
+ entry = constants::PARTNER_SIGNAL_MAP[:partner_content_diversity]
21
+ expect(entry).to eq([:openness, :positive, 0.2])
22
+ end
23
+
24
+ it 'defines partner_consistency nudging conscientiousness positively' do
25
+ entry = constants::PARTNER_SIGNAL_MAP[:partner_consistency]
26
+ expect(entry).to eq([:conscientiousness, :positive, 0.2])
27
+ end
28
+
29
+ it 'has all entries with weights of 0.2' do
30
+ constants::PARTNER_SIGNAL_MAP.each_value do |_trait, _direction, weight|
31
+ expect(weight).to eq(0.2)
32
+ end
33
+ end
34
+
35
+ it 'references only valid OCEAN traits' do
36
+ constants::PARTNER_SIGNAL_MAP.each_value do |trait, _direction, _weight|
37
+ expect(constants::TRAITS).to include(trait)
38
+ end
39
+ end
40
+ end
41
+
42
+ describe '#apply_partner_signals' do
43
+ it 'nudges extraversion upward when partner_engagement_frequency is high' do
44
+ baseline = model.trait(:extraversion)
45
+ model.apply_partner_signals(partner_engagement_frequency: 0.9)
46
+ expect(model.trait(:extraversion)).to be > baseline
47
+ end
48
+
49
+ it 'nudges agreeableness upward when partner_direct_address_ratio is high' do
50
+ baseline = model.trait(:agreeableness)
51
+ model.apply_partner_signals(partner_direct_address_ratio: 0.8)
52
+ expect(model.trait(:agreeableness)).to be > baseline
53
+ end
54
+
55
+ it 'nudges openness upward when partner_content_diversity is high' do
56
+ baseline = model.trait(:openness)
57
+ model.apply_partner_signals(partner_content_diversity: 0.8)
58
+ expect(model.trait(:openness)).to be > baseline
59
+ end
60
+
61
+ it 'nudges conscientiousness upward when partner_consistency is high' do
62
+ baseline = model.trait(:conscientiousness)
63
+ model.apply_partner_signals(partner_consistency: 0.8)
64
+ expect(model.trait(:conscientiousness)).to be > baseline
65
+ end
66
+
67
+ it 'ignores signals below the minimum threshold' do
68
+ baseline = model.trait(:extraversion)
69
+ model.apply_partner_signals(partner_engagement_frequency: 0.1)
70
+ expect(model.trait(:extraversion)).to eq(baseline)
71
+ end
72
+
73
+ it 'ignores signals exactly at the threshold boundary' do
74
+ baseline = model.trait(:extraversion)
75
+ model.apply_partner_signals(partner_engagement_frequency: 0.3)
76
+ expect(model.trait(:extraversion)).to eq(baseline)
77
+ end
78
+
79
+ it 'applies multiple signals in a single call' do
80
+ extraversion_before = model.trait(:extraversion)
81
+ agreeableness_before = model.trait(:agreeableness)
82
+
83
+ model.apply_partner_signals(
84
+ partner_engagement_frequency: 0.9,
85
+ partner_direct_address_ratio: 0.8
86
+ )
87
+
88
+ expect(model.trait(:extraversion)).to be > extraversion_before
89
+ expect(model.trait(:agreeableness)).to be > agreeableness_before
90
+ end
91
+
92
+ it 'ignores unrecognized signal keys' do
93
+ expect { model.apply_partner_signals(unknown_signal: 0.9) }.not_to raise_error
94
+ end
95
+
96
+ it 'does not change observation_count' do
97
+ model.apply_partner_signals(partner_engagement_frequency: 0.9)
98
+ expect(model.observation_count).to eq(0)
99
+ end
100
+
101
+ it 'records a history snapshot after applying signals' do
102
+ model.apply_partner_signals(partner_engagement_frequency: 0.9)
103
+ expect(model.history.size).to eq(1)
104
+ end
105
+ end
106
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-agentic-self
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 0.1.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -406,6 +406,7 @@ files:
406
406
  - spec/legion/extensions/agentic/self/narrative_self/runners/narrative_self_spec.rb
407
407
  - spec/legion/extensions/agentic/self/personality/client_spec.rb
408
408
  - spec/legion/extensions/agentic/self/personality/helpers/constants_spec.rb
409
+ - spec/legion/extensions/agentic/self/personality/helpers/partner_signals_spec.rb
409
410
  - spec/legion/extensions/agentic/self/personality/helpers/personality_store_spec.rb
410
411
  - spec/legion/extensions/agentic/self/personality/helpers/trait_model_spec.rb
411
412
  - spec/legion/extensions/agentic/self/personality/runners/personality_spec.rb