lex-distributed-cognition 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: de4b8ad54804ba25b1f565e8e1e3362f46ca3ada01425b0991f87c0ddc54ef3c
4
+ data.tar.gz: 03ea79bc086c9b67e3b80a35da37a894751cd7fccf2e62512564c033db10bb7c
5
+ SHA512:
6
+ metadata.gz: a72cc5623782cb85f4b46762dcd5980759cdc250c0ef39eac4880dc01e0061a1b92bc0e6876f31a3772f9a7dcc6265dfee7c4b7d9af9712671e8b431a9f034ff
7
+ data.tar.gz: f284c3521ffa9995aa6594738613e3d441f731a50188b2328da3e140ac511c62317cb31de1eb60db1b2deb7d7ae158ebd14c65a78517ba0818c05e037979494e
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ group :development, :test do
8
+ gem 'rspec', '~> 3.13'
9
+ gem 'rubocop', '~> 1.75'
10
+ gem 'rubocop-rspec'
11
+ end
12
+
13
+ gem 'legion-gaia', path: '../../legion-gaia'
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/legion/extensions/distributed_cognition/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lex-distributed-cognition'
7
+ spec.version = Legion::Extensions::DistributedCognition::VERSION
8
+ spec.authors = ['Esity']
9
+ spec.email = ['matthewdiverson@gmail.com']
10
+
11
+ spec.summary = 'LEX Distributed Cognition'
12
+ spec.description = "Hutchins' distributed cognition — cognitive processes spread across " \
13
+ 'agents, artifacts, and environment for brain-modeled agentic AI'
14
+ spec.homepage = 'https://github.com/LegionIO/lex-distributed-cognition'
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-distributed-cognition'
20
+ spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-distributed-cognition'
21
+ spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-distributed-cognition'
22
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-distributed-cognition/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-distributed-cognition.gemspec Gemfile]
27
+ end
28
+ spec.require_paths = ['lib']
29
+ spec.add_development_dependency 'legion-gaia'
30
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module DistributedCognition
6
+ class Client
7
+ include Runners::DistributedCognition
8
+
9
+ def initialize(engine: nil)
10
+ @engine = engine || Helpers::DistributionEngine.new
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module DistributedCognition
6
+ module Helpers
7
+ module Constants
8
+ MAX_PARTICIPANTS = 100
9
+ MAX_ARTIFACTS = 200
10
+ MAX_CONTRIBUTIONS = 1000
11
+ MAX_HISTORY = 300
12
+
13
+ DEFAULT_RELIABILITY = 0.5
14
+ RELIABILITY_FLOOR = 0.0
15
+ RELIABILITY_CEILING = 1.0
16
+
17
+ REINFORCEMENT_RATE = 0.1
18
+ PENALTY_RATE = 0.15
19
+ DECAY_RATE = 0.02
20
+ STALE_THRESHOLD = 120
21
+
22
+ PARTICIPANT_TYPES = %i[agent artifact environment].freeze
23
+
24
+ CONTRIBUTION_TYPES = %i[
25
+ computation storage retrieval transformation communication
26
+ ].freeze
27
+
28
+ RELIABILITY_LABELS = {
29
+ (0.8..) => :highly_reliable,
30
+ (0.6...0.8) => :reliable,
31
+ (0.4...0.6) => :moderate,
32
+ (0.2...0.4) => :unreliable,
33
+ (..0.2) => :failing
34
+ }.freeze
35
+
36
+ DISTRIBUTION_LABELS = {
37
+ (0.8..) => :fully_distributed,
38
+ (0.6...0.8) => :well_distributed,
39
+ (0.4...0.6) => :partially_distributed,
40
+ (0.2...0.4) => :concentrated,
41
+ (..0.2) => :centralized
42
+ }.freeze
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module DistributedCognition
6
+ module Helpers
7
+ class DistributionEngine
8
+ include Constants
9
+
10
+ attr_reader :history
11
+
12
+ def initialize
13
+ @participants = {}
14
+ @contributions = []
15
+ @history = []
16
+ end
17
+
18
+ def register_participant(name:, participant_type:, domain:, capabilities: [])
19
+ unless PARTICIPANT_TYPES.include?(participant_type)
20
+ return { success: false, reason: :invalid_participant_type }
21
+ end
22
+
23
+ evict_oldest if @participants.size >= MAX_PARTICIPANTS
24
+
25
+ participant = Participant.new(
26
+ name: name,
27
+ participant_type: participant_type,
28
+ domain: domain,
29
+ capabilities: capabilities
30
+ )
31
+ @participants[participant.id] = participant
32
+ record_history(:registered, participant.id)
33
+ participant
34
+ end
35
+
36
+ def record_contribution(participant_id:, contribution_type:, success:, context: {})
37
+ participant = @participants[participant_id]
38
+ return { success: false, reason: :not_found } unless participant
39
+
40
+ unless CONTRIBUTION_TYPES.include?(contribution_type)
41
+ return { success: false, reason: :invalid_contribution_type }
42
+ end
43
+
44
+ participant.contribute!(success: success)
45
+ store_contribution(participant_id, contribution_type, success, context)
46
+ record_history(:contributed, participant_id)
47
+ build_contribution_result(participant, contribution_type)
48
+ end
49
+
50
+ def find_capable(capability:)
51
+ @participants.values.select { |p| p.capable_of?(capability) }
52
+ .sort_by { |p| -p.reliability }
53
+ end
54
+
55
+ def by_type(participant_type:)
56
+ @participants.values.select { |p| p.participant_type == participant_type }
57
+ end
58
+
59
+ def by_domain(domain:)
60
+ @participants.values.select { |p| p.domain == domain }
61
+ end
62
+
63
+ def most_reliable(limit: 5)
64
+ @participants.values.sort_by { |p| -p.reliability }.first(limit)
65
+ end
66
+
67
+ def distribution_score
68
+ return 0.0 if @contributions.empty? || @participants.size <= 1
69
+
70
+ counts = @contributions.each_with_object(Hash.new(0)) do |contrib, hash|
71
+ hash[contrib[:participant_id]] += 1
72
+ end
73
+ compute_evenness(counts.values)
74
+ end
75
+
76
+ def distribution_label
77
+ score = distribution_score
78
+ DISTRIBUTION_LABELS.find { |range, _| range.cover?(score) }&.last || :unknown
79
+ end
80
+
81
+ def cognitive_load_balance
82
+ return {} if @participants.empty?
83
+
84
+ @participants.transform_values(&:contribution_count)
85
+ end
86
+
87
+ def decay_all
88
+ @participants.each_value(&:decay!)
89
+ end
90
+
91
+ def prune_failing
92
+ failing_ids = @participants.select { |_id, p| p.reliability <= 0.05 }.keys
93
+ failing_ids.each { |id| @participants.delete(id) }
94
+ failing_ids.size
95
+ end
96
+
97
+ def to_h
98
+ {
99
+ total_participants: @participants.size,
100
+ agent_count: by_type(participant_type: :agent).size,
101
+ artifact_count: by_type(participant_type: :artifact).size,
102
+ environment_count: by_type(participant_type: :environment).size,
103
+ total_contributions: @contributions.size,
104
+ distribution_score: distribution_score,
105
+ distribution_label: distribution_label,
106
+ history_count: @history.size
107
+ }
108
+ end
109
+
110
+ private
111
+
112
+ def store_contribution(participant_id, contribution_type, success, context)
113
+ entry = {
114
+ participant_id: participant_id,
115
+ contribution_type: contribution_type,
116
+ success: success,
117
+ context: context,
118
+ at: Time.now.utc
119
+ }
120
+ @contributions << entry
121
+ @contributions.shift while @contributions.size > MAX_CONTRIBUTIONS
122
+ end
123
+
124
+ def build_contribution_result(participant, contribution_type)
125
+ {
126
+ success: true,
127
+ participant_id: participant.id,
128
+ contribution_type: contribution_type,
129
+ reliability: participant.reliability,
130
+ success_rate: participant.success_rate
131
+ }
132
+ end
133
+
134
+ def compute_evenness(counts)
135
+ total = counts.sum.to_f
136
+ return 0.0 if total.zero?
137
+
138
+ proportions = counts.map { |c| c / total }
139
+ max_entropy = Math.log2(counts.size)
140
+ return 1.0 if max_entropy.zero?
141
+
142
+ entropy = -proportions.sum { |p| p.positive? ? p * Math.log2(p) : 0.0 }
143
+ (entropy / max_entropy).clamp(0.0, 1.0)
144
+ end
145
+
146
+ def evict_oldest
147
+ oldest_id = @participants.min_by { |_id, p| p.last_active_at }&.first
148
+ @participants.delete(oldest_id) if oldest_id
149
+ end
150
+
151
+ def record_history(event, subject_id)
152
+ @history << { event: event, subject_id: subject_id, at: Time.now.utc }
153
+ @history.shift while @history.size > MAX_HISTORY
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module DistributedCognition
8
+ module Helpers
9
+ class Participant
10
+ include Constants
11
+
12
+ attr_reader :id, :name, :participant_type, :domain, :capabilities,
13
+ :reliability, :contribution_count, :success_count,
14
+ :created_at, :last_active_at
15
+
16
+ def initialize(name:, participant_type:, domain:, capabilities: [])
17
+ @id = SecureRandom.uuid
18
+ @name = name
19
+ @participant_type = participant_type
20
+ @domain = domain
21
+ @capabilities = capabilities
22
+ @reliability = DEFAULT_RELIABILITY
23
+ @contribution_count = 0
24
+ @success_count = 0
25
+ @created_at = Time.now.utc
26
+ @last_active_at = @created_at
27
+ end
28
+
29
+ def contribute!(success:)
30
+ @contribution_count += 1
31
+ @success_count += 1 if success
32
+ @last_active_at = Time.now.utc
33
+ adjust_reliability(success)
34
+ end
35
+
36
+ def add_capability(capability)
37
+ @capabilities << capability unless @capabilities.include?(capability)
38
+ end
39
+
40
+ def capable_of?(capability)
41
+ @capabilities.include?(capability)
42
+ end
43
+
44
+ def success_rate
45
+ return 0.0 if @contribution_count.zero?
46
+
47
+ @success_count.to_f / @contribution_count
48
+ end
49
+
50
+ def reliability_label
51
+ RELIABILITY_LABELS.find { |range, _| range.cover?(@reliability) }&.last || :unknown
52
+ end
53
+
54
+ def agent?
55
+ @participant_type == :agent
56
+ end
57
+
58
+ def artifact?
59
+ @participant_type == :artifact
60
+ end
61
+
62
+ def decay!
63
+ @reliability = (@reliability - DECAY_RATE).clamp(RELIABILITY_FLOOR, RELIABILITY_CEILING)
64
+ end
65
+
66
+ def stale?
67
+ (Time.now.utc - @last_active_at) > STALE_THRESHOLD
68
+ end
69
+
70
+ def to_h
71
+ {
72
+ id: @id,
73
+ name: @name,
74
+ participant_type: @participant_type,
75
+ domain: @domain,
76
+ capabilities: @capabilities,
77
+ reliability: @reliability,
78
+ reliability_label: reliability_label,
79
+ contribution_count: @contribution_count,
80
+ success_rate: success_rate,
81
+ created_at: @created_at,
82
+ last_active_at: @last_active_at
83
+ }
84
+ end
85
+
86
+ private
87
+
88
+ def adjust_reliability(success)
89
+ delta = success ? REINFORCEMENT_RATE : -PENALTY_RATE
90
+ @reliability = (@reliability + delta).clamp(RELIABILITY_FLOOR, RELIABILITY_CEILING)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module DistributedCognition
6
+ module Runners
7
+ module DistributedCognition
8
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
9
+ Legion::Extensions::Helpers.const_defined?(:Lex)
10
+
11
+ def register_cognitive_participant(name:, participant_type:, domain:, capabilities: [], **)
12
+ unless Helpers::Constants::PARTICIPANT_TYPES.include?(participant_type)
13
+ return { success: false, error: :invalid_participant_type,
14
+ valid_types: Helpers::Constants::PARTICIPANT_TYPES }
15
+ end
16
+
17
+ result = engine.register_participant(
18
+ name: name, participant_type: participant_type,
19
+ domain: domain, capabilities: capabilities
20
+ )
21
+
22
+ return result unless result.is_a?(Helpers::Participant)
23
+
24
+ Legion::Logging.debug "[distributed_cognition] registered #{name} " \
25
+ "type=#{participant_type} id=#{result.id[0..7]}"
26
+ { success: true, participant_id: result.id, name: name,
27
+ participant_type: participant_type, domain: domain }
28
+ end
29
+
30
+ def record_cognitive_contribution(participant_id:, contribution_type:, success:, context: {}, **)
31
+ result = engine.record_contribution(
32
+ participant_id: participant_id, contribution_type: contribution_type,
33
+ success: success, context: context
34
+ )
35
+ Legion::Logging.debug '[distributed_cognition] contribution ' \
36
+ "type=#{contribution_type} success=#{success}"
37
+ result
38
+ end
39
+
40
+ def find_capable_participants(capability:, **)
41
+ participants = engine.find_capable(capability: capability)
42
+ Legion::Logging.debug '[distributed_cognition] find_capable ' \
43
+ "#{capability} count=#{participants.size}"
44
+ { success: true, capability: capability,
45
+ participants: participants.map(&:to_h), count: participants.size }
46
+ end
47
+
48
+ def most_reliable_participants(limit: 5, **)
49
+ participants = engine.most_reliable(limit: limit)
50
+ Legion::Logging.debug "[distributed_cognition] most_reliable count=#{participants.size}"
51
+ { success: true, participants: participants.map(&:to_h), count: participants.size }
52
+ end
53
+
54
+ def distribution_assessment(**)
55
+ score = engine.distribution_score
56
+ label = engine.distribution_label
57
+ balance = engine.cognitive_load_balance
58
+ Legion::Logging.debug '[distributed_cognition] distribution ' \
59
+ "score=#{score.round(3)} label=#{label}"
60
+ { success: true, distribution_score: score, distribution_label: label,
61
+ load_balance: balance }
62
+ end
63
+
64
+ def participants_by_type(participant_type:, **)
65
+ participants = engine.by_type(participant_type: participant_type)
66
+ Legion::Logging.debug "[distributed_cognition] by_type=#{participant_type} " \
67
+ "count=#{participants.size}"
68
+ { success: true, participant_type: participant_type,
69
+ participants: participants.map(&:to_h), count: participants.size }
70
+ end
71
+
72
+ def participants_by_domain(domain:, **)
73
+ participants = engine.by_domain(domain: domain)
74
+ Legion::Logging.debug "[distributed_cognition] by_domain=#{domain} " \
75
+ "count=#{participants.size}"
76
+ { success: true, domain: domain,
77
+ participants: participants.map(&:to_h), count: participants.size }
78
+ end
79
+
80
+ def update_distributed_cognition(**)
81
+ engine.decay_all
82
+ pruned = engine.prune_failing
83
+ Legion::Logging.debug "[distributed_cognition] decay+prune pruned=#{pruned}"
84
+ { success: true, pruned: pruned }
85
+ end
86
+
87
+ def distributed_cognition_stats(**)
88
+ stats = engine.to_h
89
+ Legion::Logging.debug '[distributed_cognition] stats ' \
90
+ "participants=#{stats[:total_participants]}"
91
+ { success: true }.merge(stats)
92
+ end
93
+
94
+ private
95
+
96
+ def engine
97
+ @engine ||= Helpers::DistributionEngine.new
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module DistributedCognition
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/distributed_cognition/version'
4
+ require 'legion/extensions/distributed_cognition/helpers/constants'
5
+ require 'legion/extensions/distributed_cognition/helpers/participant'
6
+ require 'legion/extensions/distributed_cognition/helpers/distribution_engine'
7
+ require 'legion/extensions/distributed_cognition/runners/distributed_cognition'
8
+ require 'legion/extensions/distributed_cognition/client'
9
+
10
+ module Legion
11
+ module Extensions
12
+ module DistributedCognition
13
+ extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::DistributedCognition::Client do
4
+ subject(:client) { described_class.new }
5
+
6
+ it 'registers and contributes' do
7
+ created = client.register_cognitive_participant(
8
+ name: 'worker', participant_type: :agent, domain: :test
9
+ )
10
+ result = client.record_cognitive_contribution(
11
+ participant_id: created[:participant_id],
12
+ contribution_type: :computation, success: true
13
+ )
14
+ expect(result[:success]).to be true
15
+ end
16
+
17
+ it 'returns stats' do
18
+ result = client.distributed_cognition_stats
19
+ expect(result[:success]).to be true
20
+ end
21
+ end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::DistributedCognition::Helpers::DistributionEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ let(:agent) do
7
+ engine.register_participant(
8
+ name: 'worker-alpha', participant_type: :agent,
9
+ domain: :reasoning, capabilities: %i[computation retrieval]
10
+ )
11
+ end
12
+
13
+ describe '#register_participant' do
14
+ it 'creates and stores a participant' do
15
+ result = agent
16
+ expect(result).to be_a(Legion::Extensions::DistributedCognition::Helpers::Participant)
17
+ end
18
+
19
+ it 'rejects invalid participant types' do
20
+ result = engine.register_participant(
21
+ name: 'test', participant_type: :invalid, domain: :test
22
+ )
23
+ expect(result[:success]).to be false
24
+ end
25
+
26
+ it 'records history' do
27
+ agent
28
+ expect(engine.history.size).to eq(1)
29
+ end
30
+ end
31
+
32
+ describe '#record_contribution' do
33
+ it 'records a successful contribution' do
34
+ result = engine.record_contribution(
35
+ participant_id: agent.id, contribution_type: :computation, success: true
36
+ )
37
+ expect(result[:success]).to be true
38
+ expect(result[:reliability]).to be > 0.5
39
+ end
40
+
41
+ it 'returns error for unknown participant' do
42
+ result = engine.record_contribution(
43
+ participant_id: 'bad', contribution_type: :computation, success: true
44
+ )
45
+ expect(result[:success]).to be false
46
+ end
47
+
48
+ it 'rejects invalid contribution types' do
49
+ result = engine.record_contribution(
50
+ participant_id: agent.id, contribution_type: :invalid, success: true
51
+ )
52
+ expect(result[:success]).to be false
53
+ end
54
+ end
55
+
56
+ describe '#find_capable' do
57
+ it 'finds participants with capability' do
58
+ agent
59
+ results = engine.find_capable(capability: :computation)
60
+ expect(results.size).to eq(1)
61
+ end
62
+
63
+ it 'returns empty for unknown capability' do
64
+ agent
65
+ results = engine.find_capable(capability: :transformation)
66
+ expect(results).to be_empty
67
+ end
68
+ end
69
+
70
+ describe '#by_type' do
71
+ it 'filters by participant type' do
72
+ agent
73
+ engine.register_participant(
74
+ name: 'db', participant_type: :artifact, domain: :storage
75
+ )
76
+ agents = engine.by_type(participant_type: :agent)
77
+ expect(agents.size).to eq(1)
78
+ end
79
+ end
80
+
81
+ describe '#by_domain' do
82
+ it 'filters by domain' do
83
+ agent
84
+ results = engine.by_domain(domain: :reasoning)
85
+ expect(results.size).to eq(1)
86
+ end
87
+ end
88
+
89
+ describe '#most_reliable' do
90
+ it 'returns sorted by reliability' do
91
+ agent
92
+ other = engine.register_participant(
93
+ name: 'worker-beta', participant_type: :agent, domain: :reasoning
94
+ )
95
+ 3.times { engine.record_contribution(participant_id: other.id, contribution_type: :computation, success: true) }
96
+ results = engine.most_reliable(limit: 2)
97
+ expect(results.first.reliability).to be >= results.last.reliability
98
+ end
99
+ end
100
+
101
+ describe '#distribution_score' do
102
+ it 'returns 0.0 with no contributions' do
103
+ expect(engine.distribution_score).to eq(0.0)
104
+ end
105
+
106
+ it 'returns higher score for even distribution' do
107
+ other = engine.register_participant(
108
+ name: 'worker-beta', participant_type: :agent, domain: :reasoning
109
+ )
110
+ 3.times do
111
+ engine.record_contribution(participant_id: agent.id, contribution_type: :computation, success: true)
112
+ engine.record_contribution(participant_id: other.id, contribution_type: :computation, success: true)
113
+ end
114
+ expect(engine.distribution_score).to be > 0.5
115
+ end
116
+ end
117
+
118
+ describe '#distribution_label' do
119
+ it 'returns a symbol' do
120
+ expect(engine.distribution_label).to be_a(Symbol)
121
+ end
122
+ end
123
+
124
+ describe '#decay_all' do
125
+ it 'reduces reliability of all participants' do
126
+ original = agent.reliability
127
+ engine.decay_all
128
+ expect(agent.reliability).to be < original
129
+ end
130
+ end
131
+
132
+ describe '#prune_failing' do
133
+ it 'removes very unreliable participants' do
134
+ agent
135
+ 30.times { agent.decay! }
136
+ pruned = engine.prune_failing
137
+ expect(pruned).to be >= 0
138
+ end
139
+ end
140
+
141
+ describe '#to_h' do
142
+ it 'returns summary stats' do
143
+ agent
144
+ stats = engine.to_h
145
+ expect(stats[:total_participants]).to eq(1)
146
+ expect(stats).to include(:distribution_score, :agent_count)
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::DistributedCognition::Helpers::Participant do
4
+ subject(:participant) do
5
+ described_class.new(
6
+ name: 'worker-alpha',
7
+ participant_type: :agent,
8
+ domain: :reasoning,
9
+ capabilities: %i[computation retrieval]
10
+ )
11
+ end
12
+
13
+ describe '#initialize' do
14
+ it 'assigns a UUID' do
15
+ expect(participant.id).to match(/\A[0-9a-f-]{36}\z/)
16
+ end
17
+
18
+ it 'stores name and type' do
19
+ expect(participant.name).to eq('worker-alpha')
20
+ expect(participant.participant_type).to eq(:agent)
21
+ end
22
+
23
+ it 'stores capabilities' do
24
+ expect(participant.capabilities).to eq(%i[computation retrieval])
25
+ end
26
+ end
27
+
28
+ describe '#contribute!' do
29
+ it 'increments contribution count' do
30
+ expect { participant.contribute!(success: true) }.to change(participant, :contribution_count).by(1)
31
+ end
32
+
33
+ it 'increments success count on success' do
34
+ expect { participant.contribute!(success: true) }.to change(participant, :success_count).by(1)
35
+ end
36
+
37
+ it 'increases reliability on success' do
38
+ original = participant.reliability
39
+ participant.contribute!(success: true)
40
+ expect(participant.reliability).to be > original
41
+ end
42
+
43
+ it 'decreases reliability on failure' do
44
+ original = participant.reliability
45
+ participant.contribute!(success: false)
46
+ expect(participant.reliability).to be < original
47
+ end
48
+ end
49
+
50
+ describe '#capable_of?' do
51
+ it 'returns true for known capability' do
52
+ expect(participant).to be_capable_of(:computation)
53
+ end
54
+
55
+ it 'returns false for unknown capability' do
56
+ expect(participant).not_to be_capable_of(:transformation)
57
+ end
58
+ end
59
+
60
+ describe '#add_capability' do
61
+ it 'adds a new capability' do
62
+ participant.add_capability(:storage)
63
+ expect(participant.capabilities).to include(:storage)
64
+ end
65
+
66
+ it 'does not add duplicates' do
67
+ 2.times { participant.add_capability(:computation) }
68
+ expect(participant.capabilities.count(:computation)).to eq(1)
69
+ end
70
+ end
71
+
72
+ describe '#success_rate' do
73
+ it 'returns 0.0 with no contributions' do
74
+ expect(participant.success_rate).to eq(0.0)
75
+ end
76
+
77
+ it 'computes ratio' do
78
+ 3.times { participant.contribute!(success: true) }
79
+ participant.contribute!(success: false)
80
+ expect(participant.success_rate).to eq(0.75)
81
+ end
82
+ end
83
+
84
+ describe '#agent?' do
85
+ it 'returns true for agent type' do
86
+ expect(participant).to be_agent
87
+ end
88
+ end
89
+
90
+ describe '#artifact?' do
91
+ it 'returns false for agent type' do
92
+ expect(participant).not_to be_artifact
93
+ end
94
+ end
95
+
96
+ describe '#reliability_label' do
97
+ it 'returns a symbol' do
98
+ expect(participant.reliability_label).to be_a(Symbol)
99
+ end
100
+ end
101
+
102
+ describe '#decay!' do
103
+ it 'reduces reliability' do
104
+ original = participant.reliability
105
+ participant.decay!
106
+ expect(participant.reliability).to be < original
107
+ end
108
+ end
109
+
110
+ describe '#to_h' do
111
+ it 'returns hash representation' do
112
+ hash = participant.to_h
113
+ expect(hash).to include(:id, :name, :participant_type, :reliability, :capabilities)
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::DistributedCognition::Runners::DistributedCognition do
4
+ let(:runner_host) do
5
+ obj = Object.new
6
+ obj.extend(described_class)
7
+ obj
8
+ end
9
+
10
+ describe '#register_cognitive_participant' do
11
+ it 'registers a participant' do
12
+ result = runner_host.register_cognitive_participant(
13
+ name: 'worker', participant_type: :agent, domain: :test
14
+ )
15
+ expect(result[:success]).to be true
16
+ expect(result[:participant_id]).to be_a(String)
17
+ end
18
+
19
+ it 'rejects invalid type' do
20
+ result = runner_host.register_cognitive_participant(
21
+ name: 'test', participant_type: :invalid, domain: :test
22
+ )
23
+ expect(result[:success]).to be false
24
+ end
25
+ end
26
+
27
+ describe '#record_cognitive_contribution' do
28
+ it 'records a contribution' do
29
+ created = runner_host.register_cognitive_participant(
30
+ name: 'worker', participant_type: :agent, domain: :test
31
+ )
32
+ result = runner_host.record_cognitive_contribution(
33
+ participant_id: created[:participant_id],
34
+ contribution_type: :computation, success: true
35
+ )
36
+ expect(result[:success]).to be true
37
+ end
38
+ end
39
+
40
+ describe '#find_capable_participants' do
41
+ it 'finds capable participants' do
42
+ runner_host.register_cognitive_participant(
43
+ name: 'worker', participant_type: :agent,
44
+ domain: :test, capabilities: [:computation]
45
+ )
46
+ result = runner_host.find_capable_participants(capability: :computation)
47
+ expect(result[:success]).to be true
48
+ expect(result[:count]).to eq(1)
49
+ end
50
+ end
51
+
52
+ describe '#most_reliable_participants' do
53
+ it 'returns reliable participants' do
54
+ result = runner_host.most_reliable_participants(limit: 3)
55
+ expect(result[:success]).to be true
56
+ end
57
+ end
58
+
59
+ describe '#distribution_assessment' do
60
+ it 'returns distribution metrics' do
61
+ result = runner_host.distribution_assessment
62
+ expect(result[:success]).to be true
63
+ expect(result).to include(:distribution_score, :distribution_label)
64
+ end
65
+ end
66
+
67
+ describe '#participants_by_type' do
68
+ it 'filters by type' do
69
+ runner_host.register_cognitive_participant(
70
+ name: 'db', participant_type: :artifact, domain: :storage
71
+ )
72
+ result = runner_host.participants_by_type(participant_type: :artifact)
73
+ expect(result[:count]).to eq(1)
74
+ end
75
+ end
76
+
77
+ describe '#participants_by_domain' do
78
+ it 'filters by domain' do
79
+ runner_host.register_cognitive_participant(
80
+ name: 'worker', participant_type: :agent, domain: :reasoning
81
+ )
82
+ result = runner_host.participants_by_domain(domain: :reasoning)
83
+ expect(result[:count]).to eq(1)
84
+ end
85
+ end
86
+
87
+ describe '#update_distributed_cognition' do
88
+ it 'runs decay and prune cycle' do
89
+ result = runner_host.update_distributed_cognition
90
+ expect(result[:success]).to be true
91
+ expect(result).to include(:pruned)
92
+ end
93
+ end
94
+
95
+ describe '#distributed_cognition_stats' do
96
+ it 'returns stats' do
97
+ result = runner_host.distributed_cognition_stats
98
+ expect(result[:success]).to be true
99
+ expect(result).to include(:total_participants, :distribution_score)
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Helpers
6
+ module Lex; end
7
+ end
8
+ end
9
+
10
+ module Logging
11
+ def self.debug(*); end
12
+
13
+ def self.info(*); end
14
+
15
+ def self.warn(*); end
16
+
17
+ def self.error(*); end
18
+ end
19
+ end
20
+
21
+ require 'legion/extensions/distributed_cognition'
22
+
23
+ RSpec.configure do |config|
24
+ config.expect_with :rspec do |expectations|
25
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
26
+ end
27
+
28
+ config.mock_with :rspec do |mocks|
29
+ mocks.verify_partial_doubles = true
30
+ end
31
+
32
+ config.filter_run_when_matching :focus
33
+ config.order = :random
34
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-distributed-cognition
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: Hutchins' distributed cognition — cognitive processes spread across agents,
27
+ artifacts, and environment for brain-modeled agentic AI
28
+ email:
29
+ - matthewdiverson@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - Gemfile
35
+ - lex-distributed-cognition.gemspec
36
+ - lib/legion/extensions/distributed_cognition.rb
37
+ - lib/legion/extensions/distributed_cognition/client.rb
38
+ - lib/legion/extensions/distributed_cognition/helpers/constants.rb
39
+ - lib/legion/extensions/distributed_cognition/helpers/distribution_engine.rb
40
+ - lib/legion/extensions/distributed_cognition/helpers/participant.rb
41
+ - lib/legion/extensions/distributed_cognition/runners/distributed_cognition.rb
42
+ - lib/legion/extensions/distributed_cognition/version.rb
43
+ - spec/legion/extensions/distributed_cognition/client_spec.rb
44
+ - spec/legion/extensions/distributed_cognition/helpers/distribution_engine_spec.rb
45
+ - spec/legion/extensions/distributed_cognition/helpers/participant_spec.rb
46
+ - spec/legion/extensions/distributed_cognition/runners/distributed_cognition_spec.rb
47
+ - spec/spec_helper.rb
48
+ homepage: https://github.com/LegionIO/lex-distributed-cognition
49
+ licenses:
50
+ - MIT
51
+ metadata:
52
+ homepage_uri: https://github.com/LegionIO/lex-distributed-cognition
53
+ source_code_uri: https://github.com/LegionIO/lex-distributed-cognition
54
+ documentation_uri: https://github.com/LegionIO/lex-distributed-cognition
55
+ changelog_uri: https://github.com/LegionIO/lex-distributed-cognition
56
+ bug_tracker_uri: https://github.com/LegionIO/lex-distributed-cognition/issues
57
+ rubygems_mfa_required: 'true'
58
+ rdoc_options: []
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '3.4'
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ requirements: []
72
+ rubygems_version: 3.6.9
73
+ specification_version: 4
74
+ summary: LEX Distributed Cognition
75
+ test_files: []