robot_lab-ractor 0.1.0 → 0.2.6
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 +4 -4
- data/.envrc +2 -0
- data/.loki +10 -0
- data/.quality/reek_baseline.txt +3 -0
- data/.rubocop.yml +1 -0
- data/CHANGELOG.md +25 -0
- data/CLAUDE.md +71 -0
- data/Rakefile +127 -3
- data/docs/guides/ractor-parallelism.md +75 -0
- data/docs/index.md +6 -0
- data/examples/{29_ractor_tools.rb → 01_ractor_tools.rb} +9 -35
- data/examples/{30_ractor_network.rb → 02_ractor_network.rb} +8 -21
- data/examples/03_ractor_hooks.rb +399 -0
- data/examples/common.rb +79 -0
- data/examples/xyzzy.rb +88 -0
- data/lib/robot_lab/ractor/version.rb +1 -1
- data/lib/robot_lab/ractor.rb +18 -11
- data/lib/robot_lab/ractor_boundary.rb +1 -1
- data/lib/robot_lab/ractor_job.rb +5 -1
- data/lib/robot_lab/ractor_memory_proxy.rb +2 -4
- data/lib/robot_lab/ractor_network_scheduler.rb +56 -41
- data/lib/robot_lab/ractor_worker_pool.rb +29 -25
- metadata +18 -11
|
@@ -50,21 +50,10 @@ module RobotLab
|
|
|
50
50
|
remaining = specs_with_deps.dup
|
|
51
51
|
|
|
52
52
|
until remaining.empty?
|
|
53
|
-
ready, remaining = remaining
|
|
54
|
-
|
|
55
|
-
deps == :none || deps == :optional ||
|
|
56
|
-
Array(deps).all? { |d| completed.key?(d) }
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
raise RobotLab::Error, "Circular dependency or unresolvable deps in RactorNetworkScheduler" if ready.empty?
|
|
53
|
+
ready, remaining = partition_ready(remaining, completed)
|
|
54
|
+
raise RobotLab::Error, 'Circular dependency or unresolvable deps in RactorNetworkScheduler' if ready.empty?
|
|
60
55
|
|
|
61
|
-
|
|
62
|
-
spec = entry[:spec]
|
|
63
|
-
msg = completed.values.last || message
|
|
64
|
-
Thread.new { [spec.name, execute_spec(spec, msg)] }.tap { |t| t.report_on_exception = false }
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
threads.each do |t|
|
|
56
|
+
dispatch_ready(ready, completed, message).each do |t|
|
|
68
57
|
name, result = t.value
|
|
69
58
|
completed[name] = result
|
|
70
59
|
end
|
|
@@ -79,7 +68,39 @@ module RobotLab
|
|
|
79
68
|
|
|
80
69
|
@closed = true
|
|
81
70
|
@size.times { @work_q.push(nil) }
|
|
82
|
-
@workers.each
|
|
71
|
+
@workers.each do |w|
|
|
72
|
+
w.join
|
|
73
|
+
rescue StandardError
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Called inside Ractor worker blocks — must be a class method.
|
|
79
|
+
def self.process_job(job)
|
|
80
|
+
spec = job.payload[:spec]
|
|
81
|
+
message = job.payload[:message]
|
|
82
|
+
job.reply_queue.push(build_and_run_robot(spec, message))
|
|
83
|
+
rescue StandardError => e
|
|
84
|
+
job.reply_queue.push(wrap_error(e))
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def self.build_and_run_robot(spec, message)
|
|
88
|
+
config = spec.config_hash.empty? ? nil : RobotLab::RunConfig.new(**spec.config_hash.transform_keys(&:to_sym))
|
|
89
|
+
robot = RobotLab::Robot.new(
|
|
90
|
+
name: spec.name,
|
|
91
|
+
template: spec.template&.to_sym,
|
|
92
|
+
system_prompt: spec.system_prompt,
|
|
93
|
+
config: config
|
|
94
|
+
)
|
|
95
|
+
spec.hook_classes.each { |handler| robot.on(handler) }
|
|
96
|
+
robot.run(message).last_text_content.to_s.freeze
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def self.wrap_error(err)
|
|
100
|
+
RobotLab::RactorJobError.new(
|
|
101
|
+
message: err.message.freeze,
|
|
102
|
+
backtrace: (err.backtrace || []).map(&:freeze).freeze
|
|
103
|
+
)
|
|
83
104
|
end
|
|
84
105
|
|
|
85
106
|
private
|
|
@@ -90,18 +111,16 @@ module RobotLab
|
|
|
90
111
|
reply_q = RactorQueue.new(capacity: 1)
|
|
91
112
|
|
|
92
113
|
job = RactorJob.new(
|
|
93
|
-
id:
|
|
94
|
-
type:
|
|
95
|
-
payload:
|
|
114
|
+
id: SecureRandom.uuid.freeze,
|
|
115
|
+
type: :robot,
|
|
116
|
+
payload: RactorBoundary.freeze_deep({ spec: frozen_spec, message: frozen_message }),
|
|
96
117
|
reply_queue: reply_q
|
|
97
118
|
)
|
|
98
119
|
|
|
99
120
|
@work_q.push(job)
|
|
100
121
|
result = reply_q.pop
|
|
101
122
|
|
|
102
|
-
if result.is_a?(RactorJobError)
|
|
103
|
-
raise RobotLab::Error, "Robot '#{spec.name}' failed in Ractor: #{result.message}"
|
|
104
|
-
end
|
|
123
|
+
raise RobotLab::Error, "Robot '#{spec.name}' failed in Ractor: #{result.message}" if result.is_a?(RactorJobError)
|
|
105
124
|
|
|
106
125
|
result
|
|
107
126
|
end
|
|
@@ -112,28 +131,24 @@ module RobotLab
|
|
|
112
131
|
job = q.pop
|
|
113
132
|
break if job.nil?
|
|
114
133
|
|
|
115
|
-
|
|
116
|
-
spec = job.payload[:spec]
|
|
117
|
-
message = job.payload[:message]
|
|
118
|
-
|
|
119
|
-
robot = RobotLab::Robot.new(
|
|
120
|
-
name: spec.name,
|
|
121
|
-
template: spec.template ? spec.template.to_sym : nil,
|
|
122
|
-
system_prompt: spec.system_prompt,
|
|
123
|
-
config: spec.config_hash.empty? ? nil : RobotLab::RunConfig.new(**spec.config_hash.transform_keys(&:to_sym))
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
robot_result = robot.run(message)
|
|
127
|
-
job.reply_queue.push(robot_result.last_text_content.to_s.freeze)
|
|
128
|
-
rescue => e
|
|
129
|
-
err = RobotLab::RactorJobError.new(
|
|
130
|
-
message: e.message.freeze,
|
|
131
|
-
backtrace: (e.backtrace || []).map(&:freeze).freeze
|
|
132
|
-
)
|
|
133
|
-
job.reply_queue.push(err)
|
|
134
|
-
end
|
|
134
|
+
RobotLab::RactorNetworkScheduler.process_job(job)
|
|
135
135
|
end
|
|
136
136
|
end
|
|
137
137
|
end
|
|
138
|
+
|
|
139
|
+
def partition_ready(remaining, completed)
|
|
140
|
+
remaining.partition do |entry|
|
|
141
|
+
deps = entry[:depends_on]
|
|
142
|
+
deps == :none || deps == :optional || Array(deps).all? { |d| completed.key?(d) }
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def dispatch_ready(ready, completed, message)
|
|
147
|
+
ready.map do |entry|
|
|
148
|
+
spec = entry[:spec]
|
|
149
|
+
msg = completed.values.last || message
|
|
150
|
+
Thread.new { [spec.name, execute_spec(spec, msg)] }.tap { |t| t.report_on_exception = false }
|
|
151
|
+
end
|
|
152
|
+
end
|
|
138
153
|
end
|
|
139
154
|
end
|
|
@@ -39,27 +39,22 @@ module RobotLab
|
|
|
39
39
|
# @return [Object] the tool's return value
|
|
40
40
|
# @raise [ToolError] if the tool raises inside the Ractor
|
|
41
41
|
def submit(tool_class_name, args)
|
|
42
|
-
raise ToolError,
|
|
42
|
+
raise ToolError, 'Pool is shut down' if @closed
|
|
43
43
|
|
|
44
44
|
reply_q = RactorQueue.new(capacity: 1)
|
|
45
|
-
payload = RactorBoundary.freeze_deep({
|
|
46
|
-
tool_class: tool_class_name.to_s,
|
|
47
|
-
args: args
|
|
48
|
-
})
|
|
45
|
+
payload = RactorBoundary.freeze_deep({ tool_class: tool_class_name.to_s, args: args })
|
|
49
46
|
|
|
50
47
|
job = RactorJob.new(
|
|
51
|
-
id:
|
|
52
|
-
type:
|
|
53
|
-
payload:
|
|
48
|
+
id: SecureRandom.uuid.freeze,
|
|
49
|
+
type: :tool,
|
|
50
|
+
payload: payload,
|
|
54
51
|
reply_queue: reply_q
|
|
55
52
|
)
|
|
56
53
|
|
|
57
54
|
@work_q.push(job)
|
|
58
55
|
result = reply_q.pop
|
|
59
56
|
|
|
60
|
-
if result.is_a?(RactorJobError)
|
|
61
|
-
raise ToolError, "Tool '#{tool_class_name}' failed in Ractor: #{result.message}"
|
|
62
|
-
end
|
|
57
|
+
raise ToolError, "Tool '#{tool_class_name}' failed in Ractor: #{result.message}" if result.is_a?(RactorJobError)
|
|
63
58
|
|
|
64
59
|
result
|
|
65
60
|
end
|
|
@@ -71,7 +66,28 @@ module RobotLab
|
|
|
71
66
|
|
|
72
67
|
@closed = true
|
|
73
68
|
@size.times { @work_q.push(nil) }
|
|
74
|
-
@workers.each
|
|
69
|
+
@workers.each do |w|
|
|
70
|
+
w.join
|
|
71
|
+
rescue StandardError
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Called inside Ractor worker blocks — must be a class method.
|
|
77
|
+
def self.process_job(job)
|
|
78
|
+
tool_class = Object.const_get(job.payload[:tool_class])
|
|
79
|
+
result = tool_class.new.execute(**job.payload[:args].transform_keys(&:to_sym))
|
|
80
|
+
frozen_result = ::Ractor.make_shareable(result.frozen? ? result : result.dup.freeze)
|
|
81
|
+
job.reply_queue.push(frozen_result)
|
|
82
|
+
rescue StandardError => e
|
|
83
|
+
job.reply_queue.push(wrap_error(e))
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.wrap_error(err)
|
|
87
|
+
RobotLab::RactorJobError.new(
|
|
88
|
+
message: err.message.freeze,
|
|
89
|
+
backtrace: (err.backtrace || []).map(&:freeze).freeze
|
|
90
|
+
)
|
|
75
91
|
end
|
|
76
92
|
|
|
77
93
|
private
|
|
@@ -82,19 +98,7 @@ module RobotLab
|
|
|
82
98
|
job = q.pop
|
|
83
99
|
break if job.nil?
|
|
84
100
|
|
|
85
|
-
|
|
86
|
-
tool_class = Object.const_get(job.payload[:tool_class])
|
|
87
|
-
tool = tool_class.new
|
|
88
|
-
result = tool.execute(**job.payload[:args].transform_keys(&:to_sym))
|
|
89
|
-
frozen_result = ::Ractor.make_shareable(result.frozen? ? result : result.dup.freeze)
|
|
90
|
-
job.reply_queue.push(frozen_result)
|
|
91
|
-
rescue => e
|
|
92
|
-
err = RobotLab::RactorJobError.new(
|
|
93
|
-
message: e.message.freeze,
|
|
94
|
-
backtrace: (e.backtrace || []).map(&:freeze).freeze
|
|
95
|
-
)
|
|
96
|
-
job.reply_queue.push(err)
|
|
97
|
-
end
|
|
101
|
+
RobotLab::RactorWorkerPool.process_job(job)
|
|
98
102
|
end
|
|
99
103
|
end
|
|
100
104
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: robot_lab-ractor
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dewayne VanHoozer
|
|
@@ -10,7 +10,7 @@ cert_chain: []
|
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
13
|
+
name: ractor_queue
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
@@ -24,7 +24,7 @@ dependencies:
|
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '0'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
|
-
name:
|
|
27
|
+
name: ractor-wrapper
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - ">="
|
|
@@ -38,19 +38,19 @@ dependencies:
|
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '0'
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
|
-
name:
|
|
41
|
+
name: robot_lab
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
|
-
- - "
|
|
44
|
+
- - "~>"
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version:
|
|
46
|
+
version: 0.2.0
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
|
-
- - "
|
|
51
|
+
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version:
|
|
53
|
+
version: 0.2.0
|
|
54
54
|
description: Provides RobotLab::RactorWorkerPool — a pool of Ruby Ractor workers for
|
|
55
55
|
executing CPU-bound, Ractor-safe tools in parallel. Includes RactorBoundary (deep-freeze
|
|
56
56
|
utility for crossing Ractor boundaries), RactorJob/RactorJobError/RobotSpec (frozen
|
|
@@ -64,7 +64,11 @@ extra_rdoc_files: []
|
|
|
64
64
|
files:
|
|
65
65
|
- ".envrc"
|
|
66
66
|
- ".github/workflows/deploy-github-pages.yml"
|
|
67
|
+
- ".loki"
|
|
68
|
+
- ".quality/reek_baseline.txt"
|
|
69
|
+
- ".rubocop.yml"
|
|
67
70
|
- CHANGELOG.md
|
|
71
|
+
- CLAUDE.md
|
|
68
72
|
- LICENSE.txt
|
|
69
73
|
- README.md
|
|
70
74
|
- Rakefile
|
|
@@ -72,8 +76,11 @@ files:
|
|
|
72
76
|
- docs/index.md
|
|
73
77
|
- docs/superpowers/plans/2026-04-14-ractor-integration.md
|
|
74
78
|
- docs/superpowers/specs/2026-04-14-ractor-integration-design.md
|
|
75
|
-
- examples/
|
|
76
|
-
- examples/
|
|
79
|
+
- examples/01_ractor_tools.rb
|
|
80
|
+
- examples/02_ractor_network.rb
|
|
81
|
+
- examples/03_ractor_hooks.rb
|
|
82
|
+
- examples/common.rb
|
|
83
|
+
- examples/xyzzy.rb
|
|
77
84
|
- lib/robot_lab/ractor.rb
|
|
78
85
|
- lib/robot_lab/ractor/version.rb
|
|
79
86
|
- lib/robot_lab/ractor_boundary.rb
|
|
@@ -104,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
104
111
|
- !ruby/object:Gem::Version
|
|
105
112
|
version: '0'
|
|
106
113
|
requirements: []
|
|
107
|
-
rubygems_version: 4.0.
|
|
114
|
+
rubygems_version: 4.0.17
|
|
108
115
|
specification_version: 4
|
|
109
116
|
summary: Ractor-based parallel tool execution for RobotLab agents
|
|
110
117
|
test_files: []
|