robot_lab 0.3.0 → 0.5.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 +4 -4
- data/CHANGELOG.md +60 -0
- data/README.md +29 -19
- data/lib/robot_lab/errors.rb +4 -0
- data/lib/robot_lab/hook_context.rb +9 -2
- data/lib/robot_lab/mcp/registry.rb +99 -0
- data/lib/robot_lab/mcp/server.rb +47 -83
- data/lib/robot_lab/mcp/server_discovery.rb +22 -9
- data/lib/robot_lab/mcp.rb +259 -0
- data/lib/robot_lab/robot/mcp_management.rb +159 -105
- data/lib/robot_lab/robot/tool_call_gating.rb +144 -0
- data/lib/robot_lab/robot.rb +72 -99
- data/lib/robot_lab/run_config.rb +3 -2
- data/lib/robot_lab/tool.rb +8 -23
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab.rb +1 -4
- metadata +16 -292
- data/.architecture/AGENTS.md +0 -32
- data/.architecture/config.yml +0 -8
- data/.architecture/members.yml +0 -60
- data/.architecture/reviews/feature-free-will.md +0 -490
- data/.architecture/reviews/overall-codebase.md +0 -427
- data/.claude/settings.local.json +0 -57
- data/.codex/config.toml +0 -2
- data/.envrc +0 -6
- data/.github/workflows/deploy-github-pages.yml +0 -52
- data/.irbrc +0 -6
- data/.loki +0 -65
- data/.quality/reek_baseline.txt +0 -43
- data/.rubocop.yml +0 -10
- data/Archspec.rb +0 -44
- data/CLAUDE.md +0 -141
- data/COMMITS.md +0 -196
- data/Rakefile +0 -193
- data/_typos.toml +0 -21
- data/agent2agent_review.md +0 -192
- data/agentf_improvements.md +0 -253
- data/agents.md +0 -14
- data/docs/api/core/index.md +0 -147
- data/docs/api/core/memory.md +0 -578
- data/docs/api/core/network.md +0 -553
- data/docs/api/core/result.md +0 -211
- data/docs/api/core/robot.md +0 -1667
- data/docs/api/core/state.md +0 -145
- data/docs/api/core/tool.md +0 -520
- data/docs/api/errors.md +0 -323
- data/docs/api/hooks.md +0 -469
- data/docs/api/index.md +0 -137
- data/docs/api/mcp/client.md +0 -387
- data/docs/api/mcp/index.md +0 -267
- data/docs/api/mcp/server.md +0 -252
- data/docs/api/mcp/transports.md +0 -339
- data/docs/api/messages/index.md +0 -126
- data/docs/api/messages/text-message.md +0 -170
- data/docs/api/messages/tool-call-message.md +0 -206
- data/docs/api/messages/tool-result-message.md +0 -242
- data/docs/api/messages/user-message.md +0 -232
- data/docs/api/skills.md +0 -423
- data/docs/api/streaming/context.md +0 -280
- data/docs/api/streaming/events.md +0 -185
- data/docs/api/streaming/index.md +0 -156
- data/docs/api/support.md +0 -846
- data/docs/architecture/core-concepts.md +0 -575
- data/docs/architecture/index.md +0 -252
- data/docs/architecture/message-flow.md +0 -366
- data/docs/architecture/network-orchestration.md +0 -478
- data/docs/architecture/robot-execution.md +0 -396
- data/docs/architecture/state-management.md +0 -410
- data/docs/assets/css/custom.css +0 -56
- data/docs/assets/images/robot_lab.jpg +0 -0
- data/docs/concepts.md +0 -641
- data/docs/examples/basic-chat.md +0 -258
- data/docs/examples/index.md +0 -425
- data/docs/examples/mcp-server.md +0 -403
- data/docs/examples/multi-robot-network.md +0 -476
- data/docs/examples/tool-usage.md +0 -468
- data/docs/getting-started/configuration.md +0 -645
- data/docs/getting-started/index.md +0 -56
- data/docs/getting-started/installation.md +0 -197
- data/docs/getting-started/quick-start.md +0 -293
- data/docs/guides/building-robots.md +0 -1187
- data/docs/guides/creating-networks.md +0 -736
- data/docs/guides/hooks.md +0 -1206
- data/docs/guides/index.md +0 -78
- data/docs/guides/knowledge.md +0 -219
- data/docs/guides/mcp-integration.md +0 -740
- data/docs/guides/memory.md +0 -458
- data/docs/guides/observability.md +0 -733
- data/docs/guides/streaming.md +0 -323
- data/docs/guides/using-tools.md +0 -780
- data/docs/index.md +0 -280
- data/docs/superpowers/plans/2026-05-06-agentskills.md +0 -1303
- data/docs/superpowers/specs/2026-05-06-agentskills-design.md +0 -247
- data/examples/.envrc +0 -3
- data/examples/01_simple_robot.rb +0 -31
- data/examples/02_tools.rb +0 -109
- data/examples/03_network.rb +0 -118
- data/examples/04_mcp.rb +0 -217
- data/examples/05_streaming.rb +0 -137
- data/examples/06_prompt_templates.rb +0 -325
- data/examples/07_network_memory.rb +0 -313
- data/examples/08_llm_config.rb +0 -162
- data/examples/09_chaining.rb +0 -251
- data/examples/10_memory.rb +0 -315
- data/examples/11_network_introspection.rb +0 -239
- data/examples/12_message_bus.rb +0 -67
- data/examples/13_spawn.rb +0 -85
- data/examples/14_rusty_circuit/.envrc +0 -1
- data/examples/14_rusty_circuit/.gitignore +0 -1
- data/examples/14_rusty_circuit/README.md +0 -1
- data/examples/14_rusty_circuit/comic.rb +0 -149
- data/examples/14_rusty_circuit/display.rb +0 -217
- data/examples/14_rusty_circuit/heckler.rb +0 -65
- data/examples/14_rusty_circuit/open_mic.rb +0 -132
- data/examples/14_rusty_circuit/prompts/open_mic_comic.md +0 -20
- data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +0 -23
- data/examples/14_rusty_circuit/prompts/open_mic_scout.md +0 -20
- data/examples/14_rusty_circuit/scout.rb +0 -165
- data/examples/14_rusty_circuit/show.log +0 -234
- data/examples/15_memory_network_and_bus/.envrc +0 -1
- data/examples/15_memory_network_and_bus/README.md +0 -66
- data/examples/15_memory_network_and_bus/editor_in_chief.rb +0 -24
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +0 -213
- data/examples/15_memory_network_and_bus/linux_writer.rb +0 -80
- data/examples/15_memory_network_and_bus/os_editor.rb +0 -48
- data/examples/15_memory_network_and_bus/os_writer.rb +0 -49
- data/examples/15_memory_network_and_bus/output/combined_article.md +0 -11
- data/examples/15_memory_network_and_bus/output/final_article.md +0 -8
- data/examples/15_memory_network_and_bus/output/linux_draft.md +0 -7
- data/examples/15_memory_network_and_bus/output/mac_draft.md +0 -7
- data/examples/15_memory_network_and_bus/output/memory.json +0 -13
- data/examples/15_memory_network_and_bus/output/revision_1.md +0 -29
- data/examples/15_memory_network_and_bus/output/revision_2.md +0 -50
- data/examples/15_memory_network_and_bus/output/revision_3.md +0 -8
- data/examples/15_memory_network_and_bus/output/windows_draft.md +0 -7
- data/examples/15_memory_network_and_bus/prompts/os_advocate.md +0 -13
- data/examples/15_memory_network_and_bus/prompts/os_chief.md +0 -13
- data/examples/15_memory_network_and_bus/prompts/os_editor.md +0 -13
- data/examples/16_writers_room/.envrc +0 -1
- data/examples/16_writers_room/display.rb +0 -158
- data/examples/16_writers_room/output/.gitignore +0 -4
- data/examples/16_writers_room/output/README.md +0 -69
- data/examples/16_writers_room/output/opus_001.md +0 -263
- data/examples/16_writers_room/output/opus_001_notes.log +0 -470
- data/examples/16_writers_room/output/opus_002.md +0 -245
- data/examples/16_writers_room/output/opus_002_notes.log +0 -546
- data/examples/16_writers_room/output/opus_002_screenplay.md +0 -7989
- data/examples/16_writers_room/output/opus_002_screenplay_notes.md +0 -993
- data/examples/16_writers_room/prompts/screenplay_writer.md +0 -66
- data/examples/16_writers_room/prompts/writer.md +0 -37
- data/examples/16_writers_room/room.rb +0 -186
- data/examples/16_writers_room/tools.rb +0 -173
- data/examples/16_writers_room/writer.rb +0 -121
- data/examples/16_writers_room/writers_room.rb +0 -256
- data/examples/17_skills.rb +0 -234
- data/examples/18_rails/.envrc +0 -3
- data/examples/18_rails/.gitignore +0 -5
- data/examples/18_rails/Gemfile +0 -11
- data/examples/18_rails/README.md +0 -67
- data/examples/18_rails/Rakefile +0 -4
- data/examples/18_rails/app/controllers/application_controller.rb +0 -4
- data/examples/18_rails/app/controllers/chat_controller.rb +0 -50
- data/examples/18_rails/app/jobs/application_job.rb +0 -4
- data/examples/18_rails/app/jobs/robot_run_job.rb +0 -25
- data/examples/18_rails/app/models/application_record.rb +0 -5
- data/examples/18_rails/app/models/robot_lab_result.rb +0 -36
- data/examples/18_rails/app/models/robot_lab_thread.rb +0 -23
- data/examples/18_rails/app/robots/chat_robot.rb +0 -25
- data/examples/18_rails/app/tools/time_tool.rb +0 -9
- data/examples/18_rails/app/views/chat/_user_message.html.erb +0 -1
- data/examples/18_rails/app/views/chat/index.html.erb +0 -67
- data/examples/18_rails/app/views/layouts/application.html.erb +0 -49
- data/examples/18_rails/bin/dev +0 -7
- data/examples/18_rails/bin/rails +0 -6
- data/examples/18_rails/bin/setup +0 -15
- data/examples/18_rails/config/application.rb +0 -33
- data/examples/18_rails/config/cable.yml +0 -2
- data/examples/18_rails/config/database.yml +0 -5
- data/examples/18_rails/config/environment.rb +0 -4
- data/examples/18_rails/config/initializers/robot_lab.rb +0 -11
- data/examples/18_rails/config/routes.rb +0 -6
- data/examples/18_rails/config.ru +0 -4
- data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +0 -32
- data/examples/19_token_tracking.rb +0 -138
- data/examples/20_circuit_breaker.rb +0 -147
- data/examples/21_learning_loop.rb +0 -181
- data/examples/22_context_compression.rb +0 -172
- data/examples/23_convergence.rb +0 -133
- data/examples/24_structured_delegation.rb +0 -151
- data/examples/25_history_search/conversation.jsonl +0 -30
- data/examples/25_history_search.rb +0 -133
- data/examples/26_document_store/api_versioning_adr.md +0 -52
- data/examples/26_document_store/incident_postmortem.md +0 -46
- data/examples/26_document_store/postgres_runbook.md +0 -49
- data/examples/26_document_store/redis_caching_guide.md +0 -48
- data/examples/26_document_store/sidekiq_guide.md +0 -51
- data/examples/26_document_store.rb +0 -140
- data/examples/27_incident_response/README.md +0 -65
- data/examples/27_incident_response/incident_response.rb +0 -261
- data/examples/28_mcp_discovery.rb +0 -113
- data/examples/29_ractor_tools.rb +0 -240
- data/examples/30_ractor_network.rb +0 -252
- data/examples/31_launch_assessment.rb +0 -246
- data/examples/32_newsletter_reader.rb +0 -193
- data/examples/33_stock_generator.rb +0 -80
- data/examples/33_stock_predictor.rb +0 -327
- data/examples/34_agentskills.rb +0 -76
- data/examples/35_hooks.rb +0 -266
- data/examples/README.md +0 -550
- data/examples/common.rb +0 -180
- data/examples/prompts/assistant.md +0 -5
- data/examples/prompts/audit_trail.md +0 -8
- data/examples/prompts/billing.md +0 -10
- data/examples/prompts/classifier.md +0 -7
- data/examples/prompts/comedian.md +0 -6
- data/examples/prompts/comedy_critic.md +0 -10
- data/examples/prompts/configurable.md +0 -9
- data/examples/prompts/dispatcher.md +0 -12
- data/examples/prompts/entity_extractor.md +0 -14
- data/examples/prompts/escalation.md +0 -42
- data/examples/prompts/frontmatter_mcp_test.md +0 -9
- data/examples/prompts/frontmatter_named_test.md +0 -5
- data/examples/prompts/frontmatter_tools_test.md +0 -6
- data/examples/prompts/general.md +0 -7
- data/examples/prompts/github_assistant.md +0 -14
- data/examples/prompts/helper.md +0 -4
- data/examples/prompts/incident_responder.md +0 -18
- data/examples/prompts/keyword_extractor.md +0 -11
- data/examples/prompts/llm_config_demo.md +0 -20
- data/examples/prompts/order_support.md +0 -35
- data/examples/prompts/os_advocate.md +0 -13
- data/examples/prompts/os_chief.md +0 -13
- data/examples/prompts/os_editor.md +0 -13
- data/examples/prompts/parameterized_main_test.md +0 -6
- data/examples/prompts/pii_redactor.md +0 -7
- data/examples/prompts/product_support.md +0 -37
- data/examples/prompts/runbook_protocol.md +0 -12
- data/examples/prompts/sentiment_analyzer.md +0 -12
- data/examples/prompts/skill_a_test.md +0 -4
- data/examples/prompts/skill_b_test.md +0 -4
- data/examples/prompts/skill_config_test.md +0 -5
- data/examples/prompts/skill_cycle_a_test.md +0 -6
- data/examples/prompts/skill_cycle_b_test.md +0 -6
- data/examples/prompts/skill_description_test.md +0 -4
- data/examples/prompts/skill_leaf_test.md +0 -4
- data/examples/prompts/skill_nested_test.md +0 -6
- data/examples/prompts/skill_refs_main_test.md +0 -6
- data/examples/prompts/skill_self_ref_test.md +0 -6
- data/examples/prompts/skill_with_mcp_test.md +0 -9
- data/examples/prompts/skill_with_params_test.md +0 -6
- data/examples/prompts/skill_with_robot_name_test.md +0 -5
- data/examples/prompts/skill_with_tools_test.md +0 -6
- data/examples/prompts/sre_compliance.md +0 -10
- data/examples/prompts/structured_output.md +0 -7
- data/examples/prompts/synthesizer.md +0 -17
- data/examples/prompts/technical.md +0 -10
- data/examples/prompts/template_with_skills_test.md +0 -6
- data/examples/prompts/triage.md +0 -22
- data/examples/ruboruby.md +0 -423
- data/examples/run_all.rb +0 -60
- data/examples/xyzzy.rb +0 -97
- data/lib/robot_lab/mcp/client.rb +0 -223
- data/lib/robot_lab/mcp/connection_poller.rb +0 -195
- data/lib/robot_lab/mcp/transports/base.rb +0 -64
- data/lib/robot_lab/mcp/transports/sse.rb +0 -120
- data/lib/robot_lab/mcp/transports/stdio.rb +0 -170
- data/lib/robot_lab/mcp/transports/streamable_http.rb +0 -141
- data/lib/robot_lab/mcp/transports/websocket.rb +0 -111
- data/logfile +0 -8
- data/mkdocs.yml +0 -209
- data/robot_concurrency.md +0 -38
- data/simple_acp_review.md +0 -298
- data/temp.md +0 -6
- data/tool_manifest_plan.md +0 -155
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "open3"
|
|
4
|
-
require "json"
|
|
5
|
-
require "timeout"
|
|
6
|
-
|
|
7
|
-
module RobotLab
|
|
8
|
-
module MCP
|
|
9
|
-
module Transports
|
|
10
|
-
# StdIO transport for local MCP servers
|
|
11
|
-
#
|
|
12
|
-
# Spawns a subprocess and communicates via stdin/stdout.
|
|
13
|
-
# All blocking I/O is wrapped with a configurable timeout
|
|
14
|
-
# so a missing or hung server cannot block the caller forever.
|
|
15
|
-
#
|
|
16
|
-
# @example
|
|
17
|
-
# transport = Stdio.new(
|
|
18
|
-
# { command: "mcp-server-filesystem", args: ["--root", "/data"] },
|
|
19
|
-
# timeout: 10
|
|
20
|
-
# )
|
|
21
|
-
#
|
|
22
|
-
# :reek:InstanceVariableAssumption -- @config and @timeout are assigned in Base#initialize (reek does not trace super).
|
|
23
|
-
# :reek:RepeatedConditional -- @connected is the connection-lifecycle guard; every IO operation must check it.
|
|
24
|
-
class Stdio < Base
|
|
25
|
-
# Creates a new Stdio transport.
|
|
26
|
-
#
|
|
27
|
-
# @param config [Hash] transport configuration
|
|
28
|
-
# @option config [String] :command the command to execute
|
|
29
|
-
# @option config [Array<String>] :args command arguments
|
|
30
|
-
# @option config [Hash] :env environment variables
|
|
31
|
-
# @option config [Numeric] :timeout request timeout in seconds (default: 15)
|
|
32
|
-
def initialize(config)
|
|
33
|
-
super
|
|
34
|
-
@stdin = nil
|
|
35
|
-
@stdout = nil
|
|
36
|
-
@stderr = nil
|
|
37
|
-
@wait_thread = nil
|
|
38
|
-
@connected = false
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Connect to the MCP server via stdio.
|
|
42
|
-
#
|
|
43
|
-
# @return [self]
|
|
44
|
-
# @raise [MCPError] if the server process cannot be started or does not
|
|
45
|
-
# respond to the MCP initialize handshake within the timeout period
|
|
46
|
-
# :reek:TooManyStatements -- linear spawn/verify/handshake sequence with per-failure-mode rescues.
|
|
47
|
-
def connect
|
|
48
|
-
return self if @connected
|
|
49
|
-
|
|
50
|
-
command = @config[:command]
|
|
51
|
-
args = @config[:args] || []
|
|
52
|
-
env = @config[:env] || {}
|
|
53
|
-
|
|
54
|
-
full_env = ENV.to_h.merge(env.transform_keys(&:to_s))
|
|
55
|
-
|
|
56
|
-
@stdin, @stdout, @stderr, @wait_thread = Open3.popen3(full_env, command, *args)
|
|
57
|
-
|
|
58
|
-
# Verify the process actually started
|
|
59
|
-
unless @wait_thread.alive?
|
|
60
|
-
raise MCPError, "MCP server process exited immediately (command: #{command})"
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
@connected = true
|
|
64
|
-
|
|
65
|
-
# Initialize MCP protocol — this must complete within the timeout
|
|
66
|
-
send_initialize
|
|
67
|
-
|
|
68
|
-
self
|
|
69
|
-
rescue Errno::ENOENT => e
|
|
70
|
-
cleanup_process
|
|
71
|
-
raise MCPError, "MCP server command not found: #{command} (#{e.message})"
|
|
72
|
-
rescue Timeout::Error
|
|
73
|
-
cleanup_process
|
|
74
|
-
raise MCPError, "MCP server '#{command}' did not respond within #{@timeout}s"
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# Send a JSON-RPC request to the MCP server.
|
|
78
|
-
#
|
|
79
|
-
# @param message [Hash] JSON-RPC message
|
|
80
|
-
# @return [Hash] the response
|
|
81
|
-
# @raise [MCPError] if not connected, no response, or timeout
|
|
82
|
-
# :reek:TooManyStatements -- write-then-read loop must stay inside the one Timeout block.
|
|
83
|
-
def send_request(message)
|
|
84
|
-
raise MCPError, "Not connected" unless @connected
|
|
85
|
-
|
|
86
|
-
Timeout.timeout(@timeout, Timeout::Error) do
|
|
87
|
-
json = message.to_json
|
|
88
|
-
@stdin.puts(json)
|
|
89
|
-
@stdin.flush
|
|
90
|
-
|
|
91
|
-
loop do
|
|
92
|
-
response_line = @stdout.gets
|
|
93
|
-
raise MCPError, "No response from MCP server (EOF on stdout)" unless response_line
|
|
94
|
-
|
|
95
|
-
parsed = JSON.parse(response_line, symbolize_names: true)
|
|
96
|
-
|
|
97
|
-
# Skip notifications (messages without an id)
|
|
98
|
-
next if parsed[:method] && !parsed.key?(:id)
|
|
99
|
-
|
|
100
|
-
return parsed
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
rescue Timeout::Error
|
|
104
|
-
raise MCPError, "MCP server did not respond within #{@timeout}s"
|
|
105
|
-
rescue Errno::EPIPE, IOError => e
|
|
106
|
-
@connected = false
|
|
107
|
-
raise MCPError, "MCP server connection lost: #{e.message}"
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# Close the connection to the MCP server.
|
|
111
|
-
#
|
|
112
|
-
# @return [self]
|
|
113
|
-
def close
|
|
114
|
-
return self unless @connected
|
|
115
|
-
|
|
116
|
-
cleanup_process
|
|
117
|
-
self
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# Check if the transport is connected.
|
|
121
|
-
#
|
|
122
|
-
# @return [Boolean] true if connected and process is alive
|
|
123
|
-
def connected?
|
|
124
|
-
@connected && @wait_thread&.alive?
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# Expose the underlying IO objects so ConnectionPoller can
|
|
128
|
-
# register them in its IO.select loop.
|
|
129
|
-
#
|
|
130
|
-
# @api private
|
|
131
|
-
attr_reader :stdin, :stdout
|
|
132
|
-
|
|
133
|
-
private
|
|
134
|
-
|
|
135
|
-
def send_initialize
|
|
136
|
-
send_request(
|
|
137
|
-
jsonrpc: "2.0",
|
|
138
|
-
id: 0,
|
|
139
|
-
method: "initialize",
|
|
140
|
-
params: {
|
|
141
|
-
protocolVersion: "2024-11-05",
|
|
142
|
-
capabilities: {},
|
|
143
|
-
clientInfo: {
|
|
144
|
-
name: "RobotLab",
|
|
145
|
-
version: RobotLab::VERSION
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
# Send initialized notification
|
|
151
|
-
@stdin.puts({ jsonrpc: "2.0", method: "notifications/initialized" }.to_json)
|
|
152
|
-
@stdin.flush
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
# :reek:TooManyStatements -- best-effort teardown; each handle is closed and nilled independently.
|
|
156
|
-
def cleanup_process
|
|
157
|
-
@connected = false
|
|
158
|
-
@stdin&.close rescue nil
|
|
159
|
-
@stdout&.close rescue nil
|
|
160
|
-
@stderr&.close rescue nil
|
|
161
|
-
@wait_thread&.kill if @wait_thread&.alive?
|
|
162
|
-
@stdin = nil
|
|
163
|
-
@stdout = nil
|
|
164
|
-
@stderr = nil
|
|
165
|
-
@wait_thread = nil
|
|
166
|
-
end
|
|
167
|
-
end
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
end
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RobotLab
|
|
4
|
-
module MCP
|
|
5
|
-
module Transports
|
|
6
|
-
# Streamable HTTP transport for MCP servers
|
|
7
|
-
#
|
|
8
|
-
# Supports session management and reconnection.
|
|
9
|
-
#
|
|
10
|
-
# @example
|
|
11
|
-
# transport = StreamableHTTP.new(
|
|
12
|
-
# url: "https://server.smithery.ai/neon/mcp",
|
|
13
|
-
# session_id: "abc123"
|
|
14
|
-
# )
|
|
15
|
-
#
|
|
16
|
-
# :reek:InstanceVariableAssumption -- @config is assigned in Base#initialize (reek does not trace super).
|
|
17
|
-
# :reek:RepeatedConditional -- @connected is the connection-lifecycle guard; every operation must check it.
|
|
18
|
-
class StreamableHTTP < Base
|
|
19
|
-
# Creates a new StreamableHTTP transport.
|
|
20
|
-
#
|
|
21
|
-
# @param config [Hash] transport configuration
|
|
22
|
-
# @option config [String] :url HTTP server URL
|
|
23
|
-
# @option config [String] :session_id optional session identifier
|
|
24
|
-
# @option config [Proc] :auth_provider optional authentication callback
|
|
25
|
-
def initialize(config)
|
|
26
|
-
super
|
|
27
|
-
@client = nil
|
|
28
|
-
@connected = false
|
|
29
|
-
@session_id = config[:session_id]
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# Connect to the MCP server via HTTP.
|
|
33
|
-
#
|
|
34
|
-
# @return [self]
|
|
35
|
-
# @raise [MCPError] if async-http gem is not available
|
|
36
|
-
# :reek:TooManyStatements -- linear require/connect/handshake sequence inside the Async block.
|
|
37
|
-
def connect
|
|
38
|
-
return self if @connected
|
|
39
|
-
|
|
40
|
-
require "async"
|
|
41
|
-
require "async/http/client"
|
|
42
|
-
require "async/http/endpoint"
|
|
43
|
-
|
|
44
|
-
url = @config[:url]
|
|
45
|
-
|
|
46
|
-
Async do
|
|
47
|
-
endpoint = Async::HTTP::Endpoint.parse(url)
|
|
48
|
-
@client = Async::HTTP::Client.new(endpoint)
|
|
49
|
-
@connected = true
|
|
50
|
-
|
|
51
|
-
# Initialize MCP protocol
|
|
52
|
-
result = send_initialize
|
|
53
|
-
@session_id ||= result.dig(:serverInfo, :sessionId)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
self
|
|
57
|
-
rescue LoadError => e
|
|
58
|
-
raise MCPError, "async-http gem required for HTTP transport: #{e.message}"
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Send a JSON-RPC request to the MCP server.
|
|
62
|
-
#
|
|
63
|
-
# @param message [Hash] JSON-RPC message
|
|
64
|
-
# @return [Hash] the response
|
|
65
|
-
# @raise [MCPError] if not connected
|
|
66
|
-
# :reek:TooManyStatements -- header assembly and POST must stay inside the one Async block.
|
|
67
|
-
def send_request(message)
|
|
68
|
-
raise MCPError, "Not connected" unless @connected
|
|
69
|
-
|
|
70
|
-
require "async"
|
|
71
|
-
|
|
72
|
-
Async do
|
|
73
|
-
headers = {
|
|
74
|
-
"Content-Type" => "application/json",
|
|
75
|
-
"Accept" => "application/json"
|
|
76
|
-
}
|
|
77
|
-
headers["X-Session-ID"] = @session_id if @session_id
|
|
78
|
-
|
|
79
|
-
# Add auth if configured
|
|
80
|
-
if @config[:auth_provider]
|
|
81
|
-
auth_header = @config[:auth_provider].call
|
|
82
|
-
headers["Authorization"] = auth_header if auth_header
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
response = @client.post(
|
|
86
|
-
@config[:url],
|
|
87
|
-
headers,
|
|
88
|
-
[message.to_json]
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
body = response.read
|
|
92
|
-
JSON.parse(body, symbolize_names: true)
|
|
93
|
-
end.wait
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
# Close the HTTP connection.
|
|
97
|
-
#
|
|
98
|
-
# @return [self]
|
|
99
|
-
def close
|
|
100
|
-
return self unless @connected
|
|
101
|
-
|
|
102
|
-
@client&.close
|
|
103
|
-
@connected = false
|
|
104
|
-
@client = nil
|
|
105
|
-
|
|
106
|
-
self
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# Check if the transport is connected.
|
|
110
|
-
#
|
|
111
|
-
# @return [Boolean] true if connected
|
|
112
|
-
def connected?
|
|
113
|
-
@connected
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
# Returns the session identifier.
|
|
117
|
-
#
|
|
118
|
-
# @return [String, nil] the session ID
|
|
119
|
-
attr_reader :session_id
|
|
120
|
-
|
|
121
|
-
private
|
|
122
|
-
|
|
123
|
-
def send_initialize
|
|
124
|
-
send_request(
|
|
125
|
-
jsonrpc: "2.0",
|
|
126
|
-
id: 0,
|
|
127
|
-
method: "initialize",
|
|
128
|
-
params: {
|
|
129
|
-
protocolVersion: "2024-11-05",
|
|
130
|
-
capabilities: {},
|
|
131
|
-
clientInfo: {
|
|
132
|
-
name: "RobotLab",
|
|
133
|
-
version: RobotLab::VERSION
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
)
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
end
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RobotLab
|
|
4
|
-
module MCP
|
|
5
|
-
module Transports
|
|
6
|
-
# WebSocket transport for MCP servers
|
|
7
|
-
#
|
|
8
|
-
# Uses async-websocket for non-blocking communication.
|
|
9
|
-
#
|
|
10
|
-
# @example
|
|
11
|
-
# transport = WebSocket.new(url: "ws://localhost:8080")
|
|
12
|
-
#
|
|
13
|
-
# :reek:InstanceVariableAssumption -- @config is assigned in Base#initialize (reek does not trace super).
|
|
14
|
-
# :reek:RepeatedConditional -- @connected is the connection-lifecycle guard; every operation must check it.
|
|
15
|
-
class WebSocket < Base
|
|
16
|
-
# Creates a new WebSocket transport.
|
|
17
|
-
#
|
|
18
|
-
# @param config [Hash] transport configuration
|
|
19
|
-
# @option config [String] :url WebSocket server URL
|
|
20
|
-
def initialize(config)
|
|
21
|
-
super
|
|
22
|
-
@connection = nil
|
|
23
|
-
@connected = false
|
|
24
|
-
@pending_requests = {}
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Connect to the MCP server via WebSocket.
|
|
28
|
-
#
|
|
29
|
-
# @return [self]
|
|
30
|
-
# @raise [MCPError] if async-websocket gem is not available
|
|
31
|
-
# :reek:TooManyStatements -- linear require/connect/handshake sequence inside the Async block.
|
|
32
|
-
def connect
|
|
33
|
-
return self if @connected
|
|
34
|
-
|
|
35
|
-
require "async"
|
|
36
|
-
require "async/websocket/client"
|
|
37
|
-
|
|
38
|
-
url = @config[:url]
|
|
39
|
-
|
|
40
|
-
Async do
|
|
41
|
-
endpoint = Async::HTTP::Endpoint.parse(url)
|
|
42
|
-
@connection = Async::WebSocket::Client.connect(endpoint)
|
|
43
|
-
@connected = true
|
|
44
|
-
|
|
45
|
-
# Initialize MCP protocol
|
|
46
|
-
send_initialize
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
self
|
|
50
|
-
rescue LoadError => e
|
|
51
|
-
raise MCPError, "async-websocket gem required for WebSocket transport: #{e.message}"
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Send a JSON-RPC request to the MCP server.
|
|
55
|
-
#
|
|
56
|
-
# @param message [Hash] JSON-RPC message
|
|
57
|
-
# @return [Hash] the response
|
|
58
|
-
# @raise [MCPError] if not connected
|
|
59
|
-
def send_request(message)
|
|
60
|
-
raise MCPError, "Not connected" unless @connected
|
|
61
|
-
|
|
62
|
-
Async do
|
|
63
|
-
@connection.write(message.to_json)
|
|
64
|
-
@connection.flush
|
|
65
|
-
|
|
66
|
-
response_text = @connection.read
|
|
67
|
-
JSON.parse(response_text, symbolize_names: true)
|
|
68
|
-
end.wait
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# Close the WebSocket connection.
|
|
72
|
-
#
|
|
73
|
-
# @return [self]
|
|
74
|
-
def close
|
|
75
|
-
return self unless @connected
|
|
76
|
-
|
|
77
|
-
@connection&.close
|
|
78
|
-
@connected = false
|
|
79
|
-
@connection = nil
|
|
80
|
-
|
|
81
|
-
self
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Check if the transport is connected.
|
|
85
|
-
#
|
|
86
|
-
# @return [Boolean] true if connected
|
|
87
|
-
def connected?
|
|
88
|
-
@connected
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
private
|
|
92
|
-
|
|
93
|
-
def send_initialize
|
|
94
|
-
send_request(
|
|
95
|
-
jsonrpc: "2.0",
|
|
96
|
-
id: 0,
|
|
97
|
-
method: "initialize",
|
|
98
|
-
params: {
|
|
99
|
-
protocolVersion: "2024-11-05",
|
|
100
|
-
capabilities: {},
|
|
101
|
-
clientInfo: {
|
|
102
|
-
name: "RobotLab",
|
|
103
|
-
version: RobotLab::VERSION
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
)
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
end
|
data/logfile
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
2026-02-25 20:56:30.644 CST [47152] LOG: starting PostgreSQL 18.2 (Homebrew) on aarch64-apple-darwin25.2.0, compiled by Apple clang version 17.0.0 (clang-1700.6.3.2), 64-bit
|
|
2
|
-
2026-02-25 20:56:30.646 CST [47152] LOG: could not bind IPv6 address "::1": Address already in use
|
|
3
|
-
2026-02-25 20:56:30.646 CST [47152] HINT: Is another postmaster already running on port 5432? If not, wait a few seconds and retry.
|
|
4
|
-
2026-02-25 20:56:30.646 CST [47152] LOG: could not bind IPv4 address "127.0.0.1": Address already in use
|
|
5
|
-
2026-02-25 20:56:30.646 CST [47152] HINT: Is another postmaster already running on port 5432? If not, wait a few seconds and retry.
|
|
6
|
-
2026-02-25 20:56:30.646 CST [47152] WARNING: could not create listen socket for "localhost"
|
|
7
|
-
2026-02-25 20:56:30.646 CST [47152] FATAL: could not create any TCP/IP sockets
|
|
8
|
-
2026-02-25 20:56:30.646 CST [47152] LOG: database system is shut down
|
data/mkdocs.yml
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
# MkDocs Configuration for RobotLab Documentation
|
|
2
|
-
site_name: RobotLab
|
|
3
|
-
site_description: Multi-robot LLM workflow orchestration for Ruby
|
|
4
|
-
site_author: Dewayne VanHoozer
|
|
5
|
-
site_url: https://madbomber.github.io/robot_lab
|
|
6
|
-
copyright: Copyright © 2025 Dewayne VanHoozer
|
|
7
|
-
|
|
8
|
-
# Repository information
|
|
9
|
-
repo_name: madbomber/robot_lab
|
|
10
|
-
repo_url: https://github.com/madbomber/robot_lab
|
|
11
|
-
edit_uri: edit/main/docs/
|
|
12
|
-
|
|
13
|
-
# Configuration
|
|
14
|
-
theme:
|
|
15
|
-
name: material
|
|
16
|
-
|
|
17
|
-
# Color scheme
|
|
18
|
-
palette:
|
|
19
|
-
- scheme: default
|
|
20
|
-
primary: deep purple
|
|
21
|
-
accent: amber
|
|
22
|
-
toggle:
|
|
23
|
-
icon: material/brightness-7
|
|
24
|
-
name: Switch to dark mode
|
|
25
|
-
|
|
26
|
-
- scheme: slate
|
|
27
|
-
primary: deep purple
|
|
28
|
-
accent: amber
|
|
29
|
-
toggle:
|
|
30
|
-
icon: material/brightness-4
|
|
31
|
-
name: Switch to light mode
|
|
32
|
-
|
|
33
|
-
# Typography
|
|
34
|
-
font:
|
|
35
|
-
text: Roboto
|
|
36
|
-
code: Roboto Mono
|
|
37
|
-
|
|
38
|
-
# Logo and icon
|
|
39
|
-
icon:
|
|
40
|
-
repo: fontawesome/brands/github
|
|
41
|
-
logo: material/robot
|
|
42
|
-
|
|
43
|
-
# Theme features
|
|
44
|
-
features:
|
|
45
|
-
- navigation.instant
|
|
46
|
-
- navigation.tracking
|
|
47
|
-
- navigation.tabs
|
|
48
|
-
- navigation.tabs.sticky
|
|
49
|
-
- navigation.path
|
|
50
|
-
- navigation.indexes
|
|
51
|
-
- navigation.top
|
|
52
|
-
- navigation.footer
|
|
53
|
-
- toc.follow
|
|
54
|
-
- search.suggest
|
|
55
|
-
- search.highlight
|
|
56
|
-
- search.share
|
|
57
|
-
- header.autohide
|
|
58
|
-
- content.code.copy
|
|
59
|
-
- content.code.annotate
|
|
60
|
-
- content.tabs.link
|
|
61
|
-
- content.tooltips
|
|
62
|
-
- content.action.edit
|
|
63
|
-
- content.action.view
|
|
64
|
-
|
|
65
|
-
# Plugins
|
|
66
|
-
plugins:
|
|
67
|
-
- search:
|
|
68
|
-
separator: '[\s\-,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;|(?!\b)(?=[A-Z][a-z])'
|
|
69
|
-
- tags
|
|
70
|
-
|
|
71
|
-
# Extensions
|
|
72
|
-
markdown_extensions:
|
|
73
|
-
- abbr
|
|
74
|
-
- admonition
|
|
75
|
-
- attr_list
|
|
76
|
-
- def_list
|
|
77
|
-
- footnotes
|
|
78
|
-
- md_in_html
|
|
79
|
-
- tables
|
|
80
|
-
- toc:
|
|
81
|
-
permalink: true
|
|
82
|
-
title: On this page
|
|
83
|
-
|
|
84
|
-
- pymdownx.arithmatex:
|
|
85
|
-
generic: true
|
|
86
|
-
- pymdownx.betterem:
|
|
87
|
-
smart_enable: all
|
|
88
|
-
- pymdownx.caret
|
|
89
|
-
- pymdownx.critic
|
|
90
|
-
- pymdownx.details
|
|
91
|
-
- pymdownx.emoji:
|
|
92
|
-
emoji_generator: !!python/name:material.extensions.emoji.to_svg
|
|
93
|
-
emoji_index: !!python/name:material.extensions.emoji.twemoji
|
|
94
|
-
- pymdownx.highlight:
|
|
95
|
-
anchor_linenums: true
|
|
96
|
-
line_spans: __span
|
|
97
|
-
pygments_lang_class: true
|
|
98
|
-
- pymdownx.inlinehilite
|
|
99
|
-
- pymdownx.keys
|
|
100
|
-
- pymdownx.magiclink:
|
|
101
|
-
repo_url_shorthand: true
|
|
102
|
-
user: madbomber
|
|
103
|
-
repo: robot_lab
|
|
104
|
-
normalize_issue_symbols: true
|
|
105
|
-
- pymdownx.mark
|
|
106
|
-
- pymdownx.smartsymbols
|
|
107
|
-
- pymdownx.snippets:
|
|
108
|
-
check_paths: true
|
|
109
|
-
- pymdownx.superfences:
|
|
110
|
-
custom_fences:
|
|
111
|
-
- name: mermaid
|
|
112
|
-
class: mermaid
|
|
113
|
-
format: !!python/name:pymdownx.superfences.fence_code_format
|
|
114
|
-
- pymdownx.tabbed:
|
|
115
|
-
alternate_style: true
|
|
116
|
-
- pymdownx.tasklist:
|
|
117
|
-
custom_checkbox: true
|
|
118
|
-
- pymdownx.tilde
|
|
119
|
-
|
|
120
|
-
# Extra CSS
|
|
121
|
-
extra_css:
|
|
122
|
-
- assets/css/custom.css
|
|
123
|
-
|
|
124
|
-
# Social media and extra configuration
|
|
125
|
-
extra:
|
|
126
|
-
social:
|
|
127
|
-
- icon: fontawesome/brands/github
|
|
128
|
-
link: https://github.com/madbomber/robot_lab
|
|
129
|
-
name: RobotLab on GitHub
|
|
130
|
-
- icon: fontawesome/solid/gem
|
|
131
|
-
link: https://rubygems.org/gems/robot_lab
|
|
132
|
-
name: RobotLab on RubyGems
|
|
133
|
-
|
|
134
|
-
analytics:
|
|
135
|
-
feedback:
|
|
136
|
-
title: Was this page helpful?
|
|
137
|
-
ratings:
|
|
138
|
-
- icon: material/emoticon-happy-outline
|
|
139
|
-
name: This page was helpful
|
|
140
|
-
data: 1
|
|
141
|
-
note: Thanks for your feedback!
|
|
142
|
-
- icon: material/emoticon-sad-outline
|
|
143
|
-
name: This page could be improved
|
|
144
|
-
data: 0
|
|
145
|
-
note: Thanks for your feedback! Help us improve by creating an issue.
|
|
146
|
-
|
|
147
|
-
# Navigation
|
|
148
|
-
nav:
|
|
149
|
-
- Home:
|
|
150
|
-
- Overview: index.md
|
|
151
|
-
- Concepts: concepts.md
|
|
152
|
-
- Getting Started:
|
|
153
|
-
- getting-started/index.md
|
|
154
|
-
- Installation: getting-started/installation.md
|
|
155
|
-
- Quick Start: getting-started/quick-start.md
|
|
156
|
-
- Configuration: getting-started/configuration.md
|
|
157
|
-
- Architecture:
|
|
158
|
-
- architecture/index.md
|
|
159
|
-
- Core Concepts: architecture/core-concepts.md
|
|
160
|
-
- Robot Execution: architecture/robot-execution.md
|
|
161
|
-
- Network Orchestration: architecture/network-orchestration.md
|
|
162
|
-
- State Management: architecture/state-management.md
|
|
163
|
-
- Message Flow: architecture/message-flow.md
|
|
164
|
-
- Guides:
|
|
165
|
-
- guides/index.md
|
|
166
|
-
- Building Robots: guides/building-robots.md
|
|
167
|
-
- Creating Networks: guides/creating-networks.md
|
|
168
|
-
- Using Tools: guides/using-tools.md
|
|
169
|
-
- MCP Integration: guides/mcp-integration.md
|
|
170
|
-
- Streaming Responses: guides/streaming.md
|
|
171
|
-
- Memory System: guides/memory.md
|
|
172
|
-
- Observability & Safety: guides/observability.md
|
|
173
|
-
- Hook System: guides/hooks.md
|
|
174
|
-
- Knowledge Search: guides/knowledge.md
|
|
175
|
-
- API Reference:
|
|
176
|
-
- api/index.md
|
|
177
|
-
- Core Classes:
|
|
178
|
-
- api/core/index.md
|
|
179
|
-
- Robot: api/core/robot.md
|
|
180
|
-
- RobotResult: api/core/result.md
|
|
181
|
-
- Network: api/core/network.md
|
|
182
|
-
- Tool: api/core/tool.md
|
|
183
|
-
- Memory: api/core/memory.md
|
|
184
|
-
- StateProxy: api/core/state.md
|
|
185
|
-
- Hooks: api/hooks.md
|
|
186
|
-
- Skills: api/skills.md
|
|
187
|
-
- Support Classes: api/support.md
|
|
188
|
-
- Errors: api/errors.md
|
|
189
|
-
- Messages:
|
|
190
|
-
- api/messages/index.md
|
|
191
|
-
- UserMessage: api/messages/user-message.md
|
|
192
|
-
- TextMessage: api/messages/text-message.md
|
|
193
|
-
- ToolCallMessage: api/messages/tool-call-message.md
|
|
194
|
-
- ToolResultMessage: api/messages/tool-result-message.md
|
|
195
|
-
- MCP:
|
|
196
|
-
- api/mcp/index.md
|
|
197
|
-
- Client: api/mcp/client.md
|
|
198
|
-
- Server: api/mcp/server.md
|
|
199
|
-
- Transports: api/mcp/transports.md
|
|
200
|
-
- Streaming:
|
|
201
|
-
- api/streaming/index.md
|
|
202
|
-
- Context: api/streaming/context.md
|
|
203
|
-
- Events: api/streaming/events.md
|
|
204
|
-
- Examples:
|
|
205
|
-
- examples/index.md
|
|
206
|
-
- Basic Chat: examples/basic-chat.md
|
|
207
|
-
- Multi-Robot Network: examples/multi-robot-network.md
|
|
208
|
-
- Tool Usage: examples/tool-usage.md
|
|
209
|
-
- MCP Server: examples/mcp-server.md
|
data/robot_concurrency.md
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# Ruby Concurrency Review — RobotLab
|
|
2
|
-
|
|
3
|
-
Source: https://paolino.me/ruby-concurrency-what-actually-happens/
|
|
4
|
-
|
|
5
|
-
## What Applies to RobotLab
|
|
6
|
-
|
|
7
|
-
### 1. You're Already in the Right Model — Lean Into It
|
|
8
|
-
|
|
9
|
-
RobotLab uses `async (~> 2.0)`, which is exactly the fiber-based scheduler the article describes. LLM calls are pure I/O-bound work (HTTP streaming), so fibers are the correct and lowest-cost primitive. The article validates this architecture choice. No changes needed here — but be deliberate about not mixing blocking thread-style calls into the `Async` reactor unnecessarily.
|
|
10
|
-
|
|
11
|
-
### 2. The Ractor Caution Is Real (Recent Commit: `feat(ractor-parallelism)`)
|
|
12
|
-
|
|
13
|
-
The article is clear: Ractors in Ruby 4.0 are still experimental and practically incompatible with gems that use global state. RobotLab's dependencies (`ruby_llm`, `prompt_manager`, `zeitwerk`) almost certainly use global state. Worth auditing whether the new Ractor code hits `Ractor::IsolationError` under realistic load. The article's recommendation: **use Processes** for CPU parallelism if Rails-style gems are involved.
|
|
14
|
-
|
|
15
|
-
### 3. ~~The `Waiter` Class May Be Misfit in Fiber Context~~ — RESOLVED
|
|
16
|
-
|
|
17
|
-
`lib/robot_lab/waiter.rb` was updated to use `IO.pipe` + `IO.select` instead of `ConditionVariable`. `IO.select` is hooked by the Ruby 3.1+ fiber scheduler protocol, so it correctly yields to the Async scheduler when called from within an Async task, and correctly blocks the calling thread when used outside one. `Async::Condition` was considered but rejected: it only works inside an Async block, whereas `Memory` is intentionally usable from both plain threads and Async fibers.
|
|
18
|
-
|
|
19
|
-
### 4. Parallel Network Execution — Check the Primitive
|
|
20
|
-
|
|
21
|
-
`call_parallel()` in `Network` (built on `SimpleFlow::Pipeline`) — if it spawns OS threads rather than async tasks, you get heavier overhead than necessary for I/O-bound robot calls. The article's guidance: for I/O work, fiber-based `Async::Barrier` with concurrent tasks is 10-20x cheaper than threads.
|
|
22
|
-
|
|
23
|
-
### 5. Resource Semaphores for Parallel Networks
|
|
24
|
-
|
|
25
|
-
When a Network runs multiple robots concurrently, each making LLM API calls, you can exhaust API rate limits or connection pools with no back-pressure. The article's semaphore pattern applies directly — cap in-flight robot tasks to match your API tier's concurrency limit. This is worth adding to `RunConfig` as a `max_concurrent_robots` infra field.
|
|
26
|
-
|
|
27
|
-
### 6. Colorless Concurrency — This Is Already Your Story
|
|
28
|
-
|
|
29
|
-
The article's main point about Ruby fibers being "colorless" (no `async/await` propagation required) is directly reflected in how `robot.run("...")` stays synchronous-looking whether run standalone or in a parallel network. This is an underappreciated design strength worth highlighting in docs/README.
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## Summary
|
|
34
|
-
|
|
35
|
-
The async/fiber architecture is correct for LLM I/O work. The two actionable concerns are:
|
|
36
|
-
|
|
37
|
-
1. **Audit the Ractor code** for isolation errors with gem dependencies (`ruby_llm`, `prompt_manager`, `zeitwerk`).
|
|
38
|
-
2. **Check `Waiter`** (condition variable in `lib/robot_lab/waiter.rb`) — if called inside an `Async` reactor block, replace with `Async::Condition` to avoid stalling the reactor.
|