lex-mentalizing 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/Gemfile +11 -0
- data/lex-mentalizing.gemspec +30 -0
- data/lib/legion/extensions/mentalizing/actors/decay.rb +41 -0
- data/lib/legion/extensions/mentalizing/client.rb +24 -0
- data/lib/legion/extensions/mentalizing/helpers/belief_attribution.rb +54 -0
- data/lib/legion/extensions/mentalizing/helpers/constants.rb +29 -0
- data/lib/legion/extensions/mentalizing/helpers/mental_model.rb +133 -0
- data/lib/legion/extensions/mentalizing/runners/mentalizing.rb +89 -0
- data/lib/legion/extensions/mentalizing/version.rb +9 -0
- data/lib/legion/extensions/mentalizing.rb +17 -0
- data/spec/legion/extensions/mentalizing/client_spec.rb +19 -0
- data/spec/legion/extensions/mentalizing/helpers/belief_attribution_spec.rb +108 -0
- data/spec/legion/extensions/mentalizing/helpers/mental_model_spec.rb +179 -0
- data/spec/legion/extensions/mentalizing/runners/mentalizing_spec.rb +162 -0
- data/spec/spec_helper.rb +20 -0
- metadata +76 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 15a3ade481ea7a8a8d77fc7ce5e21f3182e8fa37ad5895a61c8922988224eff2
|
|
4
|
+
data.tar.gz: 7aad6db5216975d38f4dae1b087cc8750412e8cac7a79fe0d36d23e7aaff8c6b
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 7a8d22b17f0fb84925452f67f7055e7e8a81ba8c53d822b59dadbd0f9934871ea5e4ccdcf45008555fda5a8cf32a7dd34039e350de7e7e821825f1dcd96a4c4f
|
|
7
|
+
data.tar.gz: 9bfd174741ef5e1f2195c7a6e761e7abca42665d059de3e309b8830f9f7622ef5246dd95e696bb4d9500d205edaba36078c06adaa768a684aede601dc48f6f34
|
data/Gemfile
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/legion/extensions/mentalizing/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'lex-mentalizing'
|
|
7
|
+
spec.version = Legion::Extensions::Mentalizing::VERSION
|
|
8
|
+
spec.authors = ['Esity']
|
|
9
|
+
spec.email = ['matthewdiverson@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'LEX Mentalizing'
|
|
12
|
+
spec.description = 'Second-order Theory of Mind for brain-modeled agentic AI. ' \
|
|
13
|
+
'Recursive belief attribution, false-belief detection, and social alignment modeling.'
|
|
14
|
+
spec.homepage = 'https://github.com/LegionIO/lex-mentalizing'
|
|
15
|
+
spec.license = 'MIT'
|
|
16
|
+
spec.required_ruby_version = '>= 3.4'
|
|
17
|
+
|
|
18
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
19
|
+
spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-mentalizing'
|
|
20
|
+
spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-mentalizing'
|
|
21
|
+
spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-mentalizing'
|
|
22
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-mentalizing/issues'
|
|
23
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
24
|
+
|
|
25
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
26
|
+
Dir.glob('{lib,spec}/**/*') + %w[lex-mentalizing.gemspec Gemfile]
|
|
27
|
+
end
|
|
28
|
+
spec.require_paths = ['lib']
|
|
29
|
+
spec.add_development_dependency 'legion-gaia'
|
|
30
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/actors/every'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Mentalizing
|
|
8
|
+
module Actor
|
|
9
|
+
class Decay < Legion::Extensions::Actors::Every
|
|
10
|
+
def runner_class
|
|
11
|
+
Legion::Extensions::Mentalizing::Runners::Mentalizing
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def runner_function
|
|
15
|
+
'update_mentalizing'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def time
|
|
19
|
+
300
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def run_now?
|
|
23
|
+
false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def use_runner?
|
|
27
|
+
false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def check_subtask?
|
|
31
|
+
false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def generate_task?
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/mentalizing/helpers/constants'
|
|
4
|
+
require 'legion/extensions/mentalizing/helpers/belief_attribution'
|
|
5
|
+
require 'legion/extensions/mentalizing/helpers/mental_model'
|
|
6
|
+
require 'legion/extensions/mentalizing/runners/mentalizing'
|
|
7
|
+
|
|
8
|
+
module Legion
|
|
9
|
+
module Extensions
|
|
10
|
+
module Mentalizing
|
|
11
|
+
class Client
|
|
12
|
+
include Runners::Mentalizing
|
|
13
|
+
|
|
14
|
+
def initialize(**)
|
|
15
|
+
@mental_model = Helpers::MentalModel.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
attr_reader :mental_model
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Mentalizing
|
|
6
|
+
module Helpers
|
|
7
|
+
class BeliefAttribution
|
|
8
|
+
attr_reader :id, :agent_id, :subject, :content, :depth, :about_agent_id, :created_at
|
|
9
|
+
attr_accessor :confidence
|
|
10
|
+
|
|
11
|
+
def initialize(agent_id:, subject:, content:, confidence:, depth: 0, about_agent_id: nil)
|
|
12
|
+
@id = SecureRandom.uuid
|
|
13
|
+
@agent_id = agent_id
|
|
14
|
+
@subject = subject
|
|
15
|
+
@content = content
|
|
16
|
+
@confidence = confidence.clamp(0.0, 1.0)
|
|
17
|
+
@depth = depth
|
|
18
|
+
@about_agent_id = about_agent_id
|
|
19
|
+
@created_at = Time.now.utc
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def decay
|
|
23
|
+
@confidence = [@confidence - Constants::BELIEF_DECAY, Constants::BELIEF_FLOOR].max
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def reinforce(amount: Constants::CONFIDENCE_ALPHA)
|
|
27
|
+
@confidence = [@confidence + amount, 1.0].min
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def label
|
|
31
|
+
Constants::CONFIDENCE_LABELS.each do |range, lbl|
|
|
32
|
+
return lbl if range.cover?(@confidence)
|
|
33
|
+
end
|
|
34
|
+
:unknown
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def to_h
|
|
38
|
+
{
|
|
39
|
+
id: @id,
|
|
40
|
+
agent_id: @agent_id,
|
|
41
|
+
subject: @subject,
|
|
42
|
+
content: @content,
|
|
43
|
+
confidence: @confidence,
|
|
44
|
+
depth: @depth,
|
|
45
|
+
about_agent_id: @about_agent_id,
|
|
46
|
+
label: label,
|
|
47
|
+
created_at: @created_at
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Mentalizing
|
|
6
|
+
module Helpers
|
|
7
|
+
module Constants
|
|
8
|
+
MAX_AGENTS = 50
|
|
9
|
+
MAX_BELIEFS_PER_AGENT = 30
|
|
10
|
+
MAX_RECURSION_DEPTH = 4
|
|
11
|
+
BELIEF_DECAY = 0.02
|
|
12
|
+
BELIEF_FLOOR = 0.05
|
|
13
|
+
CONFIDENCE_ALPHA = 0.12
|
|
14
|
+
DEFAULT_CONFIDENCE = 0.3
|
|
15
|
+
MAX_HISTORY = 200
|
|
16
|
+
PROJECTION_DISCOUNT = 0.7
|
|
17
|
+
|
|
18
|
+
CONFIDENCE_LABELS = {
|
|
19
|
+
(0.8..) => :certain,
|
|
20
|
+
(0.6...0.8) => :confident,
|
|
21
|
+
(0.4...0.6) => :uncertain,
|
|
22
|
+
(0.2...0.4) => :speculative,
|
|
23
|
+
(..0.2) => :unknown
|
|
24
|
+
}.freeze
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Mentalizing
|
|
6
|
+
module Helpers
|
|
7
|
+
class MentalModel
|
|
8
|
+
def initialize
|
|
9
|
+
@models = {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def attribute_belief(agent_id:, subject:, content:, confidence:, depth: 0, about_agent_id: nil)
|
|
13
|
+
ensure_agent_capacity(agent_id)
|
|
14
|
+
capped_depth = [depth.to_i, Constants::MAX_RECURSION_DEPTH].min
|
|
15
|
+
belief = BeliefAttribution.new(
|
|
16
|
+
agent_id: agent_id,
|
|
17
|
+
subject: subject,
|
|
18
|
+
content: content,
|
|
19
|
+
confidence: confidence,
|
|
20
|
+
depth: capped_depth,
|
|
21
|
+
about_agent_id: about_agent_id
|
|
22
|
+
)
|
|
23
|
+
@models[agent_id] ||= []
|
|
24
|
+
@models[agent_id] << belief
|
|
25
|
+
prune_agent_beliefs(agent_id)
|
|
26
|
+
belief
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def beliefs_for(agent_id:)
|
|
30
|
+
@models[agent_id] || []
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def beliefs_about(about_agent_id:)
|
|
34
|
+
@models.values.flatten.select { |b| b.about_agent_id == about_agent_id }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def recursive_belief(agent_id:, about_agent_id:, subject:)
|
|
38
|
+
beliefs = beliefs_for(agent_id: agent_id)
|
|
39
|
+
beliefs.select { |b| b.about_agent_id == about_agent_id && b.subject == subject }
|
|
40
|
+
.max_by(&:confidence)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def project_self(subject:, own_belief:, other_agent_id:)
|
|
44
|
+
discounted = (own_belief * Constants::PROJECTION_DISCOUNT).clamp(0.0, 1.0)
|
|
45
|
+
attribute_belief(
|
|
46
|
+
agent_id: :self,
|
|
47
|
+
subject: subject,
|
|
48
|
+
content: "projected: #{other_agent_id} thinks I believe this",
|
|
49
|
+
confidence: discounted,
|
|
50
|
+
depth: 1,
|
|
51
|
+
about_agent_id: other_agent_id
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def alignment(agent_a:, agent_b:, subject:)
|
|
56
|
+
beliefs_a = beliefs_on_subject(agent_a, subject)
|
|
57
|
+
beliefs_b = beliefs_on_subject(agent_b, subject)
|
|
58
|
+
return 0.0 if beliefs_a.empty? || beliefs_b.empty?
|
|
59
|
+
|
|
60
|
+
conf_a = beliefs_a.map(&:confidence).sum / beliefs_a.size
|
|
61
|
+
conf_b = beliefs_b.map(&:confidence).sum / beliefs_b.size
|
|
62
|
+
1.0 - (conf_a - conf_b).abs
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def detect_false_belief(agent_id:, subject:, reality:)
|
|
66
|
+
relevant = beliefs_for(agent_id: agent_id).select { |b| b.subject == subject }
|
|
67
|
+
return { false_belief: false, reason: :no_beliefs } if relevant.empty?
|
|
68
|
+
|
|
69
|
+
strongest = relevant.max_by(&:confidence)
|
|
70
|
+
false_belief = strongest.content != reality
|
|
71
|
+
{
|
|
72
|
+
false_belief: false_belief,
|
|
73
|
+
agent_id: agent_id,
|
|
74
|
+
subject: subject,
|
|
75
|
+
held_belief: strongest.content,
|
|
76
|
+
reality: reality,
|
|
77
|
+
confidence: strongest.confidence
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def decay_all
|
|
82
|
+
@models.each_value { |beliefs| beliefs.each(&:decay) }
|
|
83
|
+
prune_expired
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def remove_agent(agent_id:)
|
|
87
|
+
@models.delete(agent_id)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def agent_count
|
|
91
|
+
@models.size
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def belief_count
|
|
95
|
+
@models.values.sum(&:size)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def to_h
|
|
99
|
+
@models.transform_values { |beliefs| beliefs.map(&:to_h) }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def beliefs_on_subject(agent_id, subject)
|
|
105
|
+
beliefs_for(agent_id: agent_id).select { |b| b.subject == subject }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def ensure_agent_capacity(agent_id)
|
|
109
|
+
return if @models.size < Constants::MAX_AGENTS
|
|
110
|
+
return if @models.key?(agent_id)
|
|
111
|
+
|
|
112
|
+
oldest_key = @models.keys.first
|
|
113
|
+
@models.delete(oldest_key)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def prune_agent_beliefs(agent_id)
|
|
117
|
+
list = @models[agent_id]
|
|
118
|
+
return unless list && list.size > Constants::MAX_BELIEFS_PER_AGENT
|
|
119
|
+
|
|
120
|
+
@models[agent_id] = list.sort_by(&:confidence).last(Constants::MAX_BELIEFS_PER_AGENT)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def prune_expired
|
|
124
|
+
@models.each_value do |beliefs|
|
|
125
|
+
beliefs.reject! { |b| b.confidence <= Constants::BELIEF_FLOOR }
|
|
126
|
+
end
|
|
127
|
+
@models.reject! { |_, v| v.empty? }
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Mentalizing
|
|
6
|
+
module Runners
|
|
7
|
+
module Mentalizing
|
|
8
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
9
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
|
|
11
|
+
def attribute_belief(agent_id:, subject:, content:, confidence: nil, depth: 0, about_agent_id: nil, **)
|
|
12
|
+
depth = [depth.to_i, Helpers::Constants::MAX_RECURSION_DEPTH].min
|
|
13
|
+
conf = confidence || Helpers::Constants::DEFAULT_CONFIDENCE
|
|
14
|
+
belief = mental_model.attribute_belief(
|
|
15
|
+
agent_id: agent_id,
|
|
16
|
+
subject: subject,
|
|
17
|
+
content: content,
|
|
18
|
+
confidence: conf.to_f,
|
|
19
|
+
depth: depth,
|
|
20
|
+
about_agent_id: about_agent_id
|
|
21
|
+
)
|
|
22
|
+
Legion::Logging.debug "[mentalizing] attribute agent=#{agent_id} subject=#{subject} depth=#{depth} conf=#{belief.confidence.round(2)}"
|
|
23
|
+
{ attributed: true, belief: belief.to_h }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def project_belief(subject:, own_belief:, other_agent_id:, **)
|
|
27
|
+
belief = mental_model.project_self(subject: subject, own_belief: own_belief.to_f, other_agent_id: other_agent_id)
|
|
28
|
+
Legion::Logging.debug "[mentalizing] project subject=#{subject} other=#{other_agent_id} discounted_conf=#{belief.confidence.round(2)}"
|
|
29
|
+
{ projected: true, belief: belief.to_h }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def check_alignment(agent_a:, agent_b:, subject:, **)
|
|
33
|
+
score = mental_model.alignment(agent_a: agent_a, agent_b: agent_b, subject: subject)
|
|
34
|
+
Legion::Logging.debug "[mentalizing] alignment agent_a=#{agent_a} agent_b=#{agent_b} subject=#{subject} score=#{score.round(2)}"
|
|
35
|
+
{ agent_a: agent_a, agent_b: agent_b, subject: subject, alignment: score.round(4) }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def detect_false_belief(agent_id:, subject:, reality:, **)
|
|
39
|
+
result = mental_model.detect_false_belief(agent_id: agent_id, subject: subject, reality: reality)
|
|
40
|
+
Legion::Logging.info "[mentalizing] false_belief_check agent=#{agent_id} subject=#{subject} false=#{result[:false_belief]}"
|
|
41
|
+
result
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def beliefs_for_agent(agent_id:, **)
|
|
45
|
+
beliefs = mental_model.beliefs_for(agent_id: agent_id)
|
|
46
|
+
Legion::Logging.debug "[mentalizing] beliefs_for agent=#{agent_id} count=#{beliefs.size}"
|
|
47
|
+
{ agent_id: agent_id, beliefs: beliefs.map(&:to_h), count: beliefs.size }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def beliefs_about_agent(about_agent_id:, **)
|
|
51
|
+
beliefs = mental_model.beliefs_about(about_agent_id: about_agent_id)
|
|
52
|
+
Legion::Logging.debug "[mentalizing] beliefs_about about=#{about_agent_id} count=#{beliefs.size}"
|
|
53
|
+
{ about_agent_id: about_agent_id, beliefs: beliefs.map(&:to_h), count: beliefs.size }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def recursive_belief_lookup(agent_id:, about_agent_id:, subject:, **)
|
|
57
|
+
belief = mental_model.recursive_belief(agent_id: agent_id, about_agent_id: about_agent_id, subject: subject)
|
|
58
|
+
if belief
|
|
59
|
+
Legion::Logging.debug "[mentalizing] recursive agent=#{agent_id} about=#{about_agent_id} subject=#{subject} found=true"
|
|
60
|
+
{ found: true, belief: belief.to_h }
|
|
61
|
+
else
|
|
62
|
+
Legion::Logging.debug "[mentalizing] recursive agent=#{agent_id} about=#{about_agent_id} subject=#{subject} found=false"
|
|
63
|
+
{ found: false, agent_id: agent_id, about_agent_id: about_agent_id, subject: subject }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def update_mentalizing(**)
|
|
68
|
+
mental_model.decay_all
|
|
69
|
+
Legion::Logging.debug "[mentalizing] decay cycle agents=#{mental_model.agent_count} beliefs=#{mental_model.belief_count}"
|
|
70
|
+
{ decayed: true, agents: mental_model.agent_count, beliefs: mental_model.belief_count }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def mentalizing_stats(**)
|
|
74
|
+
{
|
|
75
|
+
agents: mental_model.agent_count,
|
|
76
|
+
beliefs: mental_model.belief_count
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def mental_model
|
|
83
|
+
@mental_model ||= Helpers::MentalModel.new
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
require 'legion/extensions/mentalizing/version'
|
|
5
|
+
require 'legion/extensions/mentalizing/helpers/constants'
|
|
6
|
+
require 'legion/extensions/mentalizing/helpers/belief_attribution'
|
|
7
|
+
require 'legion/extensions/mentalizing/helpers/mental_model'
|
|
8
|
+
require 'legion/extensions/mentalizing/runners/mentalizing'
|
|
9
|
+
require 'legion/extensions/mentalizing/client'
|
|
10
|
+
|
|
11
|
+
module Legion
|
|
12
|
+
module Extensions
|
|
13
|
+
module Mentalizing
|
|
14
|
+
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/mentalizing/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Mentalizing::Client do
|
|
6
|
+
let(:client) { described_class.new }
|
|
7
|
+
|
|
8
|
+
it 'responds to all runner methods' do
|
|
9
|
+
expect(client).to respond_to(:attribute_belief)
|
|
10
|
+
expect(client).to respond_to(:project_belief)
|
|
11
|
+
expect(client).to respond_to(:check_alignment)
|
|
12
|
+
expect(client).to respond_to(:detect_false_belief)
|
|
13
|
+
expect(client).to respond_to(:beliefs_for_agent)
|
|
14
|
+
expect(client).to respond_to(:beliefs_about_agent)
|
|
15
|
+
expect(client).to respond_to(:recursive_belief_lookup)
|
|
16
|
+
expect(client).to respond_to(:update_mentalizing)
|
|
17
|
+
expect(client).to respond_to(:mentalizing_stats)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::Mentalizing::Helpers::BeliefAttribution do
|
|
4
|
+
subject(:belief) do
|
|
5
|
+
described_class.new(
|
|
6
|
+
agent_id: 'alice',
|
|
7
|
+
subject: 'weather',
|
|
8
|
+
content: 'it will rain',
|
|
9
|
+
confidence: 0.7,
|
|
10
|
+
depth: 1
|
|
11
|
+
)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe '#initialize' do
|
|
15
|
+
it 'assigns fields correctly' do
|
|
16
|
+
expect(belief.agent_id).to eq('alice')
|
|
17
|
+
expect(belief.subject).to eq('weather')
|
|
18
|
+
expect(belief.content).to eq('it will rain')
|
|
19
|
+
expect(belief.depth).to eq(1)
|
|
20
|
+
expect(belief.about_agent_id).to be_nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'clamps confidence to 0.0..1.0' do
|
|
24
|
+
over = described_class.new(agent_id: 'a', subject: 's', content: 'c', confidence: 1.5)
|
|
25
|
+
under = described_class.new(agent_id: 'a', subject: 's', content: 'c', confidence: -0.5)
|
|
26
|
+
expect(over.confidence).to eq(1.0)
|
|
27
|
+
expect(under.confidence).to eq(0.0)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'assigns a uuid id' do
|
|
31
|
+
expect(belief.id).to match(/\A[0-9a-f-]{36}\z/)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'records created_at' do
|
|
35
|
+
expect(belief.created_at).to be_a(Time)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe '#decay' do
|
|
40
|
+
it 'reduces confidence by BELIEF_DECAY' do
|
|
41
|
+
before = belief.confidence
|
|
42
|
+
belief.decay
|
|
43
|
+
expect(belief.confidence).to be_within(0.001).of(before - Legion::Extensions::Mentalizing::Helpers::Constants::BELIEF_DECAY)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'does not drop below BELIEF_FLOOR' do
|
|
47
|
+
20.times { belief.decay }
|
|
48
|
+
expect(belief.confidence).to be >= Legion::Extensions::Mentalizing::Helpers::Constants::BELIEF_FLOOR
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe '#reinforce' do
|
|
53
|
+
it 'increases confidence by CONFIDENCE_ALPHA by default' do
|
|
54
|
+
belief_low = described_class.new(agent_id: 'a', subject: 's', content: 'c', confidence: 0.3)
|
|
55
|
+
before = belief_low.confidence
|
|
56
|
+
belief_low.reinforce
|
|
57
|
+
expect(belief_low.confidence).to be_within(0.001).of(before + Legion::Extensions::Mentalizing::Helpers::Constants::CONFIDENCE_ALPHA)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'does not exceed 1.0' do
|
|
61
|
+
high = described_class.new(agent_id: 'a', subject: 's', content: 'c', confidence: 0.95)
|
|
62
|
+
high.reinforce(amount: 0.5)
|
|
63
|
+
expect(high.confidence).to eq(1.0)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'accepts custom amount' do
|
|
67
|
+
low = described_class.new(agent_id: 'a', subject: 's', content: 'c', confidence: 0.2)
|
|
68
|
+
low.reinforce(amount: 0.3)
|
|
69
|
+
expect(low.confidence).to be_within(0.001).of(0.5)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe '#label' do
|
|
74
|
+
it 'returns :certain for high confidence' do
|
|
75
|
+
high = described_class.new(agent_id: 'a', subject: 's', content: 'c', confidence: 0.9)
|
|
76
|
+
expect(high.label).to eq(:certain)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it 'returns :confident for 0.6..0.8 range' do
|
|
80
|
+
mid = described_class.new(agent_id: 'a', subject: 's', content: 'c', confidence: 0.7)
|
|
81
|
+
expect(mid.label).to eq(:confident)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'returns :uncertain for 0.4..0.6 range' do
|
|
85
|
+
unc = described_class.new(agent_id: 'a', subject: 's', content: 'c', confidence: 0.5)
|
|
86
|
+
expect(unc.label).to eq(:uncertain)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'returns :speculative for 0.2..0.4 range' do
|
|
90
|
+
spec_b = described_class.new(agent_id: 'a', subject: 's', content: 'c', confidence: 0.3)
|
|
91
|
+
expect(spec_b.label).to eq(:speculative)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it 'returns :unknown for very low confidence' do
|
|
95
|
+
low = described_class.new(agent_id: 'a', subject: 's', content: 'c', confidence: 0.1)
|
|
96
|
+
expect(low.label).to eq(:unknown)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
describe '#to_h' do
|
|
101
|
+
it 'returns a hash with all fields' do
|
|
102
|
+
h = belief.to_h
|
|
103
|
+
expect(h).to include(:id, :agent_id, :subject, :content, :confidence, :depth, :about_agent_id, :label, :created_at)
|
|
104
|
+
expect(h[:agent_id]).to eq('alice')
|
|
105
|
+
expect(h[:depth]).to eq(1)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::Mentalizing::Helpers::MentalModel do
|
|
4
|
+
subject(:model) { described_class.new }
|
|
5
|
+
|
|
6
|
+
describe '#attribute_belief' do
|
|
7
|
+
it 'stores a belief and returns a BeliefAttribution' do
|
|
8
|
+
belief = model.attribute_belief(agent_id: 'alice', subject: 'rain', content: 'yes', confidence: 0.8)
|
|
9
|
+
expect(belief).to be_a(Legion::Extensions::Mentalizing::Helpers::BeliefAttribution)
|
|
10
|
+
expect(belief.agent_id).to eq('alice')
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'stores separate beliefs per agent' do
|
|
14
|
+
model.attribute_belief(agent_id: 'alice', subject: 'rain', content: 'yes', confidence: 0.8)
|
|
15
|
+
model.attribute_belief(agent_id: 'bob', subject: 'rain', content: 'no', confidence: 0.6)
|
|
16
|
+
expect(model.beliefs_for(agent_id: 'alice').size).to eq(1)
|
|
17
|
+
expect(model.beliefs_for(agent_id: 'bob').size).to eq(1)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'caps depth at MAX_RECURSION_DEPTH' do
|
|
21
|
+
belief = model.attribute_belief(agent_id: 'alice', subject: 's', content: 'c', confidence: 0.5, depth: 99)
|
|
22
|
+
expect(belief.depth).to eq(Legion::Extensions::Mentalizing::Helpers::Constants::MAX_RECURSION_DEPTH)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'stores about_agent_id when provided' do
|
|
26
|
+
belief = model.attribute_belief(agent_id: 'alice', subject: 'trust', content: 'bob trusts me', confidence: 0.7, about_agent_id: 'bob')
|
|
27
|
+
expect(belief.about_agent_id).to eq('bob')
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe '#beliefs_for' do
|
|
32
|
+
it 'returns empty array for unknown agent' do
|
|
33
|
+
expect(model.beliefs_for(agent_id: 'nobody')).to eq([])
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'returns all beliefs for known agent' do
|
|
37
|
+
model.attribute_belief(agent_id: 'alice', subject: 'rain', content: 'yes', confidence: 0.8)
|
|
38
|
+
model.attribute_belief(agent_id: 'alice', subject: 'snow', content: 'no', confidence: 0.4)
|
|
39
|
+
expect(model.beliefs_for(agent_id: 'alice').size).to eq(2)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe '#beliefs_about' do
|
|
44
|
+
it 'returns beliefs where about_agent_id matches' do
|
|
45
|
+
model.attribute_belief(agent_id: 'alice', subject: 'my_trust', content: 'low', confidence: 0.4, about_agent_id: 'me')
|
|
46
|
+
model.attribute_belief(agent_id: 'bob', subject: 'my_trust', content: 'high', confidence: 0.9, about_agent_id: 'me')
|
|
47
|
+
model.attribute_belief(agent_id: 'carol', subject: 'other', content: 'n/a', confidence: 0.5, about_agent_id: 'other')
|
|
48
|
+
result = model.beliefs_about(about_agent_id: 'me')
|
|
49
|
+
expect(result.size).to eq(2)
|
|
50
|
+
expect(result.map(&:agent_id)).to contain_exactly('alice', 'bob')
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'returns empty array when no beliefs about agent' do
|
|
54
|
+
expect(model.beliefs_about(about_agent_id: 'nobody')).to eq([])
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe '#recursive_belief' do
|
|
59
|
+
it 'returns nil when no matching recursive belief exists' do
|
|
60
|
+
result = model.recursive_belief(agent_id: 'alice', about_agent_id: 'bob', subject: 'rain')
|
|
61
|
+
expect(result).to be_nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it 'returns the highest-confidence matching belief' do
|
|
65
|
+
model.attribute_belief(agent_id: 'alice', subject: 'rain', content: 'low', confidence: 0.4, about_agent_id: 'bob')
|
|
66
|
+
model.attribute_belief(agent_id: 'alice', subject: 'rain', content: 'high', confidence: 0.9, about_agent_id: 'bob')
|
|
67
|
+
result = model.recursive_belief(agent_id: 'alice', about_agent_id: 'bob', subject: 'rain')
|
|
68
|
+
expect(result.content).to eq('high')
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe '#project_self' do
|
|
73
|
+
it 'creates a depth-1 belief attributed to :self' do
|
|
74
|
+
belief = model.project_self(subject: 'plan', own_belief: 0.8, other_agent_id: 'bob')
|
|
75
|
+
expect(belief.agent_id).to eq(:self)
|
|
76
|
+
expect(belief.depth).to eq(1)
|
|
77
|
+
expect(belief.about_agent_id).to eq('bob')
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it 'discounts confidence by PROJECTION_DISCOUNT' do
|
|
81
|
+
discount = Legion::Extensions::Mentalizing::Helpers::Constants::PROJECTION_DISCOUNT
|
|
82
|
+
belief = model.project_self(subject: 'plan', own_belief: 1.0, other_agent_id: 'bob')
|
|
83
|
+
expect(belief.confidence).to be_within(0.001).of(discount)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it 'clamps discounted confidence to 1.0' do
|
|
87
|
+
belief = model.project_self(subject: 'plan', own_belief: 0.1, other_agent_id: 'bob')
|
|
88
|
+
expect(belief.confidence).to be >= 0.0
|
|
89
|
+
expect(belief.confidence).to be <= 1.0
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe '#alignment' do
|
|
94
|
+
it 'returns 0.0 when either agent has no beliefs on subject' do
|
|
95
|
+
model.attribute_belief(agent_id: 'alice', subject: 'rain', content: 'yes', confidence: 0.8)
|
|
96
|
+
expect(model.alignment(agent_a: 'alice', agent_b: 'bob', subject: 'rain')).to eq(0.0)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'returns 1.0 when both agents have identical confidence' do
|
|
100
|
+
model.attribute_belief(agent_id: 'alice', subject: 'rain', content: 'yes', confidence: 0.7)
|
|
101
|
+
model.attribute_belief(agent_id: 'bob', subject: 'rain', content: 'yes', confidence: 0.7)
|
|
102
|
+
expect(model.alignment(agent_a: 'alice', agent_b: 'bob', subject: 'rain')).to be_within(0.001).of(1.0)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it 'returns lower score for divergent beliefs' do
|
|
106
|
+
model.attribute_belief(agent_id: 'alice', subject: 'rain', content: 'high', confidence: 0.9)
|
|
107
|
+
model.attribute_belief(agent_id: 'bob', subject: 'rain', content: 'low', confidence: 0.1)
|
|
108
|
+
score = model.alignment(agent_a: 'alice', agent_b: 'bob', subject: 'rain')
|
|
109
|
+
expect(score).to be < 0.5
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
describe '#detect_false_belief' do
|
|
114
|
+
it 'returns false_belief: false when belief matches reality' do
|
|
115
|
+
model.attribute_belief(agent_id: 'alice', subject: 'weather', content: 'sunny', confidence: 0.8)
|
|
116
|
+
result = model.detect_false_belief(agent_id: 'alice', subject: 'weather', reality: 'sunny')
|
|
117
|
+
expect(result[:false_belief]).to be false
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it 'returns false_belief: true when belief contradicts reality' do
|
|
121
|
+
model.attribute_belief(agent_id: 'alice', subject: 'weather', content: 'sunny', confidence: 0.8)
|
|
122
|
+
result = model.detect_false_belief(agent_id: 'alice', subject: 'weather', reality: 'raining')
|
|
123
|
+
expect(result[:false_belief]).to be true
|
|
124
|
+
expect(result[:held_belief]).to eq('sunny')
|
|
125
|
+
expect(result[:reality]).to eq('raining')
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it 'returns no_beliefs reason when agent has no beliefs on subject' do
|
|
129
|
+
result = model.detect_false_belief(agent_id: 'nobody', subject: 'weather', reality: 'sunny')
|
|
130
|
+
expect(result[:false_belief]).to be false
|
|
131
|
+
expect(result[:reason]).to eq(:no_beliefs)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
describe '#decay_all' do
|
|
136
|
+
it 'reduces confidence on all beliefs' do
|
|
137
|
+
model.attribute_belief(agent_id: 'alice', subject: 'rain', content: 'yes', confidence: 0.8)
|
|
138
|
+
before = model.beliefs_for(agent_id: 'alice').first.confidence
|
|
139
|
+
model.decay_all
|
|
140
|
+
after = model.beliefs_for(agent_id: 'alice').first&.confidence
|
|
141
|
+
expect(after).to be_nil.or be < before
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it 'prunes beliefs at or below BELIEF_FLOOR' do
|
|
145
|
+
model.attribute_belief(agent_id: 'alice', subject: 'rain', content: 'yes',
|
|
146
|
+
confidence: Legion::Extensions::Mentalizing::Helpers::Constants::BELIEF_FLOOR)
|
|
147
|
+
model.decay_all
|
|
148
|
+
expect(model.beliefs_for(agent_id: 'alice')).to be_empty
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
describe '#remove_agent' do
|
|
153
|
+
it 'removes all beliefs for the agent' do
|
|
154
|
+
model.attribute_belief(agent_id: 'alice', subject: 'rain', content: 'yes', confidence: 0.8)
|
|
155
|
+
model.remove_agent(agent_id: 'alice')
|
|
156
|
+
expect(model.beliefs_for(agent_id: 'alice')).to eq([])
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
describe '#agent_count and #belief_count' do
|
|
161
|
+
it 'tracks counts correctly' do
|
|
162
|
+
expect(model.agent_count).to eq(0)
|
|
163
|
+
expect(model.belief_count).to eq(0)
|
|
164
|
+
model.attribute_belief(agent_id: 'alice', subject: 'rain', content: 'yes', confidence: 0.8)
|
|
165
|
+
model.attribute_belief(agent_id: 'bob', subject: 'snow', content: 'no', confidence: 0.5)
|
|
166
|
+
expect(model.agent_count).to eq(2)
|
|
167
|
+
expect(model.belief_count).to eq(2)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
describe '#to_h' do
|
|
172
|
+
it 'serializes all beliefs as hashes' do
|
|
173
|
+
model.attribute_belief(agent_id: 'alice', subject: 'rain', content: 'yes', confidence: 0.8)
|
|
174
|
+
h = model.to_h
|
|
175
|
+
expect(h).to have_key('alice')
|
|
176
|
+
expect(h['alice'].first).to include(:agent_id, :subject, :confidence)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/mentalizing/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Mentalizing::Runners::Mentalizing do
|
|
6
|
+
let(:client) { Legion::Extensions::Mentalizing::Client.new }
|
|
7
|
+
|
|
8
|
+
describe '#attribute_belief' do
|
|
9
|
+
it 'returns attributed: true with belief hash' do
|
|
10
|
+
result = client.attribute_belief(agent_id: 'alice', subject: 'rain', content: 'yes', confidence: 0.8)
|
|
11
|
+
expect(result[:attributed]).to be true
|
|
12
|
+
expect(result[:belief]).to include(:id, :agent_id, :subject, :content, :confidence, :depth)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'defaults confidence to DEFAULT_CONFIDENCE when not provided' do
|
|
16
|
+
result = client.attribute_belief(agent_id: 'alice', subject: 'snow', content: 'maybe')
|
|
17
|
+
expect(result[:belief][:confidence]).to eq(Legion::Extensions::Mentalizing::Helpers::Constants::DEFAULT_CONFIDENCE)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'caps depth at MAX_RECURSION_DEPTH' do
|
|
21
|
+
result = client.attribute_belief(agent_id: 'alice', subject: 's', content: 'c', depth: 100)
|
|
22
|
+
expect(result[:belief][:depth]).to eq(Legion::Extensions::Mentalizing::Helpers::Constants::MAX_RECURSION_DEPTH)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'accepts depth 0 (first-order)' do
|
|
26
|
+
result = client.attribute_belief(agent_id: 'alice', subject: 'trust', content: 'high', confidence: 0.9, depth: 0)
|
|
27
|
+
expect(result[:belief][:depth]).to eq(0)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'stores about_agent_id for second-order beliefs' do
|
|
31
|
+
result = client.attribute_belief(agent_id: 'alice', subject: 'plan', content: 'I will act', confidence: 0.7,
|
|
32
|
+
depth: 1, about_agent_id: 'bob')
|
|
33
|
+
expect(result[:belief][:about_agent_id]).to eq('bob')
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe '#project_belief' do
|
|
38
|
+
it 'returns projected: true with discounted confidence' do
|
|
39
|
+
result = client.project_belief(subject: 'plan', own_belief: 0.8, other_agent_id: 'bob')
|
|
40
|
+
expect(result[:projected]).to be true
|
|
41
|
+
discount = Legion::Extensions::Mentalizing::Helpers::Constants::PROJECTION_DISCOUNT
|
|
42
|
+
expect(result[:belief][:confidence]).to be_within(0.001).of(0.8 * discount)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'creates depth-1 belief about the other agent' do
|
|
46
|
+
result = client.project_belief(subject: 'plan', own_belief: 1.0, other_agent_id: 'carol')
|
|
47
|
+
expect(result[:belief][:depth]).to eq(1)
|
|
48
|
+
expect(result[:belief][:about_agent_id]).to eq('carol')
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe '#check_alignment' do
|
|
53
|
+
it 'returns alignment score of 0.0 when no shared beliefs' do
|
|
54
|
+
result = client.check_alignment(agent_a: 'alice', agent_b: 'bob', subject: 'rain')
|
|
55
|
+
expect(result[:alignment]).to eq(0.0)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'returns alignment near 1.0 for identical confidences' do
|
|
59
|
+
client.attribute_belief(agent_id: 'alice', subject: 'rain', content: 'yes', confidence: 0.7)
|
|
60
|
+
client.attribute_belief(agent_id: 'bob', subject: 'rain', content: 'yes', confidence: 0.7)
|
|
61
|
+
result = client.check_alignment(agent_a: 'alice', agent_b: 'bob', subject: 'rain')
|
|
62
|
+
expect(result[:alignment]).to be_within(0.001).of(1.0)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'returns subject in result' do
|
|
66
|
+
result = client.check_alignment(agent_a: 'alice', agent_b: 'bob', subject: 'rain')
|
|
67
|
+
expect(result[:subject]).to eq('rain')
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
describe '#detect_false_belief' do
|
|
72
|
+
it 'detects false belief when agent belief contradicts reality' do
|
|
73
|
+
client.attribute_belief(agent_id: 'alice', subject: 'weather', content: 'sunny', confidence: 0.9)
|
|
74
|
+
result = client.detect_false_belief(agent_id: 'alice', subject: 'weather', reality: 'raining')
|
|
75
|
+
expect(result[:false_belief]).to be true
|
|
76
|
+
expect(result[:held_belief]).to eq('sunny')
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it 'returns false_belief: false when belief matches reality' do
|
|
80
|
+
client.attribute_belief(agent_id: 'alice', subject: 'weather', content: 'sunny', confidence: 0.9)
|
|
81
|
+
result = client.detect_false_belief(agent_id: 'alice', subject: 'weather', reality: 'sunny')
|
|
82
|
+
expect(result[:false_belief]).to be false
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'returns no_beliefs reason when agent unknown' do
|
|
86
|
+
result = client.detect_false_belief(agent_id: 'nobody', subject: 'weather', reality: 'sunny')
|
|
87
|
+
expect(result[:reason]).to eq(:no_beliefs)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
describe '#beliefs_for_agent' do
|
|
92
|
+
it 'returns empty beliefs for unknown agent' do
|
|
93
|
+
result = client.beliefs_for_agent(agent_id: 'nobody')
|
|
94
|
+
expect(result[:beliefs]).to eq([])
|
|
95
|
+
expect(result[:count]).to eq(0)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'returns beliefs for known agent' do
|
|
99
|
+
client.attribute_belief(agent_id: 'alice', subject: 'rain', content: 'yes', confidence: 0.8)
|
|
100
|
+
result = client.beliefs_for_agent(agent_id: 'alice')
|
|
101
|
+
expect(result[:count]).to eq(1)
|
|
102
|
+
expect(result[:beliefs].first[:agent_id]).to eq('alice')
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
describe '#beliefs_about_agent' do
|
|
107
|
+
it 'returns beliefs where about_agent_id matches' do
|
|
108
|
+
client.attribute_belief(agent_id: 'alice', subject: 'my_reliability', content: 'high', confidence: 0.8, about_agent_id: 'me')
|
|
109
|
+
client.attribute_belief(agent_id: 'bob', subject: 'my_reliability', content: 'low', confidence: 0.3, about_agent_id: 'me')
|
|
110
|
+
result = client.beliefs_about_agent(about_agent_id: 'me')
|
|
111
|
+
expect(result[:count]).to eq(2)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it 'returns empty when no beliefs about agent' do
|
|
115
|
+
result = client.beliefs_about_agent(about_agent_id: 'nobody')
|
|
116
|
+
expect(result[:count]).to eq(0)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
describe '#recursive_belief_lookup' do
|
|
121
|
+
it 'returns found: false when no match' do
|
|
122
|
+
result = client.recursive_belief_lookup(agent_id: 'alice', about_agent_id: 'bob', subject: 'rain')
|
|
123
|
+
expect(result[:found]).to be false
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it 'returns found: true with belief when match exists' do
|
|
127
|
+
client.attribute_belief(agent_id: 'alice', subject: 'rain', content: 'bob thinks rain', confidence: 0.75, about_agent_id: 'bob')
|
|
128
|
+
result = client.recursive_belief_lookup(agent_id: 'alice', about_agent_id: 'bob', subject: 'rain')
|
|
129
|
+
expect(result[:found]).to be true
|
|
130
|
+
expect(result[:belief][:content]).to eq('bob thinks rain')
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
describe '#update_mentalizing' do
|
|
135
|
+
it 'returns decayed: true' do
|
|
136
|
+
result = client.update_mentalizing
|
|
137
|
+
expect(result[:decayed]).to be true
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it 'returns agent and belief counts' do
|
|
141
|
+
client.attribute_belief(agent_id: 'alice', subject: 'rain', content: 'yes', confidence: 0.8)
|
|
142
|
+
result = client.update_mentalizing
|
|
143
|
+
expect(result).to have_key(:agents)
|
|
144
|
+
expect(result).to have_key(:beliefs)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
describe '#mentalizing_stats' do
|
|
149
|
+
it 'returns agents and beliefs counts' do
|
|
150
|
+
result = client.mentalizing_stats
|
|
151
|
+
expect(result).to include(:agents, :beliefs)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it 'reflects current state' do
|
|
155
|
+
client.attribute_belief(agent_id: 'alice', subject: 'rain', content: 'yes', confidence: 0.8)
|
|
156
|
+
client.attribute_belief(agent_id: 'bob', subject: 'snow', content: 'no', confidence: 0.5)
|
|
157
|
+
result = client.mentalizing_stats
|
|
158
|
+
expect(result[:agents]).to eq(2)
|
|
159
|
+
expect(result[:beliefs]).to eq(2)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Logging
|
|
7
|
+
def self.debug(_msg); end
|
|
8
|
+
def self.info(_msg); end
|
|
9
|
+
def self.warn(_msg); end
|
|
10
|
+
def self.error(_msg); end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
require 'legion/extensions/mentalizing'
|
|
15
|
+
|
|
16
|
+
RSpec.configure do |config|
|
|
17
|
+
config.example_status_persistence_file_path = '.rspec_status'
|
|
18
|
+
config.disable_monkey_patching!
|
|
19
|
+
config.expect_with(:rspec) { |c| c.syntax = :expect }
|
|
20
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lex-mentalizing
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Esity
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: legion-gaia
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
description: Second-order Theory of Mind for brain-modeled agentic AI. Recursive belief
|
|
27
|
+
attribution, false-belief detection, and social alignment modeling.
|
|
28
|
+
email:
|
|
29
|
+
- matthewdiverson@gmail.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- Gemfile
|
|
35
|
+
- lex-mentalizing.gemspec
|
|
36
|
+
- lib/legion/extensions/mentalizing.rb
|
|
37
|
+
- lib/legion/extensions/mentalizing/actors/decay.rb
|
|
38
|
+
- lib/legion/extensions/mentalizing/client.rb
|
|
39
|
+
- lib/legion/extensions/mentalizing/helpers/belief_attribution.rb
|
|
40
|
+
- lib/legion/extensions/mentalizing/helpers/constants.rb
|
|
41
|
+
- lib/legion/extensions/mentalizing/helpers/mental_model.rb
|
|
42
|
+
- lib/legion/extensions/mentalizing/runners/mentalizing.rb
|
|
43
|
+
- lib/legion/extensions/mentalizing/version.rb
|
|
44
|
+
- spec/legion/extensions/mentalizing/client_spec.rb
|
|
45
|
+
- spec/legion/extensions/mentalizing/helpers/belief_attribution_spec.rb
|
|
46
|
+
- spec/legion/extensions/mentalizing/helpers/mental_model_spec.rb
|
|
47
|
+
- spec/legion/extensions/mentalizing/runners/mentalizing_spec.rb
|
|
48
|
+
- spec/spec_helper.rb
|
|
49
|
+
homepage: https://github.com/LegionIO/lex-mentalizing
|
|
50
|
+
licenses:
|
|
51
|
+
- MIT
|
|
52
|
+
metadata:
|
|
53
|
+
homepage_uri: https://github.com/LegionIO/lex-mentalizing
|
|
54
|
+
source_code_uri: https://github.com/LegionIO/lex-mentalizing
|
|
55
|
+
documentation_uri: https://github.com/LegionIO/lex-mentalizing
|
|
56
|
+
changelog_uri: https://github.com/LegionIO/lex-mentalizing
|
|
57
|
+
bug_tracker_uri: https://github.com/LegionIO/lex-mentalizing/issues
|
|
58
|
+
rubygems_mfa_required: 'true'
|
|
59
|
+
rdoc_options: []
|
|
60
|
+
require_paths:
|
|
61
|
+
- lib
|
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '3.4'
|
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
68
|
+
requirements:
|
|
69
|
+
- - ">="
|
|
70
|
+
- !ruby/object:Gem::Version
|
|
71
|
+
version: '0'
|
|
72
|
+
requirements: []
|
|
73
|
+
rubygems_version: 3.6.9
|
|
74
|
+
specification_version: 4
|
|
75
|
+
summary: LEX Mentalizing
|
|
76
|
+
test_files: []
|