lex-agentic-social 0.1.6 → 0.1.8
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 +4 -4
- data/CHANGELOG.md +17 -0
- data/Gemfile +2 -0
- data/lex-agentic-social.gemspec +3 -3
- data/lib/legion/extensions/agentic/social/attachment/client.rb +15 -0
- data/lib/legion/extensions/agentic/social/attachment/helpers/attachment_model.rb +102 -0
- data/lib/legion/extensions/agentic/social/attachment/helpers/attachment_store.rb +87 -0
- data/lib/legion/extensions/agentic/social/attachment/helpers/constants.rb +55 -0
- data/lib/legion/extensions/agentic/social/attachment/runners/attachment.rb +169 -0
- data/lib/legion/extensions/agentic/social/attachment/version.rb +13 -0
- data/lib/legion/extensions/agentic/social/attachment.rb +8 -0
- data/lib/legion/extensions/agentic/social/trust/helpers/trust_map.rb +94 -64
- data/lib/legion/extensions/agentic/social/trust.rb +0 -7
- data/lib/legion/extensions/agentic/social/version.rb +1 -1
- data/lib/legion/extensions/agentic/social.rb +1 -0
- data/spec/legion/extensions/agentic/social/attachment/helpers/attachment_model_spec.rb +144 -0
- data/spec/legion/extensions/agentic/social/attachment/helpers/attachment_store_spec.rb +121 -0
- data/spec/legion/extensions/agentic/social/attachment/helpers/constants_spec.rb +53 -0
- data/spec/legion/extensions/agentic/social/attachment/runners/attachment_spec.rb +130 -0
- data/spec/legion/extensions/agentic/social/trust/local_persistence_spec.rb +245 -258
- metadata +24 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9b5652d5875cf5aae7e51adf8b8b09f0e664a719144a7575ac9eb2f4bd7b7b56
|
|
4
|
+
data.tar.gz: 62101c073d3970708e278b2e7afd1dbbe3efc582eb03246bf19f975ad1999bd4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5e210bfd1a546461a34290e84e49a893cbb792c1d304b2087c759d2345a0cc30ac37c3f15760024ab19fdf83106f7cdefb87fa83c025100fd36852931f60c82f
|
|
7
|
+
data.tar.gz: 7a49e748bfc08d1c918c494f16d8ec4f579e63b3ec97120b532d168ffc9865e1748c9113025c0dfe7c9505c0f1aa3830624299e16926bc1b60be52187a71b0a5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.8] - 2026-03-31
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Attachment sub-module for Phase C bond modeling
|
|
7
|
+
- `AttachmentModel`: per-agent attachment strength, style, and stage tracking with EMA updates and no-regression stage transitions
|
|
8
|
+
- `AttachmentStore`: in-memory store with dirty tracking and Apollo Local persistence (`to_apollo_entries`, `from_apollo`)
|
|
9
|
+
- `Attachment` runner: `update_attachment` (tick integration), `reflect_on_bonds` (dream cycle orchestrator reading cross-module bond data), `attachment_stats`
|
|
10
|
+
|
|
11
|
+
## [0.1.7] - 2026-03-31
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- Migrate `TrustMap` persistence from Data::Local SQLite to Apollo Local (`to_apollo_entries`, `from_apollo`)
|
|
15
|
+
- Add dirty tracking (`dirty?`, `mark_clean!`) to `TrustMap` matching SocialGraph/MentalStateTracker pattern
|
|
16
|
+
- Tag schema: `['trust', 'trust_entry', '<agent_id>', '<domain>']` with optional `'partner'` tag via BondRegistry
|
|
17
|
+
- Remove Data::Local migration registration from trust entry point (migration file retained for existing installs)
|
|
18
|
+
- Add one-time `scripts/migrate_trust_to_apollo.rb` for legacy SQLite data migration
|
|
19
|
+
|
|
3
20
|
## [0.1.6] - 2026-03-31
|
|
4
21
|
|
|
5
22
|
### Added
|
data/Gemfile
CHANGED
data/lex-agentic-social.gemspec
CHANGED
|
@@ -33,7 +33,7 @@ Gem::Specification.new do |spec|
|
|
|
33
33
|
spec.add_dependency 'legion-transport', '>= 1.3.9'
|
|
34
34
|
|
|
35
35
|
spec.add_development_dependency 'rspec', '~> 3.13'
|
|
36
|
-
spec.add_development_dependency 'rubocop'
|
|
37
|
-
spec.add_development_dependency 'rubocop-legion'
|
|
38
|
-
spec.add_development_dependency 'rubocop-rspec'
|
|
36
|
+
spec.add_development_dependency 'rubocop'
|
|
37
|
+
spec.add_development_dependency 'rubocop-legion'
|
|
38
|
+
spec.add_development_dependency 'rubocop-rspec'
|
|
39
39
|
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Social
|
|
7
|
+
module Attachment
|
|
8
|
+
module Helpers
|
|
9
|
+
class AttachmentModel
|
|
10
|
+
attr_reader :agent_id, :attachment_strength, :attachment_style,
|
|
11
|
+
:bond_stage, :separation_tolerance, :interaction_count
|
|
12
|
+
|
|
13
|
+
def initialize(agent_id:)
|
|
14
|
+
@agent_id = agent_id
|
|
15
|
+
@attachment_strength = 0.0
|
|
16
|
+
@attachment_style = :secure
|
|
17
|
+
@bond_stage = :initial
|
|
18
|
+
@separation_tolerance = Constants::BASE_SEPARATION_TOLERANCE
|
|
19
|
+
@interaction_count = 0
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def update_from_signals(opts = {})
|
|
23
|
+
frequency_score = opts.fetch(:frequency_score, 0.0)
|
|
24
|
+
reciprocity_score = opts.fetch(:reciprocity_score, 0.0)
|
|
25
|
+
prediction_accuracy = opts.fetch(:prediction_accuracy, 0.0)
|
|
26
|
+
direct_address_ratio = opts.fetch(:direct_address_ratio, 0.0)
|
|
27
|
+
channel_consistency = opts.fetch(:channel_consistency, 0.0)
|
|
28
|
+
|
|
29
|
+
raw = (frequency_score * Constants::FREQUENCY_WEIGHT) +
|
|
30
|
+
(reciprocity_score * Constants::RECIPROCITY_WEIGHT) +
|
|
31
|
+
(prediction_accuracy * Constants::PREDICTION_ACCURACY_WEIGHT) +
|
|
32
|
+
(direct_address_ratio * Constants::DIRECT_ADDRESS_WEIGHT) +
|
|
33
|
+
(channel_consistency * Constants::CHANNEL_CONSISTENCY_WEIGHT)
|
|
34
|
+
|
|
35
|
+
@attachment_strength = if @interaction_count.zero?
|
|
36
|
+
raw.clamp(0.0, 1.0)
|
|
37
|
+
else
|
|
38
|
+
alpha = Constants::STRENGTH_ALPHA
|
|
39
|
+
((alpha * raw) + ((1.0 - alpha) * @attachment_strength)).clamp(0.0, 1.0)
|
|
40
|
+
end
|
|
41
|
+
@interaction_count += 1
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def update_stage!
|
|
45
|
+
new_stage = derive_stage
|
|
46
|
+
return if Constants::BOND_STAGES.index(new_stage) <= Constants::BOND_STAGES.index(@bond_stage)
|
|
47
|
+
|
|
48
|
+
@bond_stage = new_stage
|
|
49
|
+
@separation_tolerance = Constants::BASE_SEPARATION_TOLERANCE +
|
|
50
|
+
Constants::SEPARATION_TOLERANCE_GROWTH.fetch(@bond_stage, 0)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def derive_style!(opts = {})
|
|
54
|
+
frequency_variance = opts.fetch(:frequency_variance, 0.0)
|
|
55
|
+
reciprocity_imbalance = opts.fetch(:reciprocity_imbalance, 0.0)
|
|
56
|
+
frequency = opts.fetch(:frequency, 0.0)
|
|
57
|
+
direct_address_ratio = opts.fetch(:direct_address_ratio, 0.0)
|
|
58
|
+
thresholds = Constants::STYLE_THRESHOLDS
|
|
59
|
+
@attachment_style = if frequency_variance > thresholds[:anxious_frequency_variance] &&
|
|
60
|
+
reciprocity_imbalance > thresholds[:anxious_reciprocity_imbalance]
|
|
61
|
+
:anxious
|
|
62
|
+
elsif frequency < thresholds[:avoidant_frequency] &&
|
|
63
|
+
direct_address_ratio < thresholds[:avoidant_direct_address]
|
|
64
|
+
:avoidant
|
|
65
|
+
else
|
|
66
|
+
:secure
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def to_h
|
|
71
|
+
{ agent_id: @agent_id, attachment_strength: @attachment_strength,
|
|
72
|
+
attachment_style: @attachment_style, bond_stage: @bond_stage,
|
|
73
|
+
separation_tolerance: @separation_tolerance, interaction_count: @interaction_count }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.from_h(hash)
|
|
77
|
+
model = new(agent_id: hash[:agent_id])
|
|
78
|
+
model.instance_variable_set(:@attachment_strength, hash[:attachment_strength].to_f)
|
|
79
|
+
model.instance_variable_set(:@attachment_style, hash[:attachment_style]&.to_sym || :secure)
|
|
80
|
+
model.instance_variable_set(:@bond_stage, hash[:bond_stage]&.to_sym || :initial)
|
|
81
|
+
model.instance_variable_set(:@separation_tolerance, hash[:separation_tolerance]&.to_i || 3)
|
|
82
|
+
model.instance_variable_set(:@interaction_count, hash[:interaction_count].to_i)
|
|
83
|
+
model
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def derive_stage
|
|
89
|
+
Constants::STAGE_THRESHOLDS.each_key.reverse_each do |stage|
|
|
90
|
+
threshold = Constants::STAGE_THRESHOLDS[stage]
|
|
91
|
+
return stage if @interaction_count >= threshold[:interactions] &&
|
|
92
|
+
@attachment_strength >= threshold[:strength]
|
|
93
|
+
end
|
|
94
|
+
:initial
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Social
|
|
7
|
+
module Attachment
|
|
8
|
+
module Helpers
|
|
9
|
+
class AttachmentStore
|
|
10
|
+
def initialize
|
|
11
|
+
@models = {}
|
|
12
|
+
@dirty = false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def get(agent_id)
|
|
16
|
+
@models[agent_id.to_s]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def get_or_create(agent_id)
|
|
20
|
+
key = agent_id.to_s
|
|
21
|
+
@models[key] ||= begin
|
|
22
|
+
@dirty = true
|
|
23
|
+
AttachmentModel.new(agent_id: key)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def all_models
|
|
28
|
+
@models.values
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def dirty?
|
|
32
|
+
@dirty
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def mark_clean!
|
|
36
|
+
@dirty = false
|
|
37
|
+
self
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def to_apollo_entries
|
|
41
|
+
@models.map do |agent_id, model|
|
|
42
|
+
tags = Constants::TAG_PREFIX.dup + [agent_id]
|
|
43
|
+
tags << 'partner' if partner?(agent_id)
|
|
44
|
+
content = serialize(model.to_h)
|
|
45
|
+
{ content: content, tags: tags }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def from_apollo(store:)
|
|
50
|
+
result = store.query(text: 'bond attachment', tags: %w[bond attachment])
|
|
51
|
+
return false unless result[:success] && result[:results]&.any?
|
|
52
|
+
|
|
53
|
+
result[:results].each do |entry|
|
|
54
|
+
parsed = deserialize(entry[:content])
|
|
55
|
+
next unless parsed && parsed[:agent_id]
|
|
56
|
+
|
|
57
|
+
@models[parsed[:agent_id].to_s] = AttachmentModel.from_h(parsed)
|
|
58
|
+
end
|
|
59
|
+
true
|
|
60
|
+
rescue StandardError => e
|
|
61
|
+
warn "[attachment_store] from_apollo error: #{e.message}"
|
|
62
|
+
false
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def partner?(agent_id)
|
|
68
|
+
defined?(Legion::Gaia::BondRegistry) && Legion::Gaia::BondRegistry.partner?(agent_id)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def serialize(hash)
|
|
72
|
+
defined?(Legion::JSON) ? Legion::JSON.dump(hash) : ::JSON.dump(hash)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def deserialize(content)
|
|
76
|
+
parsed = defined?(Legion::JSON) ? Legion::JSON.parse(content) : ::JSON.parse(content, symbolize_names: true)
|
|
77
|
+
parsed.transform_keys(&:to_sym)
|
|
78
|
+
rescue StandardError => _e
|
|
79
|
+
nil
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Social
|
|
7
|
+
module Attachment
|
|
8
|
+
module Helpers
|
|
9
|
+
module Constants
|
|
10
|
+
# Strength computation weights (must sum to 1.0)
|
|
11
|
+
FREQUENCY_WEIGHT = 0.3
|
|
12
|
+
RECIPROCITY_WEIGHT = 0.25
|
|
13
|
+
PREDICTION_ACCURACY_WEIGHT = 0.2
|
|
14
|
+
DIRECT_ADDRESS_WEIGHT = 0.15
|
|
15
|
+
CHANNEL_CONSISTENCY_WEIGHT = 0.1
|
|
16
|
+
|
|
17
|
+
# Bond lifecycle stages
|
|
18
|
+
BOND_STAGES = %i[initial forming established deep].freeze
|
|
19
|
+
|
|
20
|
+
# Attachment style classifications
|
|
21
|
+
ATTACHMENT_STYLES = %i[secure anxious avoidant].freeze
|
|
22
|
+
|
|
23
|
+
# Stage progression thresholds
|
|
24
|
+
STAGE_THRESHOLDS = {
|
|
25
|
+
forming: { interactions: 10, strength: 0.3 },
|
|
26
|
+
established: { interactions: 50, strength: 0.5 },
|
|
27
|
+
deep: { interactions: 200, strength: 0.7 }
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
# Separation tolerance (consecutive prediction misses before anxiety signal)
|
|
31
|
+
BASE_SEPARATION_TOLERANCE = 3
|
|
32
|
+
SEPARATION_TOLERANCE_GROWTH = {
|
|
33
|
+
initial: 0, forming: 1, established: 2, deep: 4
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
36
|
+
# EMA alpha for strength updates
|
|
37
|
+
STRENGTH_ALPHA = 0.15
|
|
38
|
+
|
|
39
|
+
# Style derivation thresholds
|
|
40
|
+
STYLE_THRESHOLDS = {
|
|
41
|
+
anxious_frequency_variance: 0.4,
|
|
42
|
+
anxious_reciprocity_imbalance: 0.3,
|
|
43
|
+
avoidant_frequency: 0.2,
|
|
44
|
+
avoidant_direct_address: 0.15
|
|
45
|
+
}.freeze
|
|
46
|
+
|
|
47
|
+
# Apollo Local tag prefix
|
|
48
|
+
TAG_PREFIX = %w[bond attachment].freeze
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Social
|
|
7
|
+
module Attachment
|
|
8
|
+
module Runners
|
|
9
|
+
module Attachment
|
|
10
|
+
include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers) &&
|
|
11
|
+
Legion::Extensions::Helpers.const_defined?(:Lex, false)
|
|
12
|
+
|
|
13
|
+
def update_attachment(tick_results: {}, human_observations: [], **)
|
|
14
|
+
agents = collect_agent_ids(tick_results, human_observations)
|
|
15
|
+
return { agents_updated: 0 } if agents.empty?
|
|
16
|
+
|
|
17
|
+
agents.each do |agent_id|
|
|
18
|
+
model = attachment_store.get_or_create(agent_id)
|
|
19
|
+
signals = extract_signals(agent_id, tick_results, human_observations)
|
|
20
|
+
model.update_from_signals(signals)
|
|
21
|
+
model.update_stage!
|
|
22
|
+
model.derive_style!(extract_style_signals(agent_id, human_observations))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
{ agents_updated: agents.size, models: agents.map { |id| attachment_store.get(id)&.to_h } }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def reflect_on_bonds(_tick_results: {}, _bond_summary: {}, **)
|
|
29
|
+
store = apollo_local_store
|
|
30
|
+
return { success: false, error: :no_store } unless store
|
|
31
|
+
|
|
32
|
+
partner_id = resolve_partner_id
|
|
33
|
+
model = attachment_store.get(partner_id) if partner_id
|
|
34
|
+
comm_patterns = read_communication_patterns(store, partner_id)
|
|
35
|
+
arc_state = read_relationship_arc(store, partner_id)
|
|
36
|
+
health = compute_relationship_health(model, comm_patterns, arc_state)
|
|
37
|
+
|
|
38
|
+
{
|
|
39
|
+
bonds_reflected: attachment_store.all_models.size,
|
|
40
|
+
partner_bond: if model
|
|
41
|
+
{
|
|
42
|
+
stage: model.bond_stage,
|
|
43
|
+
strength: model.attachment_strength,
|
|
44
|
+
style: model.attachment_style,
|
|
45
|
+
health: health,
|
|
46
|
+
milestones_today: arc_state[:milestones_today] || [],
|
|
47
|
+
narrative: nil
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
}
|
|
51
|
+
rescue StandardError => e
|
|
52
|
+
{ success: false, error: e.message }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def attachment_stats(**)
|
|
56
|
+
partner_id = resolve_partner_id
|
|
57
|
+
partner_model = attachment_store.get(partner_id) if partner_id
|
|
58
|
+
{
|
|
59
|
+
bonds_tracked: attachment_store.all_models.size,
|
|
60
|
+
partner_bond: partner_model&.to_h
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def attachment_store
|
|
67
|
+
@attachment_store ||= Helpers::AttachmentStore.new
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def collect_agent_ids(tick_results, human_observations)
|
|
71
|
+
ids = Set.new
|
|
72
|
+
(tick_results.dig(:social_cognition, :reputation_updates) || []).each do |u|
|
|
73
|
+
ids << u[:agent_id].to_s if u[:agent_id]
|
|
74
|
+
end
|
|
75
|
+
human_observations.each { |o| ids << o[:agent_id].to_s if o[:agent_id] }
|
|
76
|
+
ids.to_a
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def extract_signals(agent_id, tick_results, human_observations)
|
|
80
|
+
reputation = (tick_results.dig(:social_cognition, :reputation_updates) || [])
|
|
81
|
+
.find { |u| u[:agent_id].to_s == agent_id }
|
|
82
|
+
prediction = tick_results.dig(:theory_of_mind, :prediction_accuracy) || {}
|
|
83
|
+
obs = human_observations.select { |o| o[:agent_id].to_s == agent_id }
|
|
84
|
+
|
|
85
|
+
direct_count = obs.count { |o| o[:direct_address] }
|
|
86
|
+
channels = obs.filter_map { |o| o[:channel] }.uniq
|
|
87
|
+
|
|
88
|
+
{
|
|
89
|
+
frequency_score: obs.size.clamp(0, 10) / 10.0,
|
|
90
|
+
reciprocity_score: (reputation&.dig(:composite) || 0.0).clamp(0.0, 1.0),
|
|
91
|
+
prediction_accuracy: (prediction[agent_id] || 0.0).clamp(0.0, 1.0),
|
|
92
|
+
direct_address_ratio: obs.empty? ? 0.0 : (direct_count.to_f / obs.size).clamp(0.0, 1.0),
|
|
93
|
+
channel_consistency: channels.size <= 1 ? 1.0 : (1.0 / channels.size).clamp(0.0, 1.0)
|
|
94
|
+
}
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def extract_style_signals(agent_id, human_observations)
|
|
98
|
+
obs = human_observations.select { |o| o[:agent_id].to_s == agent_id }
|
|
99
|
+
direct_count = obs.count { |o| o[:direct_address] }
|
|
100
|
+
{
|
|
101
|
+
frequency_variance: 0.0,
|
|
102
|
+
reciprocity_imbalance: 0.0,
|
|
103
|
+
frequency: obs.size.clamp(0, 10) / 10.0,
|
|
104
|
+
direct_address_ratio: obs.empty? ? 0.0 : direct_count.to_f / obs.size
|
|
105
|
+
}
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def resolve_partner_id
|
|
109
|
+
if defined?(Legion::Gaia::BondRegistry)
|
|
110
|
+
bond = Legion::Gaia::BondRegistry.all_bonds.find { |b| b[:role] == :partner }
|
|
111
|
+
return bond&.dig(:identity)&.to_s
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
strongest = attachment_store.all_models.max_by(&:attachment_strength)
|
|
115
|
+
strongest&.agent_id
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def apollo_local_store
|
|
119
|
+
return nil unless defined?(Legion::Apollo::Local) && Legion::Apollo::Local.started?
|
|
120
|
+
|
|
121
|
+
Legion::Apollo::Local
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def read_communication_patterns(store, partner_id)
|
|
125
|
+
return {} unless partner_id
|
|
126
|
+
|
|
127
|
+
result = store.query(text: 'communication_pattern',
|
|
128
|
+
tags: ['bond', 'communication_pattern', partner_id])
|
|
129
|
+
return {} unless result[:success] && result[:results]&.any?
|
|
130
|
+
|
|
131
|
+
deserialize(result[:results].first[:content]) || {}
|
|
132
|
+
rescue StandardError => _e
|
|
133
|
+
{}
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def read_relationship_arc(store, partner_id)
|
|
137
|
+
return {} unless partner_id
|
|
138
|
+
|
|
139
|
+
result = store.query(text: 'relationship_arc',
|
|
140
|
+
tags: ['bond', 'relationship_arc', partner_id])
|
|
141
|
+
return {} unless result[:success] && result[:results]&.any?
|
|
142
|
+
|
|
143
|
+
deserialize(result[:results].first[:content]) || {}
|
|
144
|
+
rescue StandardError => _e
|
|
145
|
+
{}
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def compute_relationship_health(model, comm_patterns, arc_state)
|
|
149
|
+
return 0.0 unless model
|
|
150
|
+
|
|
151
|
+
strength_component = model.attachment_strength * 0.4
|
|
152
|
+
reciprocity_component = (arc_state[:reciprocity_balance] || 0.5) * 0.3
|
|
153
|
+
consistency_component = (comm_patterns[:consistency] || 0.5) * 0.3
|
|
154
|
+
(strength_component + reciprocity_component + consistency_component).clamp(0.0, 1.0)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def deserialize(content)
|
|
158
|
+
parsed = defined?(Legion::JSON) ? Legion::JSON.parse(content) : ::JSON.parse(content, symbolize_names: true)
|
|
159
|
+
parsed.is_a?(Hash) ? parsed.transform_keys(&:to_sym) : {}
|
|
160
|
+
rescue StandardError => _e
|
|
161
|
+
{}
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'attachment/version'
|
|
4
|
+
require_relative 'attachment/helpers/constants'
|
|
5
|
+
require_relative 'attachment/helpers/attachment_model'
|
|
6
|
+
require_relative 'attachment/helpers/attachment_store'
|
|
7
|
+
require_relative 'attachment/runners/attachment'
|
|
8
|
+
require_relative 'attachment/client'
|