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 +7 -0
- data/Gemfile +13 -0
- data/lex-distributed-cognition.gemspec +30 -0
- data/lib/legion/extensions/distributed_cognition/client.rb +15 -0
- data/lib/legion/extensions/distributed_cognition/helpers/constants.rb +47 -0
- data/lib/legion/extensions/distributed_cognition/helpers/distribution_engine.rb +159 -0
- data/lib/legion/extensions/distributed_cognition/helpers/participant.rb +96 -0
- data/lib/legion/extensions/distributed_cognition/runners/distributed_cognition.rb +103 -0
- data/lib/legion/extensions/distributed_cognition/version.rb +9 -0
- data/lib/legion/extensions/distributed_cognition.rb +16 -0
- data/spec/legion/extensions/distributed_cognition/client_spec.rb +21 -0
- data/spec/legion/extensions/distributed_cognition/helpers/distribution_engine_spec.rb +149 -0
- data/spec/legion/extensions/distributed_cognition/helpers/participant_spec.rb +116 -0
- data/spec/legion/extensions/distributed_cognition/runners/distributed_cognition_spec.rb +102 -0
- data/spec/spec_helper.rb +34 -0
- metadata +75 -0
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,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,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
|
data/spec/spec_helper.rb
ADDED
|
@@ -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: []
|