lex-trust 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ee93b725a6f165775e614f7dff64a7fe24d9ed596ca3782b22710df86895e763
4
+ data.tar.gz: bdab508af3b14b60d6821c572bbaaefa6ba59c18f79fd460ab90630319101710
5
+ SHA512:
6
+ metadata.gz: 34b5000f12abe1eba146fca0bdb2dfd3badf793330e2d488111ceef7f238bd67a0d0f5a216c935e9abd7bb3e4458090c0e3403fad651dac266e094cc2849859b
7
+ data.tar.gz: 6c93059d3e4685de0d9607831104a58ca71ca3bf8aa1e339336ec5cc9133e557b97e2e5351ecb28e06fc93e01f99b2c371f3fc9f5c3ae1863cad36ee433df6e7
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ gem 'rspec', '~> 3.13'
8
+ gem 'rubocop', '~> 1.75', require: false
9
+ gem 'rubocop-rspec', require: false
10
+
11
+ gem 'sequel', '>= 5.70'
12
+ gem 'sqlite3', '>= 2.0'
data/lex-trust.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/legion/extensions/trust/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lex-trust'
7
+ spec.version = Legion::Extensions::Trust::VERSION
8
+ spec.authors = ['Esity']
9
+ spec.email = ['matthewdiverson@gmail.com']
10
+
11
+ spec.summary = 'LEX Trust'
12
+ spec.description = 'Emergent domain-specific trust modeling for brain-modeled agentic AI'
13
+ spec.homepage = 'https://github.com/LegionIO/lex-trust'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 3.4'
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-trust'
19
+ spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-trust'
20
+ spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-trust'
21
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-trust/issues'
22
+ spec.metadata['rubygems_mfa_required'] = 'true'
23
+
24
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
25
+ Dir.glob('{lib,spec}/**/*') + %w[lex-trust.gemspec Gemfile]
26
+ end
27
+ spec.require_paths = ['lib']
28
+ spec.add_development_dependency 'sequel', '>= 5.70'
29
+ spec.add_development_dependency 'sqlite3', '>= 2.0'
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 Trust
8
+ module Actor
9
+ class Decay < Legion::Extensions::Actors::Every
10
+ def runner_class
11
+ Legion::Extensions::Trust::Runners::Trust
12
+ end
13
+
14
+ def runner_function
15
+ 'decay_trust'
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,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/trust/helpers/trust_model'
4
+ require 'legion/extensions/trust/helpers/trust_map'
5
+ require 'legion/extensions/trust/runners/trust'
6
+
7
+ module Legion
8
+ module Extensions
9
+ module Trust
10
+ class Client
11
+ include Runners::Trust
12
+
13
+ def initialize(**)
14
+ @trust_map = Helpers::TrustMap.new
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :trust_map
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Trust
6
+ module Helpers
7
+ class TrustMap
8
+ attr_reader :entries
9
+
10
+ def initialize
11
+ @entries = {} # key: "agent_id:domain"
12
+ load_from_local
13
+ end
14
+
15
+ def get(agent_id, domain: :general)
16
+ @entries[key(agent_id, domain)]
17
+ end
18
+
19
+ def get_or_create(agent_id, domain: :general)
20
+ @entries[key(agent_id, domain)] ||= TrustModel.new_trust_entry(agent_id: agent_id, domain: domain)
21
+ end
22
+
23
+ def record_interaction(agent_id, positive:, domain: :general)
24
+ entry = get_or_create(agent_id, domain: domain)
25
+ entry[:interaction_count] += 1
26
+ entry[:last_interaction] = Time.now.utc
27
+
28
+ if positive
29
+ entry[:positive_count] += 1
30
+ TrustModel::TRUST_DIMENSIONS.each do |dim|
31
+ entry[:dimensions][dim] = TrustModel.clamp(entry[:dimensions][dim] + TrustModel::TRUST_REINFORCEMENT)
32
+ end
33
+ else
34
+ entry[:negative_count] += 1
35
+ TrustModel::TRUST_DIMENSIONS.each do |dim|
36
+ entry[:dimensions][dim] = TrustModel.clamp(entry[:dimensions][dim] - TrustModel::TRUST_PENALTY)
37
+ end
38
+ end
39
+
40
+ entry[:composite] = TrustModel.composite_score(entry[:dimensions])
41
+ entry
42
+ end
43
+
44
+ def reinforce_dimension(agent_id, dimension:, domain: :general, amount: TrustModel::TRUST_REINFORCEMENT)
45
+ entry = get_or_create(agent_id, domain: domain)
46
+ return unless TrustModel::TRUST_DIMENSIONS.include?(dimension)
47
+
48
+ entry[:dimensions][dimension] = TrustModel.clamp(entry[:dimensions][dimension] + amount)
49
+ entry[:composite] = TrustModel.composite_score(entry[:dimensions])
50
+ end
51
+
52
+ def decay_all
53
+ decayed = 0
54
+ @entries.each_value do |entry|
55
+ TrustModel::TRUST_DIMENSIONS.each do |dim|
56
+ old = entry[:dimensions][dim]
57
+ entry[:dimensions][dim] = TrustModel.clamp(old - TrustModel::TRUST_DECAY_RATE)
58
+ end
59
+ entry[:composite] = TrustModel.composite_score(entry[:dimensions])
60
+ decayed += 1
61
+ end
62
+ decayed
63
+ end
64
+
65
+ def trusted_agents(domain: :general, min_trust: TrustModel::TRUST_CONSIDER_THRESHOLD)
66
+ @entries.values
67
+ .select { |e| e[:domain] == domain && e[:composite] >= min_trust }
68
+ .sort_by { |e| -e[:composite] }
69
+ end
70
+
71
+ def delegatable_agents(domain: :general)
72
+ trusted_agents(domain: domain, min_trust: TrustModel::TRUST_DELEGATE_THRESHOLD)
73
+ end
74
+
75
+ def count
76
+ @entries.size
77
+ end
78
+
79
+ def save_to_local
80
+ return unless defined?(Legion::Data::Local) && Legion::Data::Local.connected?
81
+
82
+ dataset = Legion::Data::Local.connection[:trust_entries]
83
+
84
+ @entries.each_value do |entry|
85
+ row = {
86
+ agent_id: entry[:agent_id].to_s,
87
+ domain: entry[:domain].to_s,
88
+ reliability: entry[:dimensions][:reliability],
89
+ competence: entry[:dimensions][:competence],
90
+ integrity: entry[:dimensions][:integrity],
91
+ benevolence: entry[:dimensions][:benevolence],
92
+ composite: entry[:composite],
93
+ interaction_count: entry[:interaction_count],
94
+ positive_count: entry[:positive_count],
95
+ negative_count: entry[:negative_count],
96
+ last_interaction: entry[:last_interaction],
97
+ created_at: entry[:created_at]
98
+ }
99
+ existing = dataset.where(agent_id: row[:agent_id], domain: row[:domain]).first
100
+ if existing
101
+ dataset.where(agent_id: row[:agent_id], domain: row[:domain])
102
+ .update(row.except(:agent_id, :domain))
103
+ else
104
+ dataset.insert(row)
105
+ end
106
+ end
107
+
108
+ # Remove DB rows for entries no longer in memory
109
+ memory_pairs = @entries.values.map { |e| [e[:agent_id].to_s, e[:domain].to_s] }
110
+ dataset.each do |row|
111
+ pair = [row[:agent_id], row[:domain]]
112
+ dataset.where(agent_id: pair[0], domain: pair[1]).delete unless memory_pairs.include?(pair)
113
+ end
114
+ rescue StandardError => e
115
+ Legion::Logging.warn "[trust] save_to_local failed: #{e.message}" if defined?(Legion::Logging)
116
+ end
117
+
118
+ def load_from_local
119
+ return unless defined?(Legion::Data::Local) && Legion::Data::Local.connected?
120
+
121
+ Legion::Data::Local.connection[:trust_entries].each do |row|
122
+ agent_id = row[:agent_id]
123
+ domain_str = row[:domain]
124
+ domain_val = domain_str.to_sym
125
+ entry_key = "#{agent_id}:#{domain_str}"
126
+ @entries[entry_key] = {
127
+ agent_id: agent_id,
128
+ domain: domain_val,
129
+ dimensions: {
130
+ reliability: row[:reliability].to_f,
131
+ competence: row[:competence].to_f,
132
+ integrity: row[:integrity].to_f,
133
+ benevolence: row[:benevolence].to_f
134
+ },
135
+ composite: row[:composite].to_f,
136
+ interaction_count: row[:interaction_count].to_i,
137
+ positive_count: row[:positive_count].to_i,
138
+ negative_count: row[:negative_count].to_i,
139
+ last_interaction: row[:last_interaction],
140
+ created_at: row[:created_at]
141
+ }
142
+ end
143
+ rescue StandardError => e
144
+ Legion::Logging.warn "[trust] load_from_local failed: #{e.message}" if defined?(Legion::Logging)
145
+ end
146
+
147
+ private
148
+
149
+ def key(agent_id, domain)
150
+ "#{agent_id}:#{domain}"
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Trust
6
+ module Helpers
7
+ module TrustModel
8
+ # Trust is emergent, multidimensional, domain-specific (spec design-decisions-v5 #4)
9
+ TRUST_DIMENSIONS = %i[reliability competence integrity benevolence].freeze
10
+
11
+ # Thresholds
12
+ TRUST_CONSIDER_THRESHOLD = 0.3 # minimum trust to consider agent's input
13
+ TRUST_DELEGATE_THRESHOLD = 0.7 # minimum trust to delegate actions
14
+ TRUST_DECAY_RATE = 0.005 # per-cycle decay
15
+ TRUST_REINFORCEMENT = 0.05 # per positive interaction
16
+ TRUST_PENALTY = 0.15 # per negative interaction (asymmetric)
17
+ NEUTRAL_TRUST = 0.3 # starting trust for new agents
18
+
19
+ module_function
20
+
21
+ def new_trust_entry(agent_id:, domain: :general)
22
+ {
23
+ agent_id: agent_id,
24
+ domain: domain,
25
+ dimensions: TRUST_DIMENSIONS.to_h { |d| [d, NEUTRAL_TRUST] },
26
+ composite: NEUTRAL_TRUST,
27
+ interaction_count: 0,
28
+ positive_count: 0,
29
+ negative_count: 0,
30
+ last_interaction: nil,
31
+ created_at: Time.now.utc
32
+ }
33
+ end
34
+
35
+ def composite_score(dimensions)
36
+ return 0.0 if dimensions.empty?
37
+
38
+ dimensions.values.sum / dimensions.size
39
+ end
40
+
41
+ def clamp(value, min = 0.0, max = 1.0)
42
+ value.clamp(min, max)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ create_table(:trust_entries) do
6
+ primary_key :id
7
+ String :agent_id, null: false
8
+ String :domain, null: false
9
+ Float :reliability, default: 0.3
10
+ Float :competence, default: 0.3
11
+ Float :integrity, default: 0.3
12
+ Float :benevolence, default: 0.3
13
+ Float :composite, default: 0.3
14
+ Integer :interaction_count, default: 0
15
+ Integer :positive_count, default: 0
16
+ Integer :negative_count, default: 0
17
+ DateTime :last_interaction
18
+ DateTime :created_at, null: false
19
+ unique %i[agent_id domain]
20
+ index [:agent_id]
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Trust
6
+ module Runners
7
+ module Trust
8
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
9
+ Legion::Extensions::Helpers.const_defined?(:Lex)
10
+
11
+ def get_trust(agent_id:, domain: :general, **)
12
+ entry = trust_map.get(agent_id, domain: domain)
13
+ if entry
14
+ Legion::Logging.debug "[trust] get agent=#{agent_id} domain=#{domain} composite=#{entry[:composite].round(2)}"
15
+ { found: true, trust: entry }
16
+ else
17
+ Legion::Logging.debug "[trust] get agent=#{agent_id} domain=#{domain} not found"
18
+ { found: false, agent_id: agent_id, domain: domain }
19
+ end
20
+ end
21
+
22
+ def record_trust_interaction(agent_id:, positive:, domain: :general, **)
23
+ entry = trust_map.record_interaction(agent_id, domain: domain, positive: positive)
24
+ msg = "[trust] interaction: agent=#{agent_id} domain=#{domain} positive=#{positive} " \
25
+ "composite=#{entry[:composite].round(2)} total=#{entry[:interaction_count]}"
26
+ Legion::Logging.info msg
27
+ {
28
+ agent_id: agent_id,
29
+ domain: domain,
30
+ positive: positive,
31
+ composite: entry[:composite],
32
+ interactions: entry[:interaction_count]
33
+ }
34
+ end
35
+
36
+ def reinforce_trust_dimension(agent_id:, dimension:, domain: :general, amount: nil, **)
37
+ amt = amount || Helpers::TrustModel::TRUST_REINFORCEMENT
38
+ trust_map.reinforce_dimension(agent_id, domain: domain, dimension: dimension, amount: amt)
39
+ entry = trust_map.get(agent_id, domain: domain)
40
+ Legion::Logging.debug "[trust] reinforce: agent=#{agent_id} dimension=#{dimension} amount=#{amt} composite=#{entry[:composite].round(2)}"
41
+ { agent_id: agent_id, domain: domain, dimension: dimension, composite: entry[:composite] }
42
+ end
43
+
44
+ def decay_trust(**)
45
+ decayed = trust_map.decay_all
46
+ Legion::Logging.debug "[trust] decay cycle: entries_updated=#{decayed}"
47
+ { decayed: decayed }
48
+ end
49
+
50
+ def trusted_agents(domain: :general, min_trust: nil, **)
51
+ min = min_trust || Helpers::TrustModel::TRUST_CONSIDER_THRESHOLD
52
+ agents = trust_map.trusted_agents(domain: domain, min_trust: min)
53
+ Legion::Logging.debug "[trust] trusted agents: domain=#{domain} min=#{min} count=#{agents.size}"
54
+ { agents: agents, count: agents.size }
55
+ end
56
+
57
+ def delegatable_agents(domain: :general, **)
58
+ agents = trust_map.delegatable_agents(domain: domain)
59
+ Legion::Logging.debug "[trust] delegatable agents: domain=#{domain} count=#{agents.size}"
60
+ { agents: agents, count: agents.size }
61
+ end
62
+
63
+ def trust_status(**)
64
+ { total_entries: trust_map.count }
65
+ end
66
+
67
+ private
68
+
69
+ def trust_map
70
+ @trust_map ||= Helpers::TrustMap.new
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Trust
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/trust/version'
4
+ require 'legion/extensions/trust/helpers/trust_model'
5
+ require 'legion/extensions/trust/helpers/trust_map'
6
+ require 'legion/extensions/trust/runners/trust'
7
+
8
+ module Legion
9
+ module Extensions
10
+ module Trust
11
+ extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
12
+ end
13
+ end
14
+ end
15
+
16
+ if defined?(Legion::Data::Local)
17
+ Legion::Data::Local.register_migrations(
18
+ name: :trust,
19
+ path: File.join(__dir__, 'trust', 'local_migrations')
20
+ )
21
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Stub the framework actor base class since legionio gem is not available in test
4
+ module Legion
5
+ module Extensions
6
+ module Actors
7
+ class Every # rubocop:disable Lint/EmptyClass
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ # Intercept the require in the actor file so it doesn't fail
14
+ $LOADED_FEATURES << 'legion/extensions/actors/every'
15
+
16
+ require 'legion/extensions/trust/actors/decay'
17
+
18
+ RSpec.describe Legion::Extensions::Trust::Actor::Decay do
19
+ subject(:actor) { described_class.new }
20
+
21
+ describe '#runner_class' do
22
+ it 'returns the Trust module' do
23
+ expect(actor.runner_class).to eq(Legion::Extensions::Trust::Runners::Trust)
24
+ end
25
+ end
26
+
27
+ describe '#runner_function' do
28
+ it 'returns decay_trust' do
29
+ expect(actor.runner_function).to eq('decay_trust')
30
+ end
31
+ end
32
+
33
+ describe '#time' do
34
+ it 'returns 300 seconds' do
35
+ expect(actor.time).to eq(300)
36
+ end
37
+ end
38
+
39
+ describe '#run_now?' do
40
+ it 'returns false' do
41
+ expect(actor.run_now?).to be false
42
+ end
43
+ end
44
+
45
+ describe '#use_runner?' do
46
+ it 'returns false' do
47
+ expect(actor.use_runner?).to be false
48
+ end
49
+ end
50
+
51
+ describe '#check_subtask?' do
52
+ it 'returns false' do
53
+ expect(actor.check_subtask?).to be false
54
+ end
55
+ end
56
+
57
+ describe '#generate_task?' do
58
+ it 'returns false' do
59
+ expect(actor.generate_task?).to be false
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/trust/client'
4
+
5
+ RSpec.describe Legion::Extensions::Trust::Client do
6
+ let(:client) { described_class.new }
7
+
8
+ it 'responds to trust runner methods' do
9
+ expect(client).to respond_to(:get_trust)
10
+ expect(client).to respond_to(:record_trust_interaction)
11
+ expect(client).to respond_to(:reinforce_trust_dimension)
12
+ expect(client).to respond_to(:decay_trust)
13
+ expect(client).to respond_to(:trusted_agents)
14
+ expect(client).to respond_to(:delegatable_agents)
15
+ expect(client).to respond_to(:trust_status)
16
+ end
17
+ end