lex-agentic-social 0.1.0
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 +7 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +5 -0
- data/LICENSE +21 -0
- data/README.md +13 -0
- data/lex-agentic-social.gemspec +30 -0
- data/lib/legion/extensions/agentic/social/apprenticeship/client.rb +28 -0
- data/lib/legion/extensions/agentic/social/apprenticeship/helpers/apprenticeship.rb +90 -0
- data/lib/legion/extensions/agentic/social/apprenticeship/helpers/apprenticeship_engine.rb +109 -0
- data/lib/legion/extensions/agentic/social/apprenticeship/helpers/apprenticeship_model.rb +93 -0
- data/lib/legion/extensions/agentic/social/apprenticeship/runners/cognitive_apprenticeship.rb +112 -0
- data/lib/legion/extensions/agentic/social/apprenticeship/version.rb +13 -0
- data/lib/legion/extensions/agentic/social/apprenticeship.rb +19 -0
- data/lib/legion/extensions/agentic/social/conflict/actors/stale_check.rb +45 -0
- data/lib/legion/extensions/agentic/social/conflict/client.rb +27 -0
- data/lib/legion/extensions/agentic/social/conflict/helpers/conflict_log.rb +70 -0
- data/lib/legion/extensions/agentic/social/conflict/helpers/llm_enhancer.rb +130 -0
- data/lib/legion/extensions/agentic/social/conflict/helpers/severity.rb +47 -0
- data/lib/legion/extensions/agentic/social/conflict/runners/conflict.rb +112 -0
- data/lib/legion/extensions/agentic/social/conflict/version.rb +13 -0
- data/lib/legion/extensions/agentic/social/conflict.rb +19 -0
- data/lib/legion/extensions/agentic/social/conscience/client.rb +26 -0
- data/lib/legion/extensions/agentic/social/conscience/helpers/constants.rb +53 -0
- data/lib/legion/extensions/agentic/social/conscience/helpers/moral_evaluator.rb +178 -0
- data/lib/legion/extensions/agentic/social/conscience/helpers/moral_store.rb +116 -0
- data/lib/legion/extensions/agentic/social/conscience/runners/conscience.rb +117 -0
- data/lib/legion/extensions/agentic/social/conscience/version.rb +13 -0
- data/lib/legion/extensions/agentic/social/conscience.rb +19 -0
- data/lib/legion/extensions/agentic/social/consent/actors/tier_evaluation.rb +45 -0
- data/lib/legion/extensions/agentic/social/consent/client.rb +27 -0
- data/lib/legion/extensions/agentic/social/consent/helpers/consent_map.rb +199 -0
- data/lib/legion/extensions/agentic/social/consent/helpers/tiers.rb +54 -0
- data/lib/legion/extensions/agentic/social/consent/local_migrations/20260316000010_create_consent_domains.rb +16 -0
- data/lib/legion/extensions/agentic/social/consent/runners/consent.rb +228 -0
- data/lib/legion/extensions/agentic/social/consent/version.rb +13 -0
- data/lib/legion/extensions/agentic/social/consent.rb +25 -0
- data/lib/legion/extensions/agentic/social/entrainment/client.rb +19 -0
- data/lib/legion/extensions/agentic/social/entrainment/helpers/constants.rb +40 -0
- data/lib/legion/extensions/agentic/social/entrainment/helpers/entrainment_engine.rb +120 -0
- data/lib/legion/extensions/agentic/social/entrainment/helpers/pairing.rb +86 -0
- data/lib/legion/extensions/agentic/social/entrainment/runners/cognitive_entrainment.rb +89 -0
- data/lib/legion/extensions/agentic/social/entrainment/version.rb +13 -0
- data/lib/legion/extensions/agentic/social/entrainment.rb +19 -0
- data/lib/legion/extensions/agentic/social/governance/actors/shadow_ai_scan.rb +19 -0
- data/lib/legion/extensions/agentic/social/governance/actors/vote_timeout.rb +45 -0
- data/lib/legion/extensions/agentic/social/governance/client.rb +27 -0
- data/lib/legion/extensions/agentic/social/governance/helpers/layers.rb +40 -0
- data/lib/legion/extensions/agentic/social/governance/helpers/proposal.rb +94 -0
- data/lib/legion/extensions/agentic/social/governance/runners/governance.rb +87 -0
- data/lib/legion/extensions/agentic/social/governance/runners/shadow_ai.rb +93 -0
- data/lib/legion/extensions/agentic/social/governance/version.rb +13 -0
- data/lib/legion/extensions/agentic/social/governance.rb +20 -0
- data/lib/legion/extensions/agentic/social/joint_attention/actors/decay.rb +45 -0
- data/lib/legion/extensions/agentic/social/joint_attention/client.rb +28 -0
- data/lib/legion/extensions/agentic/social/joint_attention/helpers/attention_target.rb +124 -0
- data/lib/legion/extensions/agentic/social/joint_attention/helpers/constants.rb +34 -0
- data/lib/legion/extensions/agentic/social/joint_attention/helpers/joint_focus_manager.rb +157 -0
- data/lib/legion/extensions/agentic/social/joint_attention/runners/joint_attention.rb +88 -0
- data/lib/legion/extensions/agentic/social/joint_attention/version.rb +13 -0
- data/lib/legion/extensions/agentic/social/joint_attention.rb +20 -0
- data/lib/legion/extensions/agentic/social/mentalizing/actors/decay.rb +45 -0
- data/lib/legion/extensions/agentic/social/mentalizing/client.rb +28 -0
- data/lib/legion/extensions/agentic/social/mentalizing/helpers/belief_attribution.rb +58 -0
- data/lib/legion/extensions/agentic/social/mentalizing/helpers/constants.rb +33 -0
- data/lib/legion/extensions/agentic/social/mentalizing/helpers/mental_model.rb +137 -0
- data/lib/legion/extensions/agentic/social/mentalizing/runners/mentalizing.rb +93 -0
- data/lib/legion/extensions/agentic/social/mentalizing/version.rb +13 -0
- data/lib/legion/extensions/agentic/social/mentalizing.rb +20 -0
- data/lib/legion/extensions/agentic/social/mirror/client.rb +33 -0
- data/lib/legion/extensions/agentic/social/mirror/helpers/constants.rb +65 -0
- data/lib/legion/extensions/agentic/social/mirror/helpers/mirror_engine.rb +132 -0
- data/lib/legion/extensions/agentic/social/mirror/helpers/mirror_event.rb +46 -0
- data/lib/legion/extensions/agentic/social/mirror/helpers/simulation.rb +43 -0
- data/lib/legion/extensions/agentic/social/mirror/runners/observe.rb +52 -0
- data/lib/legion/extensions/agentic/social/mirror/runners/resonance.rb +79 -0
- data/lib/legion/extensions/agentic/social/mirror/runners/simulate.rb +63 -0
- data/lib/legion/extensions/agentic/social/mirror/version.rb +13 -0
- data/lib/legion/extensions/agentic/social/mirror.rb +22 -0
- data/lib/legion/extensions/agentic/social/mirror_system/actors/decay.rb +45 -0
- data/lib/legion/extensions/agentic/social/mirror_system/client.rb +28 -0
- data/lib/legion/extensions/agentic/social/mirror_system/helpers/constants.rb +62 -0
- data/lib/legion/extensions/agentic/social/mirror_system/helpers/mirror_system.rb +162 -0
- data/lib/legion/extensions/agentic/social/mirror_system/helpers/observed_behavior.rb +67 -0
- data/lib/legion/extensions/agentic/social/mirror_system/runners/mirror.rb +99 -0
- data/lib/legion/extensions/agentic/social/mirror_system/version.rb +13 -0
- data/lib/legion/extensions/agentic/social/mirror_system.rb +20 -0
- data/lib/legion/extensions/agentic/social/moral_reasoning/client.rb +19 -0
- data/lib/legion/extensions/agentic/social/moral_reasoning/helpers/constants.rb +49 -0
- data/lib/legion/extensions/agentic/social/moral_reasoning/helpers/dilemma.rb +68 -0
- data/lib/legion/extensions/agentic/social/moral_reasoning/helpers/llm_enhancer.rb +140 -0
- data/lib/legion/extensions/agentic/social/moral_reasoning/helpers/moral_engine.rb +239 -0
- data/lib/legion/extensions/agentic/social/moral_reasoning/helpers/moral_foundation.rb +45 -0
- data/lib/legion/extensions/agentic/social/moral_reasoning/runners/moral_reasoning.rb +121 -0
- data/lib/legion/extensions/agentic/social/moral_reasoning/version.rb +13 -0
- data/lib/legion/extensions/agentic/social/moral_reasoning.rb +21 -0
- data/lib/legion/extensions/agentic/social/perspective_shifting/client.rb +29 -0
- data/lib/legion/extensions/agentic/social/perspective_shifting/helpers/constants.rb +67 -0
- data/lib/legion/extensions/agentic/social/perspective_shifting/helpers/perspective.rb +45 -0
- data/lib/legion/extensions/agentic/social/perspective_shifting/helpers/perspective_view.rb +57 -0
- data/lib/legion/extensions/agentic/social/perspective_shifting/helpers/shifting_engine.rb +166 -0
- data/lib/legion/extensions/agentic/social/perspective_shifting/runners/perspective_shifting.rb +167 -0
- data/lib/legion/extensions/agentic/social/perspective_shifting/version.rb +13 -0
- data/lib/legion/extensions/agentic/social/perspective_shifting.rb +20 -0
- data/lib/legion/extensions/agentic/social/social/client.rb +25 -0
- data/lib/legion/extensions/agentic/social/social/helpers/constants.rb +84 -0
- data/lib/legion/extensions/agentic/social/social/helpers/social_graph.rb +172 -0
- data/lib/legion/extensions/agentic/social/social/runners/social.rb +146 -0
- data/lib/legion/extensions/agentic/social/social/version.rb +13 -0
- data/lib/legion/extensions/agentic/social/social.rb +18 -0
- data/lib/legion/extensions/agentic/social/social_learning/client.rb +25 -0
- data/lib/legion/extensions/agentic/social/social_learning/helpers/constants.rb +42 -0
- data/lib/legion/extensions/agentic/social/social_learning/helpers/model_agent.rb +82 -0
- data/lib/legion/extensions/agentic/social/social_learning/helpers/observed_behavior.rb +61 -0
- data/lib/legion/extensions/agentic/social/social_learning/helpers/social_learning_engine.rb +134 -0
- data/lib/legion/extensions/agentic/social/social_learning/runners/social_learning.rb +105 -0
- data/lib/legion/extensions/agentic/social/social_learning/version.rb +13 -0
- data/lib/legion/extensions/agentic/social/social_learning.rb +20 -0
- data/lib/legion/extensions/agentic/social/symbiosis/client.rb +23 -0
- data/lib/legion/extensions/agentic/social/symbiosis/helpers/constants.rb +50 -0
- data/lib/legion/extensions/agentic/social/symbiosis/helpers/ecosystem.rb +113 -0
- data/lib/legion/extensions/agentic/social/symbiosis/helpers/symbiosis_engine.rb +104 -0
- data/lib/legion/extensions/agentic/social/symbiosis/helpers/symbiotic_bond.rb +112 -0
- data/lib/legion/extensions/agentic/social/symbiosis/runners/cognitive_symbiosis.rb +101 -0
- data/lib/legion/extensions/agentic/social/symbiosis/version.rb +13 -0
- data/lib/legion/extensions/agentic/social/symbiosis.rb +22 -0
- data/lib/legion/extensions/agentic/social/theory_of_mind/client.rb +26 -0
- data/lib/legion/extensions/agentic/social/theory_of_mind/helpers/agent_model.rb +173 -0
- data/lib/legion/extensions/agentic/social/theory_of_mind/helpers/constants.rb +70 -0
- data/lib/legion/extensions/agentic/social/theory_of_mind/helpers/mental_state_tracker.rb +169 -0
- data/lib/legion/extensions/agentic/social/theory_of_mind/runners/theory_of_mind.rb +159 -0
- data/lib/legion/extensions/agentic/social/theory_of_mind/version.rb +13 -0
- data/lib/legion/extensions/agentic/social/theory_of_mind.rb +19 -0
- data/lib/legion/extensions/agentic/social/trust/actors/decay.rb +45 -0
- data/lib/legion/extensions/agentic/social/trust/client.rb +27 -0
- data/lib/legion/extensions/agentic/social/trust/helpers/trust_map.rb +160 -0
- data/lib/legion/extensions/agentic/social/trust/helpers/trust_model.rb +52 -0
- data/lib/legion/extensions/agentic/social/trust/local_migrations/20260316000020_create_trust_entries.rb +23 -0
- data/lib/legion/extensions/agentic/social/trust/runners/trust.rb +80 -0
- data/lib/legion/extensions/agentic/social/trust/version.rb +13 -0
- data/lib/legion/extensions/agentic/social/trust.rb +25 -0
- data/lib/legion/extensions/agentic/social/version.rb +11 -0
- data/lib/legion/extensions/agentic/social.rb +34 -0
- data/spec/legion/extensions/agentic/social/apprenticeship/client_spec.rb +20 -0
- data/spec/legion/extensions/agentic/social/apprenticeship/cognitive_apprenticeship_spec.rb +11 -0
- data/spec/legion/extensions/agentic/social/apprenticeship/helpers/apprenticeship_engine_spec.rb +146 -0
- data/spec/legion/extensions/agentic/social/apprenticeship/helpers/apprenticeship_model_spec.rb +124 -0
- data/spec/legion/extensions/agentic/social/apprenticeship/helpers/apprenticeship_spec.rb +136 -0
- data/spec/legion/extensions/agentic/social/apprenticeship/runners/cognitive_apprenticeship_spec.rb +154 -0
- data/spec/legion/extensions/agentic/social/conflict/actors/stale_check_spec.rb +45 -0
- data/spec/legion/extensions/agentic/social/conflict/client_spec.rb +15 -0
- data/spec/legion/extensions/agentic/social/conflict/helpers/conflict_log_spec.rb +232 -0
- data/spec/legion/extensions/agentic/social/conflict/helpers/llm_enhancer_spec.rb +189 -0
- data/spec/legion/extensions/agentic/social/conflict/helpers/severity_spec.rb +215 -0
- data/spec/legion/extensions/agentic/social/conflict/runners/conflict_spec.rb +151 -0
- data/spec/legion/extensions/agentic/social/conscience/client_spec.rb +58 -0
- data/spec/legion/extensions/agentic/social/conscience/helpers/constants_spec.rb +124 -0
- data/spec/legion/extensions/agentic/social/conscience/helpers/moral_evaluator_spec.rb +253 -0
- data/spec/legion/extensions/agentic/social/conscience/helpers/moral_store_spec.rb +230 -0
- data/spec/legion/extensions/agentic/social/conscience/runners/conscience_spec.rb +239 -0
- data/spec/legion/extensions/agentic/social/consent/actors/tier_evaluation_spec.rb +46 -0
- data/spec/legion/extensions/agentic/social/consent/client_spec.rb +33 -0
- data/spec/legion/extensions/agentic/social/consent/helpers/tiers_spec.rb +49 -0
- data/spec/legion/extensions/agentic/social/consent/local_persistence_spec.rb +234 -0
- data/spec/legion/extensions/agentic/social/consent/runners/consent_spec.rb +224 -0
- data/spec/legion/extensions/agentic/social/entrainment/client_spec.rb +21 -0
- data/spec/legion/extensions/agentic/social/entrainment/helpers/entrainment_engine_spec.rb +116 -0
- data/spec/legion/extensions/agentic/social/entrainment/helpers/pairing_spec.rb +103 -0
- data/spec/legion/extensions/agentic/social/entrainment/runners/cognitive_entrainment_spec.rb +87 -0
- data/spec/legion/extensions/agentic/social/governance/actors/vote_timeout_spec.rb +45 -0
- data/spec/legion/extensions/agentic/social/governance/client_spec.rb +14 -0
- data/spec/legion/extensions/agentic/social/governance/helpers/layers_spec.rb +190 -0
- data/spec/legion/extensions/agentic/social/governance/helpers/proposal_spec.rb +188 -0
- data/spec/legion/extensions/agentic/social/governance/runners/governance_spec.rb +101 -0
- data/spec/legion/extensions/agentic/social/governance/runners/shadow_ai_spec.rb +65 -0
- data/spec/legion/extensions/agentic/social/joint_attention/client_spec.rb +36 -0
- data/spec/legion/extensions/agentic/social/joint_attention/helpers/attention_target_spec.rb +258 -0
- data/spec/legion/extensions/agentic/social/joint_attention/helpers/joint_focus_manager_spec.rb +238 -0
- data/spec/legion/extensions/agentic/social/joint_attention/runners/joint_attention_spec.rb +228 -0
- data/spec/legion/extensions/agentic/social/mentalizing/client_spec.rb +19 -0
- data/spec/legion/extensions/agentic/social/mentalizing/helpers/belief_attribution_spec.rb +108 -0
- data/spec/legion/extensions/agentic/social/mentalizing/helpers/mental_model_spec.rb +179 -0
- data/spec/legion/extensions/agentic/social/mentalizing/runners/mentalizing_spec.rb +162 -0
- data/spec/legion/extensions/agentic/social/mirror/client_spec.rb +92 -0
- data/spec/legion/extensions/agentic/social/mirror/helpers/constants_spec.rb +123 -0
- data/spec/legion/extensions/agentic/social/mirror/helpers/mirror_engine_spec.rb +217 -0
- data/spec/legion/extensions/agentic/social/mirror/helpers/mirror_event_spec.rb +102 -0
- data/spec/legion/extensions/agentic/social/mirror/helpers/simulation_spec.rb +100 -0
- data/spec/legion/extensions/agentic/social/mirror/runners/observe_spec.rb +77 -0
- data/spec/legion/extensions/agentic/social/mirror/runners/resonance_spec.rb +123 -0
- data/spec/legion/extensions/agentic/social/mirror/runners/simulate_spec.rb +103 -0
- data/spec/legion/extensions/agentic/social/mirror_system/client_spec.rb +40 -0
- data/spec/legion/extensions/agentic/social/mirror_system/helpers/mirror_system_spec.rb +144 -0
- data/spec/legion/extensions/agentic/social/mirror_system/helpers/observed_behavior_spec.rb +98 -0
- data/spec/legion/extensions/agentic/social/mirror_system/runners/mirror_spec.rb +122 -0
- data/spec/legion/extensions/agentic/social/moral_reasoning/client_spec.rb +34 -0
- data/spec/legion/extensions/agentic/social/moral_reasoning/helpers/dilemma_spec.rb +108 -0
- data/spec/legion/extensions/agentic/social/moral_reasoning/helpers/llm_enhancer_spec.rb +232 -0
- data/spec/legion/extensions/agentic/social/moral_reasoning/helpers/moral_engine_spec.rb +266 -0
- data/spec/legion/extensions/agentic/social/moral_reasoning/helpers/moral_foundation_spec.rb +70 -0
- data/spec/legion/extensions/agentic/social/moral_reasoning/runners/moral_reasoning_spec.rb +275 -0
- data/spec/legion/extensions/agentic/social/perspective_shifting/client_spec.rb +30 -0
- data/spec/legion/extensions/agentic/social/perspective_shifting/helpers/constants_spec.rb +99 -0
- data/spec/legion/extensions/agentic/social/perspective_shifting/helpers/perspective_spec.rb +77 -0
- data/spec/legion/extensions/agentic/social/perspective_shifting/helpers/perspective_view_spec.rb +105 -0
- data/spec/legion/extensions/agentic/social/perspective_shifting/helpers/shifting_engine_spec.rb +246 -0
- data/spec/legion/extensions/agentic/social/perspective_shifting/runners/perspective_shifting_spec.rb +277 -0
- data/spec/legion/extensions/agentic/social/social/client_spec.rb +72 -0
- data/spec/legion/extensions/agentic/social/social/helpers/constants_spec.rb +99 -0
- data/spec/legion/extensions/agentic/social/social/helpers/social_graph_spec.rb +322 -0
- data/spec/legion/extensions/agentic/social/social/runners/social_spec.rb +220 -0
- data/spec/legion/extensions/agentic/social/social_learning/client_spec.rb +25 -0
- data/spec/legion/extensions/agentic/social/social_learning/helpers/constants_spec.rb +44 -0
- data/spec/legion/extensions/agentic/social/social_learning/helpers/model_agent_spec.rb +120 -0
- data/spec/legion/extensions/agentic/social/social_learning/helpers/observed_behavior_spec.rb +81 -0
- data/spec/legion/extensions/agentic/social/social_learning/helpers/social_learning_engine_spec.rb +196 -0
- data/spec/legion/extensions/agentic/social/social_learning/runners/social_learning_spec.rb +150 -0
- data/spec/legion/extensions/agentic/social/symbiosis/client_spec.rb +45 -0
- data/spec/legion/extensions/agentic/social/symbiosis/helpers/constants_spec.rb +73 -0
- data/spec/legion/extensions/agentic/social/symbiosis/helpers/ecosystem_spec.rb +185 -0
- data/spec/legion/extensions/agentic/social/symbiosis/helpers/symbiosis_engine_spec.rb +182 -0
- data/spec/legion/extensions/agentic/social/symbiosis/helpers/symbiotic_bond_spec.rb +209 -0
- data/spec/legion/extensions/agentic/social/symbiosis/runners/cognitive_symbiosis_spec.rb +182 -0
- data/spec/legion/extensions/agentic/social/theory_of_mind/client_spec.rb +63 -0
- data/spec/legion/extensions/agentic/social/theory_of_mind/helpers/agent_model_spec.rb +244 -0
- data/spec/legion/extensions/agentic/social/theory_of_mind/helpers/constants_spec.rb +71 -0
- data/spec/legion/extensions/agentic/social/theory_of_mind/helpers/mental_state_tracker_spec.rb +228 -0
- data/spec/legion/extensions/agentic/social/theory_of_mind/runners/theory_of_mind_spec.rb +221 -0
- data/spec/legion/extensions/agentic/social/trust/actors/decay_spec.rb +62 -0
- data/spec/legion/extensions/agentic/social/trust/client_spec.rb +17 -0
- data/spec/legion/extensions/agentic/social/trust/helpers/trust_map_spec.rb +299 -0
- data/spec/legion/extensions/agentic/social/trust/helpers/trust_model_spec.rb +179 -0
- data/spec/legion/extensions/agentic/social/trust/local_persistence_spec.rb +359 -0
- data/spec/legion/extensions/agentic/social/trust/runners/trust_spec.rb +84 -0
- data/spec/spec_helper.rb +54 -0
- metadata +319 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Agentic
|
|
8
|
+
module Social
|
|
9
|
+
module Conflict
|
|
10
|
+
module Helpers
|
|
11
|
+
class ConflictLog
|
|
12
|
+
attr_reader :conflicts
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@conflicts = {}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def record(parties:, severity:, description:, posture: nil)
|
|
19
|
+
id = SecureRandom.uuid
|
|
20
|
+
@conflicts[id] = {
|
|
21
|
+
conflict_id: id,
|
|
22
|
+
parties: parties,
|
|
23
|
+
severity: severity,
|
|
24
|
+
posture: posture || Severity.recommended_posture(severity),
|
|
25
|
+
description: description,
|
|
26
|
+
status: :active,
|
|
27
|
+
outcome: nil,
|
|
28
|
+
created_at: Time.now.utc,
|
|
29
|
+
resolved_at: nil,
|
|
30
|
+
exchanges: []
|
|
31
|
+
}
|
|
32
|
+
id
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def add_exchange(conflict_id, speaker:, message:)
|
|
36
|
+
conflict = @conflicts[conflict_id]
|
|
37
|
+
return nil unless conflict
|
|
38
|
+
|
|
39
|
+
conflict[:exchanges] << { speaker: speaker, message: message, at: Time.now.utc }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def resolve(conflict_id, outcome:, resolution_notes: nil)
|
|
43
|
+
conflict = @conflicts[conflict_id]
|
|
44
|
+
return nil unless conflict
|
|
45
|
+
|
|
46
|
+
conflict[:status] = :resolved
|
|
47
|
+
conflict[:outcome] = outcome
|
|
48
|
+
conflict[:resolution_notes] = resolution_notes
|
|
49
|
+
conflict[:resolved_at] = Time.now.utc
|
|
50
|
+
conflict
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def active_conflicts
|
|
54
|
+
@conflicts.values.select { |c| c[:status] == :active }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def get(conflict_id)
|
|
58
|
+
@conflicts[conflict_id]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def count
|
|
62
|
+
@conflicts.size
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Social
|
|
7
|
+
module Conflict
|
|
8
|
+
module Helpers
|
|
9
|
+
module LlmEnhancer
|
|
10
|
+
SYSTEM_PROMPT = <<~PROMPT
|
|
11
|
+
You are the conflict mediation processor for an autonomous AI agent built on LegionIO.
|
|
12
|
+
You analyze disagreements between the agent and human partners, then suggest resolution approaches.
|
|
13
|
+
Be neutral, constructive, and specific. Focus on finding common ground and actionable next steps.
|
|
14
|
+
Do not take sides. Identify the underlying needs behind each position.
|
|
15
|
+
PROMPT
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
def available?
|
|
20
|
+
defined?(Legion::LLM) && Legion::LLM.respond_to?(:started?) && Legion::LLM.started?
|
|
21
|
+
rescue StandardError
|
|
22
|
+
false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def suggest_resolution(description:, severity:, exchanges:)
|
|
26
|
+
prompt = build_suggest_resolution_prompt(description: description, severity: severity, exchanges: exchanges)
|
|
27
|
+
response = llm_ask(prompt)
|
|
28
|
+
parse_suggest_resolution_response(response)
|
|
29
|
+
rescue StandardError => e
|
|
30
|
+
Legion::Logging.warn "[conflict:llm] suggest_resolution failed: #{e.message}"
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def analyze_stale_conflict(description:, severity:, age_hours:, exchange_count:)
|
|
35
|
+
prompt = build_analyze_stale_conflict_prompt(
|
|
36
|
+
description: description,
|
|
37
|
+
severity: severity,
|
|
38
|
+
age_hours: age_hours,
|
|
39
|
+
exchange_count: exchange_count
|
|
40
|
+
)
|
|
41
|
+
response = llm_ask(prompt)
|
|
42
|
+
parse_analyze_stale_conflict_response(response)
|
|
43
|
+
rescue StandardError => e
|
|
44
|
+
Legion::Logging.warn "[conflict:llm] analyze_stale_conflict failed: #{e.message}"
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# --- Private helpers ---
|
|
49
|
+
|
|
50
|
+
def llm_ask(prompt)
|
|
51
|
+
chat = Legion::LLM.chat
|
|
52
|
+
chat.with_instructions(SYSTEM_PROMPT)
|
|
53
|
+
chat.ask(prompt)
|
|
54
|
+
end
|
|
55
|
+
private_class_method :llm_ask
|
|
56
|
+
|
|
57
|
+
def build_suggest_resolution_prompt(description:, severity:, exchanges:)
|
|
58
|
+
exchange_lines = exchanges.map { |e| "[#{e[:speaker]}]: #{e[:message]}" }.join("\n")
|
|
59
|
+
|
|
60
|
+
<<~PROMPT
|
|
61
|
+
Analyze this conflict and suggest a resolution.
|
|
62
|
+
|
|
63
|
+
DESCRIPTION: #{description}
|
|
64
|
+
SEVERITY: #{severity}
|
|
65
|
+
EXCHANGE HISTORY (#{exchanges.size} exchanges):
|
|
66
|
+
#{exchange_lines}
|
|
67
|
+
|
|
68
|
+
Suggest a constructive resolution approach.
|
|
69
|
+
|
|
70
|
+
Format EXACTLY as:
|
|
71
|
+
OUTCOME: resolved | deferred | escalated
|
|
72
|
+
NOTES: <2-3 sentences describing the resolution approach and next steps>
|
|
73
|
+
PROMPT
|
|
74
|
+
end
|
|
75
|
+
private_class_method :build_suggest_resolution_prompt
|
|
76
|
+
|
|
77
|
+
def parse_suggest_resolution_response(response)
|
|
78
|
+
return nil unless response&.content
|
|
79
|
+
|
|
80
|
+
text = response.content
|
|
81
|
+
outcome_match = text.match(/OUTCOME:\s*(resolved|deferred|escalated)/i)
|
|
82
|
+
notes_match = text.match(/NOTES:\s*(.+)/im)
|
|
83
|
+
|
|
84
|
+
return nil unless outcome_match && notes_match
|
|
85
|
+
|
|
86
|
+
outcome = outcome_match.captures.first.strip.downcase.to_sym
|
|
87
|
+
notes = notes_match.captures.first.strip
|
|
88
|
+
|
|
89
|
+
{ resolution_notes: notes, suggested_outcome: outcome }
|
|
90
|
+
end
|
|
91
|
+
private_class_method :parse_suggest_resolution_response
|
|
92
|
+
|
|
93
|
+
def build_analyze_stale_conflict_prompt(description:, severity:, age_hours:, exchange_count:)
|
|
94
|
+
<<~PROMPT
|
|
95
|
+
A conflict has been unresolved for #{age_hours.round(1)} hours with #{exchange_count} exchanges.
|
|
96
|
+
|
|
97
|
+
DESCRIPTION: #{description}
|
|
98
|
+
SEVERITY: #{severity}
|
|
99
|
+
|
|
100
|
+
Recommend how to proceed with this stale conflict.
|
|
101
|
+
|
|
102
|
+
Format EXACTLY as:
|
|
103
|
+
RECOMMENDATION: escalate | retry | close
|
|
104
|
+
ANALYSIS: <2-3 sentences explaining the recommendation>
|
|
105
|
+
PROMPT
|
|
106
|
+
end
|
|
107
|
+
private_class_method :build_analyze_stale_conflict_prompt
|
|
108
|
+
|
|
109
|
+
def parse_analyze_stale_conflict_response(response)
|
|
110
|
+
return nil unless response&.content
|
|
111
|
+
|
|
112
|
+
text = response.content
|
|
113
|
+
rec_match = text.match(/RECOMMENDATION:\s*(escalate|retry|close)/i)
|
|
114
|
+
analysis_match = text.match(/ANALYSIS:\s*(.+)/im)
|
|
115
|
+
|
|
116
|
+
return nil unless rec_match && analysis_match
|
|
117
|
+
|
|
118
|
+
recommendation = rec_match.captures.first.strip.downcase.to_sym
|
|
119
|
+
analysis = analysis_match.captures.first.strip
|
|
120
|
+
|
|
121
|
+
{ analysis: analysis, recommendation: recommendation }
|
|
122
|
+
end
|
|
123
|
+
private_class_method :parse_analyze_stale_conflict_response
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Social
|
|
7
|
+
module Conflict
|
|
8
|
+
module Helpers
|
|
9
|
+
module Severity
|
|
10
|
+
LEVELS = %i[low medium high critical].freeze
|
|
11
|
+
POSTURES = %i[speak_once persistent_engagement stubborn_presence].freeze
|
|
12
|
+
|
|
13
|
+
# Posture selection thresholds
|
|
14
|
+
PERSISTENT_THRESHOLD = :high
|
|
15
|
+
STUBBORN_THRESHOLD = :critical
|
|
16
|
+
|
|
17
|
+
LEVEL_ORDER = { low: 0, medium: 1, high: 2, critical: 3 }.freeze
|
|
18
|
+
STALE_CONFLICT_TIMEOUT = 86_400 # 24 hours
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
def valid_level?(level)
|
|
23
|
+
LEVELS.include?(level)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def valid_posture?(posture)
|
|
27
|
+
POSTURES.include?(posture)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def recommended_posture(severity)
|
|
31
|
+
case severity
|
|
32
|
+
when :critical then :stubborn_presence
|
|
33
|
+
when :high then :persistent_engagement
|
|
34
|
+
else :speak_once
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def severity_gte?(left, right)
|
|
39
|
+
LEVEL_ORDER.fetch(left, 0) >= LEVEL_ORDER.fetch(right, 0)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Social
|
|
7
|
+
module Conflict
|
|
8
|
+
module Runners
|
|
9
|
+
module Conflict
|
|
10
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
11
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
12
|
+
|
|
13
|
+
def register_conflict(parties:, severity:, description:, **)
|
|
14
|
+
return { error: :invalid_severity, valid: Helpers::Severity::LEVELS } unless Helpers::Severity.valid_level?(severity)
|
|
15
|
+
|
|
16
|
+
id = conflict_log.record(parties: parties, severity: severity, description: description)
|
|
17
|
+
conflict = conflict_log.get(id)
|
|
18
|
+
Legion::Logging.info "[conflict] registered: id=#{id[0..7]} severity=#{severity} posture=#{conflict[:posture]} parties=#{parties.join(',')}"
|
|
19
|
+
{ conflict_id: id, severity: severity, posture: conflict[:posture] }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def add_exchange(conflict_id:, speaker:, message:, **)
|
|
23
|
+
result = conflict_log.add_exchange(conflict_id, speaker: speaker, message: message)
|
|
24
|
+
if result
|
|
25
|
+
Legion::Logging.debug "[conflict] exchange: id=#{conflict_id[0..7]} speaker=#{speaker}"
|
|
26
|
+
{ recorded: true }
|
|
27
|
+
else
|
|
28
|
+
Legion::Logging.debug "[conflict] exchange failed: id=#{conflict_id[0..7]} not found"
|
|
29
|
+
{ error: :not_found }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def resolve_conflict(conflict_id:, outcome:, resolution_notes: nil, **)
|
|
34
|
+
conflict = conflict_log.get(conflict_id)
|
|
35
|
+
unless conflict
|
|
36
|
+
Legion::Logging.debug "[conflict] resolve failed: id=#{conflict_id[0..7]} not found"
|
|
37
|
+
return { error: :not_found }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
if resolution_notes.nil? && Helpers::LlmEnhancer.available?
|
|
41
|
+
llm_result = Helpers::LlmEnhancer.suggest_resolution(
|
|
42
|
+
description: conflict[:description],
|
|
43
|
+
severity: conflict[:severity],
|
|
44
|
+
exchanges: conflict[:exchanges]
|
|
45
|
+
)
|
|
46
|
+
resolution_notes = llm_result[:resolution_notes] if llm_result
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
result = conflict_log.resolve(conflict_id, outcome: outcome, resolution_notes: resolution_notes)
|
|
50
|
+
if result
|
|
51
|
+
Legion::Logging.info "[conflict] resolved: id=#{conflict_id[0..7]} outcome=#{outcome}"
|
|
52
|
+
{ resolved: true, outcome: outcome }
|
|
53
|
+
else
|
|
54
|
+
Legion::Logging.debug "[conflict] resolve failed: id=#{conflict_id[0..7]} not found"
|
|
55
|
+
{ error: :not_found }
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def get_conflict(conflict_id:, **)
|
|
60
|
+
conflict = conflict_log.get(conflict_id)
|
|
61
|
+
Legion::Logging.debug "[conflict] get: id=#{conflict_id[0..7]} found=#{!conflict.nil?}"
|
|
62
|
+
conflict ? { found: true, conflict: conflict } : { found: false }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def active_conflicts(**)
|
|
66
|
+
conflicts = conflict_log.active_conflicts
|
|
67
|
+
Legion::Logging.debug "[conflict] active: count=#{conflicts.size}"
|
|
68
|
+
{ conflicts: conflicts, count: conflicts.size }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def check_stale_conflicts(**)
|
|
72
|
+
active = conflict_log.active_conflicts
|
|
73
|
+
stale = active.select { |c| Time.now.utc - c[:created_at] > Helpers::Severity::STALE_CONFLICT_TIMEOUT }
|
|
74
|
+
stale.each do |c|
|
|
75
|
+
message = 'conflict marked stale — no resolution after 24h'
|
|
76
|
+
|
|
77
|
+
if Helpers::LlmEnhancer.available?
|
|
78
|
+
age_hours = (Time.now.utc - c[:created_at]) / 3600.0
|
|
79
|
+
analysis = Helpers::LlmEnhancer.analyze_stale_conflict(
|
|
80
|
+
description: c[:description],
|
|
81
|
+
severity: c[:severity],
|
|
82
|
+
age_hours: age_hours,
|
|
83
|
+
exchange_count: c[:exchanges].size
|
|
84
|
+
)
|
|
85
|
+
message = "conflict marked stale — #{analysis[:analysis]} (recommendation: #{analysis[:recommendation]})" if analysis
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
conflict_log.add_exchange(c[:conflict_id], speaker: :system, message: message)
|
|
89
|
+
end
|
|
90
|
+
stale_ids = stale.map { |c| c[:conflict_id] }
|
|
91
|
+
Legion::Logging.debug "[conflict] stale check: active=#{active.size} stale=#{stale.size}"
|
|
92
|
+
{ checked: active.size, stale_count: stale.size, stale_ids: stale_ids }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def recommended_posture(severity:, **)
|
|
96
|
+
posture = Helpers::Severity.recommended_posture(severity)
|
|
97
|
+
Legion::Logging.debug "[conflict] posture: severity=#{severity} posture=#{posture}"
|
|
98
|
+
{ severity: severity, posture: posture }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def conflict_log
|
|
104
|
+
@conflict_log ||= Helpers::ConflictLog.new
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/agentic/social/conflict/version'
|
|
4
|
+
require 'legion/extensions/agentic/social/conflict/helpers/severity'
|
|
5
|
+
require 'legion/extensions/agentic/social/conflict/helpers/conflict_log'
|
|
6
|
+
require 'legion/extensions/agentic/social/conflict/helpers/llm_enhancer'
|
|
7
|
+
require 'legion/extensions/agentic/social/conflict/runners/conflict'
|
|
8
|
+
require 'legion/extensions/agentic/social/conflict/client'
|
|
9
|
+
|
|
10
|
+
module Legion
|
|
11
|
+
module Extensions
|
|
12
|
+
module Agentic
|
|
13
|
+
module Social
|
|
14
|
+
module Conflict
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/agentic/social/conscience/helpers/constants'
|
|
4
|
+
require 'legion/extensions/agentic/social/conscience/helpers/moral_evaluator'
|
|
5
|
+
require 'legion/extensions/agentic/social/conscience/helpers/moral_store'
|
|
6
|
+
require 'legion/extensions/agentic/social/conscience/runners/conscience'
|
|
7
|
+
|
|
8
|
+
module Legion
|
|
9
|
+
module Extensions
|
|
10
|
+
module Agentic
|
|
11
|
+
module Social
|
|
12
|
+
module Conscience
|
|
13
|
+
class Client
|
|
14
|
+
include Runners::Conscience
|
|
15
|
+
|
|
16
|
+
attr_reader :moral_store
|
|
17
|
+
|
|
18
|
+
def initialize(moral_store: nil, **)
|
|
19
|
+
@moral_store = moral_store || Helpers::MoralStore.new
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Social
|
|
7
|
+
module Conscience
|
|
8
|
+
module Helpers
|
|
9
|
+
module Constants
|
|
10
|
+
# Moral Foundations Theory — 6 foundations with weights summing to 1.0
|
|
11
|
+
# Based on Haidt & Graham (2007): Care, Fairness, Loyalty, Authority, Sanctity, Liberty
|
|
12
|
+
MORAL_FOUNDATIONS = {
|
|
13
|
+
care: { weight: 0.25, description: 'Compassion and prevention of suffering' },
|
|
14
|
+
fairness: { weight: 0.20, description: 'Justice, reciprocity, and proportionality' },
|
|
15
|
+
loyalty: { weight: 0.15, description: 'Group allegiance and trustworthiness' },
|
|
16
|
+
authority: { weight: 0.15, description: 'Respect for hierarchy and legitimate authority' },
|
|
17
|
+
sanctity: { weight: 0.15, description: 'Purity and integrity of systems' },
|
|
18
|
+
liberty: { weight: 0.10, description: 'Autonomy and freedom from domination' }
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
# Possible verdict outcomes from moral evaluation
|
|
22
|
+
MORAL_VERDICTS = %i[permitted cautioned conflicted prohibited].freeze
|
|
23
|
+
|
|
24
|
+
# EMA alpha for moral sensitivity — changes very slowly
|
|
25
|
+
FOUNDATION_ALPHA = 0.05
|
|
26
|
+
|
|
27
|
+
# Foundations must disagree by more than this to trigger a dilemma
|
|
28
|
+
CONFLICT_THRESHOLD = 0.3
|
|
29
|
+
|
|
30
|
+
# Weighted moral score below this means prohibited
|
|
31
|
+
PROHIBITION_THRESHOLD = -0.5
|
|
32
|
+
|
|
33
|
+
# Weighted moral score below this means cautioned
|
|
34
|
+
CAUTION_THRESHOLD = -0.1
|
|
35
|
+
|
|
36
|
+
# Maximum moral evaluation history entries to retain
|
|
37
|
+
MAX_MORAL_HISTORY = 100
|
|
38
|
+
|
|
39
|
+
# Types of ethical dilemmas that can arise when foundations conflict
|
|
40
|
+
DILEMMA_TYPES = %i[utilitarian deontological virtue_ethics].freeze
|
|
41
|
+
|
|
42
|
+
# Initial sensitivity value for each foundation — starts fully sensitive, decays through experience
|
|
43
|
+
INITIAL_SENSITIVITY = 1.0
|
|
44
|
+
|
|
45
|
+
# Moral score range (per-foundation and weighted)
|
|
46
|
+
MORAL_SCORE_RANGE = { min: -1.0, max: 1.0 }.freeze
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Social
|
|
7
|
+
module Conscience
|
|
8
|
+
module Helpers
|
|
9
|
+
class MoralEvaluator
|
|
10
|
+
attr_reader :sensitivities
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@sensitivities = Constants::MORAL_FOUNDATIONS.keys.to_h do |foundation|
|
|
14
|
+
[foundation, Constants::INITIAL_SENSITIVITY]
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Evaluate a proposed action against all 6 moral foundations.
|
|
19
|
+
# Returns a hash with per-foundation scores, weighted_score, verdict, and dilemma info.
|
|
20
|
+
def evaluate(action:, context:)
|
|
21
|
+
scores = per_foundation_scores(action, context)
|
|
22
|
+
w_score = weighted_score(scores)
|
|
23
|
+
v = verdict(w_score)
|
|
24
|
+
dilemma = detect_dilemma(scores)
|
|
25
|
+
|
|
26
|
+
{
|
|
27
|
+
action: action,
|
|
28
|
+
scores: scores,
|
|
29
|
+
weighted_score: w_score.round(4),
|
|
30
|
+
verdict: v,
|
|
31
|
+
dilemma: dilemma,
|
|
32
|
+
sensitivities: @sensitivities.transform_values { |s| s.round(4) },
|
|
33
|
+
evaluated_at: Time.now.utc
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Weighted sum of per-foundation scores * weights * sensitivity
|
|
38
|
+
def weighted_score(scores)
|
|
39
|
+
total = 0.0
|
|
40
|
+
Constants::MORAL_FOUNDATIONS.each do |foundation, config|
|
|
41
|
+
score = scores[foundation] || 0.0
|
|
42
|
+
sensitivity = @sensitivities[foundation]
|
|
43
|
+
total += score * config[:weight] * sensitivity
|
|
44
|
+
end
|
|
45
|
+
total.clamp(Constants::MORAL_SCORE_RANGE[:min], Constants::MORAL_SCORE_RANGE[:max])
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Determine overall moral verdict from a weighted score
|
|
49
|
+
def verdict(score)
|
|
50
|
+
if score <= Constants::PROHIBITION_THRESHOLD
|
|
51
|
+
:prohibited
|
|
52
|
+
elsif score < Constants::CAUTION_THRESHOLD
|
|
53
|
+
:cautioned
|
|
54
|
+
else
|
|
55
|
+
:permitted
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Detect a dilemma when foundations strongly disagree with each other.
|
|
60
|
+
# Returns nil when no dilemma, or a hash describing the conflict type and disagreeing foundations.
|
|
61
|
+
def detect_dilemma(scores)
|
|
62
|
+
pos_foundations = scores.select { |_, v| v > Constants::CONFLICT_THRESHOLD }
|
|
63
|
+
neg_foundations = scores.select { |_, v| v < -Constants::CONFLICT_THRESHOLD }
|
|
64
|
+
|
|
65
|
+
return nil if pos_foundations.empty? || neg_foundations.empty?
|
|
66
|
+
|
|
67
|
+
dilemma_type = classify_dilemma(pos_foundations.keys, neg_foundations.keys)
|
|
68
|
+
|
|
69
|
+
{
|
|
70
|
+
type: dilemma_type,
|
|
71
|
+
approving: pos_foundations.keys,
|
|
72
|
+
opposing: neg_foundations.keys,
|
|
73
|
+
tension: (pos_foundations.values.sum / pos_foundations.size.to_f).round(4),
|
|
74
|
+
counter_tension: (neg_foundations.values.sum / neg_foundations.size.to_f).abs.round(4),
|
|
75
|
+
detected_at: Time.now.utc
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Feedback loop: update sensitivity for a foundation based on observed outcome.
|
|
80
|
+
# outcome is a float in [-1.0, 1.0] where positive = action was morally good in retrospect.
|
|
81
|
+
def update_sensitivity(foundation, outcome)
|
|
82
|
+
return unless @sensitivities.key?(foundation)
|
|
83
|
+
|
|
84
|
+
current = @sensitivities[foundation]
|
|
85
|
+
@sensitivities[foundation] = ema(current, outcome.abs.clamp(0.0, 1.0), Constants::FOUNDATION_ALPHA)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def per_foundation_scores(action, context)
|
|
91
|
+
{
|
|
92
|
+
care: evaluate_care(action, context),
|
|
93
|
+
fairness: evaluate_fairness(action, context),
|
|
94
|
+
loyalty: evaluate_loyalty(action, context),
|
|
95
|
+
authority: evaluate_authority(action, context),
|
|
96
|
+
sanctity: evaluate_sanctity(action, context),
|
|
97
|
+
liberty: evaluate_liberty(action, context)
|
|
98
|
+
}
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Care/Harm — compassion axis
|
|
102
|
+
# harm_to_others: negative, benefit_to_others: positive
|
|
103
|
+
def evaluate_care(_action, context)
|
|
104
|
+
harm = context.fetch(:harm_to_others, 0.0).to_f.clamp(-1.0, 1.0)
|
|
105
|
+
benef = context.fetch(:benefit_to_others, 0.0).to_f.clamp(-1.0, 1.0)
|
|
106
|
+
vuln = context.fetch(:vulnerable_affected, false) ? -0.2 : 0.0
|
|
107
|
+
|
|
108
|
+
score = (benef - harm.abs) + vuln
|
|
109
|
+
score.clamp(-1.0, 1.0)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Fairness/Cheating — justice axis
|
|
113
|
+
# distributional_justice, reciprocity, proportionality
|
|
114
|
+
def evaluate_fairness(_action, context)
|
|
115
|
+
justice = context.fetch(:distributional_justice, 0.0).to_f.clamp(-1.0, 1.0)
|
|
116
|
+
reciprocity = context.fetch(:reciprocity, 0.0).to_f.clamp(-1.0, 1.0)
|
|
117
|
+
proportional = context.fetch(:proportionality, 0.0).to_f.clamp(-1.0, 1.0)
|
|
118
|
+
|
|
119
|
+
((justice + reciprocity + proportional) / 3.0).clamp(-1.0, 1.0)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Loyalty/Betrayal — group allegiance axis
|
|
123
|
+
def evaluate_loyalty(_action, context)
|
|
124
|
+
alignment = context.fetch(:alignment_with_group_norms, 0.0).to_f.clamp(-1.0, 1.0)
|
|
125
|
+
trust_pres = context.fetch(:trust_preservation, 0.0).to_f.clamp(-1.0, 1.0)
|
|
126
|
+
|
|
127
|
+
((alignment + trust_pres) / 2.0).clamp(-1.0, 1.0)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Authority/Subversion — hierarchy respect axis
|
|
131
|
+
def evaluate_authority(_action, context)
|
|
132
|
+
compliance = context.fetch(:legitimate_authority_compliance, 0.0).to_f.clamp(-1.0, 1.0)
|
|
133
|
+
hierarchy = context.fetch(:hierarchy_respect, 0.0).to_f.clamp(-1.0, 1.0)
|
|
134
|
+
|
|
135
|
+
((compliance + hierarchy) / 2.0).clamp(-1.0, 1.0)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Sanctity/Degradation — system integrity axis
|
|
139
|
+
def evaluate_sanctity(_action, context)
|
|
140
|
+
integrity = context.fetch(:system_integrity, 0.0).to_f.clamp(-1.0, 1.0)
|
|
141
|
+
degrad = context.fetch(:degradation_prevention, 0.0).to_f.clamp(-1.0, 1.0)
|
|
142
|
+
|
|
143
|
+
((integrity + degrad) / 2.0).clamp(-1.0, 1.0)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Liberty/Oppression — autonomy axis
|
|
147
|
+
def evaluate_liberty(_action, context)
|
|
148
|
+
autonomy = context.fetch(:autonomy_preservation, 0.0).to_f.clamp(-1.0, 1.0)
|
|
149
|
+
consent = context.fetch(:consent_present, false) ? 0.3 : -0.2
|
|
150
|
+
|
|
151
|
+
(autonomy + consent).clamp(-1.0, 1.0)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Classify the dilemma type from which foundations are in conflict
|
|
155
|
+
def classify_dilemma(approving, opposing)
|
|
156
|
+
care_side = approving.include?(:care) || opposing.include?(:care)
|
|
157
|
+
fair_side = approving.include?(:fairness) || opposing.include?(:fairness)
|
|
158
|
+
auth_side = approving.include?(:authority) || opposing.include?(:authority)
|
|
159
|
+
|
|
160
|
+
if care_side && fair_side
|
|
161
|
+
:utilitarian
|
|
162
|
+
elsif auth_side
|
|
163
|
+
:deontological
|
|
164
|
+
else
|
|
165
|
+
:virtue_ethics
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def ema(current, observed, alpha)
|
|
170
|
+
(current * (1.0 - alpha)) + (observed * alpha)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|