lex-swarm 0.1.1
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-swarm.gemspec +29 -0
- data/lib/legion/extensions/swarm/actors/stale_check.rb +41 -0
- data/lib/legion/extensions/swarm/client.rb +23 -0
- data/lib/legion/extensions/swarm/helpers/charter.rb +42 -0
- data/lib/legion/extensions/swarm/helpers/swarm_store.rb +63 -0
- data/lib/legion/extensions/swarm/runners/swarm.rb +96 -0
- data/lib/legion/extensions/swarm/version.rb +9 -0
- data/lib/legion/extensions/swarm.rb +14 -0
- data/spec/legion/extensions/swarm/actors/stale_check_spec.rb +46 -0
- data/spec/legion/extensions/swarm/client_spec.rb +16 -0
- data/spec/legion/extensions/swarm/helpers/charter_spec.rb +137 -0
- data/spec/legion/extensions/swarm/helpers/swarm_store_spec.rb +249 -0
- data/spec/legion/extensions/swarm/runners/swarm_spec.rb +84 -0
- data/spec/spec_helper.rb +20 -0
- metadata +75 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 1b72b8c33768eb24380924cb9f33a342dd5e0e07fa1daaf151089de1d38e141e
|
|
4
|
+
data.tar.gz: 790c2c25675b41bb049410a15f21585fc361a00963d771af73752b612c2d15e9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 9ca0b32d4b9c8af87086c777c71822c32228b195e0e1c7cb825c89c9a883dd97cee6e8815e228ca4d4a8a07ce1d31813eca91e09191405f06b45c9ed0c347453
|
|
7
|
+
data.tar.gz: 6765b85f142bebdb96f537860d4984e56c762551d87da9b6fd3ab0a3b028c6f6fc3236e46b50bf445a020bf1d570c6fae3791af408c6cdc77d6ea77f68382c64
|
data/Gemfile
ADDED
data/lex-swarm.gemspec
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/legion/extensions/swarm/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'lex-swarm'
|
|
7
|
+
spec.version = Legion::Extensions::Swarm::VERSION
|
|
8
|
+
spec.authors = ['Esity']
|
|
9
|
+
spec.email = ['matthewdiverson@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'LEX Swarm'
|
|
12
|
+
spec.description = 'Swarm orchestration and charter system for brain-modeled agentic AI'
|
|
13
|
+
spec.homepage = 'https://github.com/LegionIO/lex-swarm'
|
|
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-swarm'
|
|
19
|
+
spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-swarm'
|
|
20
|
+
spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-swarm'
|
|
21
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-swarm/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-swarm.gemspec Gemfile]
|
|
26
|
+
end
|
|
27
|
+
spec.require_paths = ['lib']
|
|
28
|
+
spec.add_development_dependency 'legion-gaia'
|
|
29
|
+
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 Swarm
|
|
8
|
+
module Actor
|
|
9
|
+
class StaleCheck < Legion::Extensions::Actors::Every
|
|
10
|
+
def runner_class
|
|
11
|
+
Legion::Extensions::Swarm::Runners::Swarm
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def runner_function
|
|
15
|
+
'timeout_stale_swarms'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def time
|
|
19
|
+
3600
|
|
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/swarm/helpers/charter'
|
|
4
|
+
require 'legion/extensions/swarm/helpers/swarm_store'
|
|
5
|
+
require 'legion/extensions/swarm/runners/swarm'
|
|
6
|
+
|
|
7
|
+
module Legion
|
|
8
|
+
module Extensions
|
|
9
|
+
module Swarm
|
|
10
|
+
class Client
|
|
11
|
+
include Runners::Swarm
|
|
12
|
+
|
|
13
|
+
def initialize(**)
|
|
14
|
+
@swarm_store = Helpers::SwarmStore.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
attr_reader :swarm_store
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Swarm
|
|
8
|
+
module Helpers
|
|
9
|
+
module Charter
|
|
10
|
+
ROLES = %i[finder fixer validator reviewer coordinator].freeze
|
|
11
|
+
STATUSES = %i[forming active completing disbanded failed].freeze
|
|
12
|
+
SWARM_STALE_TIMEOUT = 86_400 # 24 hours
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def new_charter(name:, objective:, roles: [], max_agents: 10, timeout: 3600)
|
|
17
|
+
{
|
|
18
|
+
charter_id: SecureRandom.uuid,
|
|
19
|
+
name: name,
|
|
20
|
+
objective: objective,
|
|
21
|
+
roles: roles.empty? ? ROLES : roles,
|
|
22
|
+
max_agents: max_agents,
|
|
23
|
+
timeout: timeout,
|
|
24
|
+
status: :forming,
|
|
25
|
+
agents: [],
|
|
26
|
+
created_at: Time.now.utc,
|
|
27
|
+
completed_at: nil
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def valid_role?(role)
|
|
32
|
+
ROLES.include?(role)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def valid_status?(status)
|
|
36
|
+
STATUSES.include?(status)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Swarm
|
|
6
|
+
module Helpers
|
|
7
|
+
class SwarmStore
|
|
8
|
+
attr_reader :charters
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@charters = {}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create(charter)
|
|
15
|
+
@charters[charter[:charter_id]] = charter
|
|
16
|
+
charter[:charter_id]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def get(charter_id)
|
|
20
|
+
@charters[charter_id]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def join(charter_id, agent_id:, role:)
|
|
24
|
+
charter = @charters[charter_id]
|
|
25
|
+
return :not_found unless charter
|
|
26
|
+
return :full if charter[:agents].size >= charter[:max_agents]
|
|
27
|
+
return :already_joined if charter[:agents].any? { |a| a[:agent_id] == agent_id }
|
|
28
|
+
|
|
29
|
+
charter[:agents] << { agent_id: agent_id, role: role, joined_at: Time.now.utc }
|
|
30
|
+
charter[:status] = :active if charter[:status] == :forming
|
|
31
|
+
:joined
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def leave(charter_id, agent_id:)
|
|
35
|
+
charter = @charters[charter_id]
|
|
36
|
+
return :not_found unless charter
|
|
37
|
+
|
|
38
|
+
removed = charter[:agents].reject! { |a| a[:agent_id] == agent_id }
|
|
39
|
+
removed ? :left : :not_member
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def complete(charter_id, outcome:)
|
|
43
|
+
charter = @charters[charter_id]
|
|
44
|
+
return nil unless charter
|
|
45
|
+
|
|
46
|
+
charter[:status] = outcome == :success ? :completing : :failed
|
|
47
|
+
charter[:completed_at] = Time.now.utc
|
|
48
|
+
charter[:outcome] = outcome
|
|
49
|
+
charter
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def active_charters
|
|
53
|
+
@charters.values.select { |c| c[:status] == :active }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def count
|
|
57
|
+
@charters.size
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Swarm
|
|
6
|
+
module Runners
|
|
7
|
+
module Swarm
|
|
8
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
9
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
|
|
11
|
+
def create_swarm(name:, objective:, roles: [], max_agents: 10, timeout: 3600, **) # rubocop:disable Metrics/ParameterLists
|
|
12
|
+
charter = Helpers::Charter.new_charter(name: name, objective: objective,
|
|
13
|
+
roles: roles, max_agents: max_agents, timeout: timeout)
|
|
14
|
+
id = swarm_store.create(charter)
|
|
15
|
+
Legion::Logging.info "[swarm] created: id=#{id[0..7]} name=#{name} max=#{max_agents} roles=#{roles.join(',')}"
|
|
16
|
+
{ charter_id: id, name: name, status: :forming }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def join_swarm(charter_id:, agent_id:, role:, **)
|
|
20
|
+
return { error: :invalid_role, valid: Helpers::Charter::ROLES } unless Helpers::Charter.valid_role?(role)
|
|
21
|
+
|
|
22
|
+
result = swarm_store.join(charter_id, agent_id: agent_id, role: role)
|
|
23
|
+
Legion::Logging.debug "[swarm] join: charter=#{charter_id[0..7]} agent=#{agent_id} role=#{role} result=#{result}"
|
|
24
|
+
Legion::Logging.info "[swarm] agent joined: charter=#{charter_id[0..7]} agent=#{agent_id} role=#{role}" if result == :joined
|
|
25
|
+
{
|
|
26
|
+
joined: { joined: true, charter_id: charter_id },
|
|
27
|
+
full: { error: :swarm_full },
|
|
28
|
+
not_found: { error: :not_found },
|
|
29
|
+
already_joined: { error: :already_joined }
|
|
30
|
+
}[result]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def leave_swarm(charter_id:, agent_id:, **)
|
|
34
|
+
result = swarm_store.leave(charter_id, agent_id: agent_id)
|
|
35
|
+
Legion::Logging.debug "[swarm] leave: charter=#{charter_id[0..7]} agent=#{agent_id} result=#{result}"
|
|
36
|
+
Legion::Logging.info "[swarm] agent left: charter=#{charter_id[0..7]} agent=#{agent_id}" if result == :left
|
|
37
|
+
{
|
|
38
|
+
left: { left: true },
|
|
39
|
+
not_found: { error: :not_found },
|
|
40
|
+
not_member: { error: :not_member }
|
|
41
|
+
}[result]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def complete_swarm(charter_id:, outcome:, **)
|
|
45
|
+
result = swarm_store.complete(charter_id, outcome: outcome)
|
|
46
|
+
if result
|
|
47
|
+
Legion::Logging.info "[swarm] completed: charter=#{charter_id[0..7]} outcome=#{outcome}"
|
|
48
|
+
else
|
|
49
|
+
Legion::Logging.debug "[swarm] complete failed: charter=#{charter_id[0..7]} not found"
|
|
50
|
+
end
|
|
51
|
+
result ? { completed: true, outcome: outcome } : { error: :not_found }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def get_swarm(charter_id:, **)
|
|
55
|
+
charter = swarm_store.get(charter_id)
|
|
56
|
+
Legion::Logging.debug "[swarm] get: charter=#{charter_id[0..7]} found=#{!charter.nil?}"
|
|
57
|
+
charter ? { found: true, charter: charter } : { found: false }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def active_swarms(**)
|
|
61
|
+
charters = swarm_store.active_charters
|
|
62
|
+
Legion::Logging.debug "[swarm] active: count=#{charters.size}"
|
|
63
|
+
{ charters: charters, count: charters.size }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def swarm_status(**)
|
|
67
|
+
total = swarm_store.count
|
|
68
|
+
Legion::Logging.debug "[swarm] status: total=#{total}"
|
|
69
|
+
{ total: total }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def timeout_stale_swarms(**)
|
|
73
|
+
now = Time.now.utc
|
|
74
|
+
timeout = Helpers::Charter::SWARM_STALE_TIMEOUT
|
|
75
|
+
disbanded = []
|
|
76
|
+
swarm_store.charters.each_value do |charter|
|
|
77
|
+
next unless %i[forming active].include?(charter[:status])
|
|
78
|
+
next unless now - charter[:created_at] > timeout
|
|
79
|
+
|
|
80
|
+
charter[:status] = :disbanded
|
|
81
|
+
disbanded << charter[:charter_id]
|
|
82
|
+
end
|
|
83
|
+
Legion::Logging.debug "[swarm] stale check: checked=#{swarm_store.charters.size} disbanded=#{disbanded.size}"
|
|
84
|
+
{ checked: swarm_store.charters.size, disbanded: disbanded.size, disbanded_ids: disbanded }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def swarm_store
|
|
90
|
+
@swarm_store ||= Helpers::SwarmStore.new
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/swarm/version'
|
|
4
|
+
require 'legion/extensions/swarm/helpers/charter'
|
|
5
|
+
require 'legion/extensions/swarm/helpers/swarm_store'
|
|
6
|
+
require 'legion/extensions/swarm/runners/swarm'
|
|
7
|
+
|
|
8
|
+
module Legion
|
|
9
|
+
module Extensions
|
|
10
|
+
module Swarm
|
|
11
|
+
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Actors
|
|
6
|
+
class Every # rubocop:disable Lint/EmptyClass
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
$LOADED_FEATURES << 'legion/extensions/actors/every'
|
|
13
|
+
|
|
14
|
+
require_relative '../../../../../lib/legion/extensions/swarm/actors/stale_check'
|
|
15
|
+
|
|
16
|
+
RSpec.describe Legion::Extensions::Swarm::Actor::StaleCheck do
|
|
17
|
+
subject(:actor) { described_class.new }
|
|
18
|
+
|
|
19
|
+
describe '#runner_class' do
|
|
20
|
+
it { expect(actor.runner_class).to eq Legion::Extensions::Swarm::Runners::Swarm }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe '#runner_function' do
|
|
24
|
+
it { expect(actor.runner_function).to eq 'timeout_stale_swarms' }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe '#time' do
|
|
28
|
+
it { expect(actor.time).to eq 3600 }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe '#run_now?' do
|
|
32
|
+
it { expect(actor.run_now?).to be false }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe '#use_runner?' do
|
|
36
|
+
it { expect(actor.use_runner?).to be false }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe '#check_subtask?' do
|
|
40
|
+
it { expect(actor.check_subtask?).to be false }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe '#generate_task?' do
|
|
44
|
+
it { expect(actor.generate_task?).to be false }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/swarm/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Swarm::Client do
|
|
6
|
+
it 'responds to swarm runner methods' do
|
|
7
|
+
client = described_class.new
|
|
8
|
+
expect(client).to respond_to(:create_swarm)
|
|
9
|
+
expect(client).to respond_to(:join_swarm)
|
|
10
|
+
expect(client).to respond_to(:leave_swarm)
|
|
11
|
+
expect(client).to respond_to(:complete_swarm)
|
|
12
|
+
expect(client).to respond_to(:get_swarm)
|
|
13
|
+
expect(client).to respond_to(:active_swarms)
|
|
14
|
+
expect(client).to respond_to(:swarm_status)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::Swarm::Helpers::Charter do
|
|
4
|
+
describe 'constants' do
|
|
5
|
+
it 'defines ROLES as the five swarm roles' do
|
|
6
|
+
expect(described_class::ROLES).to eq(%i[finder fixer validator reviewer coordinator])
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it 'defines STATUSES as the five lifecycle statuses' do
|
|
10
|
+
expect(described_class::STATUSES).to eq(%i[forming active completing disbanded failed])
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe '.new_charter' do
|
|
15
|
+
let(:charter) { described_class.new_charter(name: 'test-swarm', objective: 'fix all bugs') }
|
|
16
|
+
|
|
17
|
+
it 'returns a hash with a UUID charter_id' do
|
|
18
|
+
expect(charter[:charter_id]).to match(/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'stores the provided name' do
|
|
22
|
+
expect(charter[:name]).to eq('test-swarm')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'stores the provided objective' do
|
|
26
|
+
expect(charter[:objective]).to eq('fix all bugs')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'defaults status to :forming' do
|
|
30
|
+
expect(charter[:status]).to eq(:forming)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'starts with an empty agents array' do
|
|
34
|
+
expect(charter[:agents]).to eq([])
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'sets completed_at to nil' do
|
|
38
|
+
expect(charter[:completed_at]).to be_nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'records created_at as a UTC Time' do
|
|
42
|
+
before = Time.now.utc
|
|
43
|
+
c = described_class.new_charter(name: 'n', objective: 'o')
|
|
44
|
+
after = Time.now.utc
|
|
45
|
+
expect(c[:created_at]).to be_between(before, after)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'defaults to all ROLES when roles is empty' do
|
|
49
|
+
expect(charter[:roles]).to eq(described_class::ROLES)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'uses the provided roles array when non-empty' do
|
|
53
|
+
c = described_class.new_charter(name: 'n', objective: 'o', roles: %i[finder fixer])
|
|
54
|
+
expect(c[:roles]).to eq(%i[finder fixer])
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'defaults max_agents to 10' do
|
|
58
|
+
expect(charter[:max_agents]).to eq(10)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'accepts a custom max_agents value' do
|
|
62
|
+
c = described_class.new_charter(name: 'n', objective: 'o', max_agents: 5)
|
|
63
|
+
expect(c[:max_agents]).to eq(5)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'defaults timeout to 3600' do
|
|
67
|
+
expect(charter[:timeout]).to eq(3600)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'accepts a custom timeout value' do
|
|
71
|
+
c = described_class.new_charter(name: 'n', objective: 'o', timeout: 7200)
|
|
72
|
+
expect(c[:timeout]).to eq(7200)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'generates unique charter_ids for each call' do
|
|
76
|
+
c1 = described_class.new_charter(name: 'a', objective: 'b')
|
|
77
|
+
c2 = described_class.new_charter(name: 'a', objective: 'b')
|
|
78
|
+
expect(c1[:charter_id]).not_to eq(c2[:charter_id])
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
describe '.valid_role?' do
|
|
83
|
+
it 'returns true for all defined ROLES' do
|
|
84
|
+
described_class::ROLES.each do |role|
|
|
85
|
+
expect(described_class.valid_role?(role)).to be true
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'returns true for :finder' do
|
|
90
|
+
expect(described_class.valid_role?(:finder)).to be true
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it 'returns true for :coordinator' do
|
|
94
|
+
expect(described_class.valid_role?(:coordinator)).to be true
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it 'returns false for an unknown role' do
|
|
98
|
+
expect(described_class.valid_role?(:operator)).to be false
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it 'returns false for nil' do
|
|
102
|
+
expect(described_class.valid_role?(nil)).to be false
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it 'returns false for a string version of a valid role' do
|
|
106
|
+
expect(described_class.valid_role?('finder')).to be false
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
describe '.valid_status?' do
|
|
111
|
+
it 'returns true for all defined STATUSES' do
|
|
112
|
+
described_class::STATUSES.each do |status|
|
|
113
|
+
expect(described_class.valid_status?(status)).to be true
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it 'returns true for :forming' do
|
|
118
|
+
expect(described_class.valid_status?(:forming)).to be true
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it 'returns true for :disbanded' do
|
|
122
|
+
expect(described_class.valid_status?(:disbanded)).to be true
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'returns false for an unknown status' do
|
|
126
|
+
expect(described_class.valid_status?(:pending)).to be false
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it 'returns false for nil' do
|
|
130
|
+
expect(described_class.valid_status?(nil)).to be false
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it 'returns false for a string version of a valid status' do
|
|
134
|
+
expect(described_class.valid_status?('active')).to be false
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::Swarm::Helpers::SwarmStore do
|
|
4
|
+
let(:store) { described_class.new }
|
|
5
|
+
let(:charter_helper) { Legion::Extensions::Swarm::Helpers::Charter }
|
|
6
|
+
|
|
7
|
+
let(:charter) { charter_helper.new_charter(name: 'test-swarm', objective: 'fix bugs') }
|
|
8
|
+
|
|
9
|
+
describe '#initialize' do
|
|
10
|
+
it 'starts with an empty charters hash' do
|
|
11
|
+
expect(store.charters).to eq({})
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe '#create' do
|
|
16
|
+
it 'stores the charter by its charter_id' do
|
|
17
|
+
id = store.create(charter)
|
|
18
|
+
expect(store.charters[id]).to eq(charter)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'returns the charter_id' do
|
|
22
|
+
id = store.create(charter)
|
|
23
|
+
expect(id).to eq(charter[:charter_id])
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'stores multiple charters independently' do
|
|
27
|
+
c1 = charter_helper.new_charter(name: 'swarm-a', objective: 'a')
|
|
28
|
+
c2 = charter_helper.new_charter(name: 'swarm-b', objective: 'b')
|
|
29
|
+
store.create(c1)
|
|
30
|
+
store.create(c2)
|
|
31
|
+
expect(store.count).to eq(2)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe '#get' do
|
|
36
|
+
it 'returns the charter for a known id' do
|
|
37
|
+
store.create(charter)
|
|
38
|
+
expect(store.get(charter[:charter_id])).to eq(charter)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'returns nil for an unknown id' do
|
|
42
|
+
expect(store.get('nonexistent-id')).to be_nil
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe '#join' do
|
|
47
|
+
before { store.create(charter) }
|
|
48
|
+
|
|
49
|
+
it 'returns :joined when agent successfully joins' do
|
|
50
|
+
result = store.join(charter[:charter_id], agent_id: 'agent-1', role: :finder)
|
|
51
|
+
expect(result).to eq(:joined)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'adds the agent to the charter agents list' do
|
|
55
|
+
store.join(charter[:charter_id], agent_id: 'agent-1', role: :finder)
|
|
56
|
+
agents = store.get(charter[:charter_id])[:agents]
|
|
57
|
+
expect(agents.size).to eq(1)
|
|
58
|
+
expect(agents.first[:agent_id]).to eq('agent-1')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'stores the role on the joined agent record' do
|
|
62
|
+
store.join(charter[:charter_id], agent_id: 'agent-1', role: :finder)
|
|
63
|
+
expect(store.get(charter[:charter_id])[:agents].first[:role]).to eq(:finder)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'records joined_at as a UTC Time' do
|
|
67
|
+
before = Time.now.utc
|
|
68
|
+
store.join(charter[:charter_id], agent_id: 'agent-1', role: :finder)
|
|
69
|
+
after = Time.now.utc
|
|
70
|
+
ts = store.get(charter[:charter_id])[:agents].first[:joined_at]
|
|
71
|
+
expect(ts).to be_between(before, after)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'transitions status from :forming to :active on first join' do
|
|
75
|
+
store.join(charter[:charter_id], agent_id: 'agent-1', role: :finder)
|
|
76
|
+
expect(store.get(charter[:charter_id])[:status]).to eq(:active)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it 'keeps status :active when a second agent joins' do
|
|
80
|
+
store.join(charter[:charter_id], agent_id: 'agent-1', role: :finder)
|
|
81
|
+
store.join(charter[:charter_id], agent_id: 'agent-2', role: :fixer)
|
|
82
|
+
expect(store.get(charter[:charter_id])[:status]).to eq(:active)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'returns :not_found for unknown charter_id' do
|
|
86
|
+
result = store.join('unknown-id', agent_id: 'agent-1', role: :finder)
|
|
87
|
+
expect(result).to eq(:not_found)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'returns :already_joined when the agent is already a member' do
|
|
91
|
+
store.join(charter[:charter_id], agent_id: 'agent-1', role: :finder)
|
|
92
|
+
result = store.join(charter[:charter_id], agent_id: 'agent-1', role: :fixer)
|
|
93
|
+
expect(result).to eq(:already_joined)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'returns :full when max_agents is reached' do
|
|
97
|
+
small_charter = charter_helper.new_charter(name: 'tiny', objective: 'min', max_agents: 1)
|
|
98
|
+
store.create(small_charter)
|
|
99
|
+
store.join(small_charter[:charter_id], agent_id: 'a1', role: :finder)
|
|
100
|
+
result = store.join(small_charter[:charter_id], agent_id: 'a2', role: :fixer)
|
|
101
|
+
expect(result).to eq(:full)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it 'does not modify the charter when returning :full' do
|
|
105
|
+
small_charter = charter_helper.new_charter(name: 'tiny', objective: 'min', max_agents: 1)
|
|
106
|
+
store.create(small_charter)
|
|
107
|
+
store.join(small_charter[:charter_id], agent_id: 'a1', role: :finder)
|
|
108
|
+
store.join(small_charter[:charter_id], agent_id: 'a2', role: :fixer)
|
|
109
|
+
expect(store.get(small_charter[:charter_id])[:agents].size).to eq(1)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
describe '#leave' do
|
|
114
|
+
before do
|
|
115
|
+
store.create(charter)
|
|
116
|
+
store.join(charter[:charter_id], agent_id: 'agent-1', role: :finder)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it 'returns :left when agent successfully leaves' do
|
|
120
|
+
result = store.leave(charter[:charter_id], agent_id: 'agent-1')
|
|
121
|
+
expect(result).to eq(:left)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it 'removes the agent from the agents list' do
|
|
125
|
+
store.leave(charter[:charter_id], agent_id: 'agent-1')
|
|
126
|
+
expect(store.get(charter[:charter_id])[:agents]).to be_empty
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it 'returns :not_member for an agent not in the charter' do
|
|
130
|
+
result = store.leave(charter[:charter_id], agent_id: 'agent-99')
|
|
131
|
+
expect(result).to eq(:not_member)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it 'returns :not_found for unknown charter_id' do
|
|
135
|
+
result = store.leave('unknown-id', agent_id: 'agent-1')
|
|
136
|
+
expect(result).to eq(:not_found)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it 'leaves other members unaffected' do
|
|
140
|
+
store.join(charter[:charter_id], agent_id: 'agent-2', role: :fixer)
|
|
141
|
+
store.leave(charter[:charter_id], agent_id: 'agent-1')
|
|
142
|
+
agents = store.get(charter[:charter_id])[:agents]
|
|
143
|
+
expect(agents.map { |a| a[:agent_id] }).to contain_exactly('agent-2')
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
describe '#complete' do
|
|
148
|
+
before { store.create(charter) }
|
|
149
|
+
|
|
150
|
+
it 'sets status to :completing on success outcome' do
|
|
151
|
+
store.complete(charter[:charter_id], outcome: :success)
|
|
152
|
+
expect(store.get(charter[:charter_id])[:status]).to eq(:completing)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it 'sets status to :failed on non-success outcome' do
|
|
156
|
+
store.complete(charter[:charter_id], outcome: :failure)
|
|
157
|
+
expect(store.get(charter[:charter_id])[:status]).to eq(:failed)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it 'sets status to :failed for an arbitrary non-success outcome' do
|
|
161
|
+
store.complete(charter[:charter_id], outcome: :timeout)
|
|
162
|
+
expect(store.get(charter[:charter_id])[:status]).to eq(:failed)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it 'records completed_at as a UTC Time' do
|
|
166
|
+
before = Time.now.utc
|
|
167
|
+
store.complete(charter[:charter_id], outcome: :success)
|
|
168
|
+
after = Time.now.utc
|
|
169
|
+
ts = store.get(charter[:charter_id])[:completed_at]
|
|
170
|
+
expect(ts).to be_between(before, after)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it 'stores the outcome on the charter' do
|
|
174
|
+
store.complete(charter[:charter_id], outcome: :success)
|
|
175
|
+
expect(store.get(charter[:charter_id])[:outcome]).to eq(:success)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it 'returns the updated charter hash' do
|
|
179
|
+
result = store.complete(charter[:charter_id], outcome: :success)
|
|
180
|
+
expect(result[:charter_id]).to eq(charter[:charter_id])
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
it 'returns nil for unknown charter_id' do
|
|
184
|
+
expect(store.complete('unknown-id', outcome: :success)).to be_nil
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
describe '#active_charters' do
|
|
189
|
+
it 'returns an empty array when no charters exist' do
|
|
190
|
+
expect(store.active_charters).to eq([])
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
it 'excludes charters with :forming status' do
|
|
194
|
+
store.create(charter)
|
|
195
|
+
expect(store.active_charters).to eq([])
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
it 'returns charters with :active status' do
|
|
199
|
+
store.create(charter)
|
|
200
|
+
store.join(charter[:charter_id], agent_id: 'a1', role: :finder)
|
|
201
|
+
expect(store.active_charters.size).to eq(1)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
it 'excludes charters with :completing status' do
|
|
205
|
+
store.create(charter)
|
|
206
|
+
store.join(charter[:charter_id], agent_id: 'a1', role: :finder)
|
|
207
|
+
store.complete(charter[:charter_id], outcome: :success)
|
|
208
|
+
expect(store.active_charters).to eq([])
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
it 'excludes charters with :failed status' do
|
|
212
|
+
store.create(charter)
|
|
213
|
+
store.join(charter[:charter_id], agent_id: 'a1', role: :finder)
|
|
214
|
+
store.complete(charter[:charter_id], outcome: :error)
|
|
215
|
+
expect(store.active_charters).to eq([])
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
it 'returns only the active subset among multiple charters' do
|
|
219
|
+
c1 = charter_helper.new_charter(name: 'active', objective: 'a')
|
|
220
|
+
c2 = charter_helper.new_charter(name: 'forming', objective: 'b')
|
|
221
|
+
store.create(c1)
|
|
222
|
+
store.create(c2)
|
|
223
|
+
store.join(c1[:charter_id], agent_id: 'a1', role: :finder)
|
|
224
|
+
expect(store.active_charters.size).to eq(1)
|
|
225
|
+
expect(store.active_charters.first[:charter_id]).to eq(c1[:charter_id])
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
describe '#count' do
|
|
230
|
+
it 'returns 0 for an empty store' do
|
|
231
|
+
expect(store.count).to eq(0)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
it 'returns the number of charters stored' do
|
|
235
|
+
store.create(charter)
|
|
236
|
+
expect(store.count).to eq(1)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
it 'counts all charters regardless of status' do
|
|
240
|
+
c1 = charter_helper.new_charter(name: 'a', objective: 'a')
|
|
241
|
+
c2 = charter_helper.new_charter(name: 'b', objective: 'b')
|
|
242
|
+
store.create(c1)
|
|
243
|
+
store.create(c2)
|
|
244
|
+
store.join(c1[:charter_id], agent_id: 'a1', role: :finder)
|
|
245
|
+
store.complete(c1[:charter_id], outcome: :success)
|
|
246
|
+
expect(store.count).to eq(2)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/swarm/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Swarm::Runners::Swarm do
|
|
6
|
+
let(:client) { Legion::Extensions::Swarm::Client.new }
|
|
7
|
+
|
|
8
|
+
describe '#create_swarm' do
|
|
9
|
+
it 'creates a swarm charter' do
|
|
10
|
+
result = client.create_swarm(name: 'test-swarm', objective: 'fix bugs')
|
|
11
|
+
expect(result[:charter_id]).to match(/\A[0-9a-f-]{36}\z/)
|
|
12
|
+
expect(result[:status]).to eq(:forming)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe '#join_swarm' do
|
|
17
|
+
it 'adds agent to swarm' do
|
|
18
|
+
swarm = client.create_swarm(name: 'test', objective: 'test')
|
|
19
|
+
result = client.join_swarm(charter_id: swarm[:charter_id], agent_id: 'a1', role: :finder)
|
|
20
|
+
expect(result[:joined]).to be true
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'rejects invalid role' do
|
|
24
|
+
swarm = client.create_swarm(name: 'test', objective: 'test')
|
|
25
|
+
result = client.join_swarm(charter_id: swarm[:charter_id], agent_id: 'a1', role: :invalid)
|
|
26
|
+
expect(result[:error]).to eq(:invalid_role)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'prevents duplicate joins' do
|
|
30
|
+
swarm = client.create_swarm(name: 'test', objective: 'test')
|
|
31
|
+
client.join_swarm(charter_id: swarm[:charter_id], agent_id: 'a1', role: :finder)
|
|
32
|
+
result = client.join_swarm(charter_id: swarm[:charter_id], agent_id: 'a1', role: :fixer)
|
|
33
|
+
expect(result[:error]).to eq(:already_joined)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe '#complete_swarm' do
|
|
38
|
+
it 'completes a swarm' do
|
|
39
|
+
swarm = client.create_swarm(name: 'test', objective: 'test')
|
|
40
|
+
result = client.complete_swarm(charter_id: swarm[:charter_id], outcome: :success)
|
|
41
|
+
expect(result[:completed]).to be true
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe '#active_swarms' do
|
|
46
|
+
it 'lists active swarms' do
|
|
47
|
+
swarm = client.create_swarm(name: 'test', objective: 'test')
|
|
48
|
+
client.join_swarm(charter_id: swarm[:charter_id], agent_id: 'a1', role: :finder)
|
|
49
|
+
result = client.active_swarms
|
|
50
|
+
expect(result[:count]).to eq(1)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe '#timeout_stale_swarms' do
|
|
55
|
+
it 'returns zero disbanded when store is empty' do
|
|
56
|
+
result = client.timeout_stale_swarms
|
|
57
|
+
expect(result[:disbanded]).to eq(0)
|
|
58
|
+
expect(result[:disbanded_ids]).to eq([])
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'does not disband a freshly created swarm' do
|
|
62
|
+
client.create_swarm(name: 'fresh', objective: 'test')
|
|
63
|
+
result = client.timeout_stale_swarms
|
|
64
|
+
expect(result[:disbanded]).to eq(0)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'disbands a stale forming swarm' do
|
|
68
|
+
swarm = client.create_swarm(name: 'stale', objective: 'test')
|
|
69
|
+
# backdate created_at past the timeout threshold
|
|
70
|
+
store = client.send(:swarm_store)
|
|
71
|
+
store.charters[swarm[:charter_id]][:created_at] = Time.now.utc - 90_000
|
|
72
|
+
result = client.timeout_stale_swarms
|
|
73
|
+
expect(result[:disbanded]).to eq(1)
|
|
74
|
+
expect(result[:disbanded_ids]).to include(swarm[:charter_id])
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'reports correct checked count' do
|
|
78
|
+
client.create_swarm(name: 's1', objective: 'test')
|
|
79
|
+
client.create_swarm(name: 's2', objective: 'test')
|
|
80
|
+
result = client.timeout_stale_swarms
|
|
81
|
+
expect(result[:checked]).to eq(2)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
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/swarm'
|
|
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,75 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lex-swarm
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
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: Swarm orchestration and charter system for brain-modeled agentic AI
|
|
27
|
+
email:
|
|
28
|
+
- matthewdiverson@gmail.com
|
|
29
|
+
executables: []
|
|
30
|
+
extensions: []
|
|
31
|
+
extra_rdoc_files: []
|
|
32
|
+
files:
|
|
33
|
+
- Gemfile
|
|
34
|
+
- lex-swarm.gemspec
|
|
35
|
+
- lib/legion/extensions/swarm.rb
|
|
36
|
+
- lib/legion/extensions/swarm/actors/stale_check.rb
|
|
37
|
+
- lib/legion/extensions/swarm/client.rb
|
|
38
|
+
- lib/legion/extensions/swarm/helpers/charter.rb
|
|
39
|
+
- lib/legion/extensions/swarm/helpers/swarm_store.rb
|
|
40
|
+
- lib/legion/extensions/swarm/runners/swarm.rb
|
|
41
|
+
- lib/legion/extensions/swarm/version.rb
|
|
42
|
+
- spec/legion/extensions/swarm/actors/stale_check_spec.rb
|
|
43
|
+
- spec/legion/extensions/swarm/client_spec.rb
|
|
44
|
+
- spec/legion/extensions/swarm/helpers/charter_spec.rb
|
|
45
|
+
- spec/legion/extensions/swarm/helpers/swarm_store_spec.rb
|
|
46
|
+
- spec/legion/extensions/swarm/runners/swarm_spec.rb
|
|
47
|
+
- spec/spec_helper.rb
|
|
48
|
+
homepage: https://github.com/LegionIO/lex-swarm
|
|
49
|
+
licenses:
|
|
50
|
+
- MIT
|
|
51
|
+
metadata:
|
|
52
|
+
homepage_uri: https://github.com/LegionIO/lex-swarm
|
|
53
|
+
source_code_uri: https://github.com/LegionIO/lex-swarm
|
|
54
|
+
documentation_uri: https://github.com/LegionIO/lex-swarm
|
|
55
|
+
changelog_uri: https://github.com/LegionIO/lex-swarm
|
|
56
|
+
bug_tracker_uri: https://github.com/LegionIO/lex-swarm/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 Swarm
|
|
75
|
+
test_files: []
|