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.
Files changed (274) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +60 -0
  3. data/README.md +29 -19
  4. data/lib/robot_lab/errors.rb +4 -0
  5. data/lib/robot_lab/hook_context.rb +9 -2
  6. data/lib/robot_lab/mcp/registry.rb +99 -0
  7. data/lib/robot_lab/mcp/server.rb +47 -83
  8. data/lib/robot_lab/mcp/server_discovery.rb +22 -9
  9. data/lib/robot_lab/mcp.rb +259 -0
  10. data/lib/robot_lab/robot/mcp_management.rb +159 -105
  11. data/lib/robot_lab/robot/tool_call_gating.rb +144 -0
  12. data/lib/robot_lab/robot.rb +72 -99
  13. data/lib/robot_lab/run_config.rb +3 -2
  14. data/lib/robot_lab/tool.rb +8 -23
  15. data/lib/robot_lab/version.rb +1 -1
  16. data/lib/robot_lab.rb +1 -4
  17. metadata +16 -292
  18. data/.architecture/AGENTS.md +0 -32
  19. data/.architecture/config.yml +0 -8
  20. data/.architecture/members.yml +0 -60
  21. data/.architecture/reviews/feature-free-will.md +0 -490
  22. data/.architecture/reviews/overall-codebase.md +0 -427
  23. data/.claude/settings.local.json +0 -57
  24. data/.codex/config.toml +0 -2
  25. data/.envrc +0 -6
  26. data/.github/workflows/deploy-github-pages.yml +0 -52
  27. data/.irbrc +0 -6
  28. data/.loki +0 -65
  29. data/.quality/reek_baseline.txt +0 -43
  30. data/.rubocop.yml +0 -10
  31. data/Archspec.rb +0 -44
  32. data/CLAUDE.md +0 -141
  33. data/COMMITS.md +0 -196
  34. data/Rakefile +0 -193
  35. data/_typos.toml +0 -21
  36. data/agent2agent_review.md +0 -192
  37. data/agentf_improvements.md +0 -253
  38. data/agents.md +0 -14
  39. data/docs/api/core/index.md +0 -147
  40. data/docs/api/core/memory.md +0 -578
  41. data/docs/api/core/network.md +0 -553
  42. data/docs/api/core/result.md +0 -211
  43. data/docs/api/core/robot.md +0 -1667
  44. data/docs/api/core/state.md +0 -145
  45. data/docs/api/core/tool.md +0 -520
  46. data/docs/api/errors.md +0 -323
  47. data/docs/api/hooks.md +0 -469
  48. data/docs/api/index.md +0 -137
  49. data/docs/api/mcp/client.md +0 -387
  50. data/docs/api/mcp/index.md +0 -267
  51. data/docs/api/mcp/server.md +0 -252
  52. data/docs/api/mcp/transports.md +0 -339
  53. data/docs/api/messages/index.md +0 -126
  54. data/docs/api/messages/text-message.md +0 -170
  55. data/docs/api/messages/tool-call-message.md +0 -206
  56. data/docs/api/messages/tool-result-message.md +0 -242
  57. data/docs/api/messages/user-message.md +0 -232
  58. data/docs/api/skills.md +0 -423
  59. data/docs/api/streaming/context.md +0 -280
  60. data/docs/api/streaming/events.md +0 -185
  61. data/docs/api/streaming/index.md +0 -156
  62. data/docs/api/support.md +0 -846
  63. data/docs/architecture/core-concepts.md +0 -575
  64. data/docs/architecture/index.md +0 -252
  65. data/docs/architecture/message-flow.md +0 -366
  66. data/docs/architecture/network-orchestration.md +0 -478
  67. data/docs/architecture/robot-execution.md +0 -396
  68. data/docs/architecture/state-management.md +0 -410
  69. data/docs/assets/css/custom.css +0 -56
  70. data/docs/assets/images/robot_lab.jpg +0 -0
  71. data/docs/concepts.md +0 -641
  72. data/docs/examples/basic-chat.md +0 -258
  73. data/docs/examples/index.md +0 -425
  74. data/docs/examples/mcp-server.md +0 -403
  75. data/docs/examples/multi-robot-network.md +0 -476
  76. data/docs/examples/tool-usage.md +0 -468
  77. data/docs/getting-started/configuration.md +0 -645
  78. data/docs/getting-started/index.md +0 -56
  79. data/docs/getting-started/installation.md +0 -197
  80. data/docs/getting-started/quick-start.md +0 -293
  81. data/docs/guides/building-robots.md +0 -1187
  82. data/docs/guides/creating-networks.md +0 -736
  83. data/docs/guides/hooks.md +0 -1206
  84. data/docs/guides/index.md +0 -78
  85. data/docs/guides/knowledge.md +0 -219
  86. data/docs/guides/mcp-integration.md +0 -740
  87. data/docs/guides/memory.md +0 -458
  88. data/docs/guides/observability.md +0 -733
  89. data/docs/guides/streaming.md +0 -323
  90. data/docs/guides/using-tools.md +0 -780
  91. data/docs/index.md +0 -280
  92. data/docs/superpowers/plans/2026-05-06-agentskills.md +0 -1303
  93. data/docs/superpowers/specs/2026-05-06-agentskills-design.md +0 -247
  94. data/examples/.envrc +0 -3
  95. data/examples/01_simple_robot.rb +0 -31
  96. data/examples/02_tools.rb +0 -109
  97. data/examples/03_network.rb +0 -118
  98. data/examples/04_mcp.rb +0 -217
  99. data/examples/05_streaming.rb +0 -137
  100. data/examples/06_prompt_templates.rb +0 -325
  101. data/examples/07_network_memory.rb +0 -313
  102. data/examples/08_llm_config.rb +0 -162
  103. data/examples/09_chaining.rb +0 -251
  104. data/examples/10_memory.rb +0 -315
  105. data/examples/11_network_introspection.rb +0 -239
  106. data/examples/12_message_bus.rb +0 -67
  107. data/examples/13_spawn.rb +0 -85
  108. data/examples/14_rusty_circuit/.envrc +0 -1
  109. data/examples/14_rusty_circuit/.gitignore +0 -1
  110. data/examples/14_rusty_circuit/README.md +0 -1
  111. data/examples/14_rusty_circuit/comic.rb +0 -149
  112. data/examples/14_rusty_circuit/display.rb +0 -217
  113. data/examples/14_rusty_circuit/heckler.rb +0 -65
  114. data/examples/14_rusty_circuit/open_mic.rb +0 -132
  115. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +0 -20
  116. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +0 -23
  117. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +0 -20
  118. data/examples/14_rusty_circuit/scout.rb +0 -165
  119. data/examples/14_rusty_circuit/show.log +0 -234
  120. data/examples/15_memory_network_and_bus/.envrc +0 -1
  121. data/examples/15_memory_network_and_bus/README.md +0 -66
  122. data/examples/15_memory_network_and_bus/editor_in_chief.rb +0 -24
  123. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +0 -213
  124. data/examples/15_memory_network_and_bus/linux_writer.rb +0 -80
  125. data/examples/15_memory_network_and_bus/os_editor.rb +0 -48
  126. data/examples/15_memory_network_and_bus/os_writer.rb +0 -49
  127. data/examples/15_memory_network_and_bus/output/combined_article.md +0 -11
  128. data/examples/15_memory_network_and_bus/output/final_article.md +0 -8
  129. data/examples/15_memory_network_and_bus/output/linux_draft.md +0 -7
  130. data/examples/15_memory_network_and_bus/output/mac_draft.md +0 -7
  131. data/examples/15_memory_network_and_bus/output/memory.json +0 -13
  132. data/examples/15_memory_network_and_bus/output/revision_1.md +0 -29
  133. data/examples/15_memory_network_and_bus/output/revision_2.md +0 -50
  134. data/examples/15_memory_network_and_bus/output/revision_3.md +0 -8
  135. data/examples/15_memory_network_and_bus/output/windows_draft.md +0 -7
  136. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +0 -13
  137. data/examples/15_memory_network_and_bus/prompts/os_chief.md +0 -13
  138. data/examples/15_memory_network_and_bus/prompts/os_editor.md +0 -13
  139. data/examples/16_writers_room/.envrc +0 -1
  140. data/examples/16_writers_room/display.rb +0 -158
  141. data/examples/16_writers_room/output/.gitignore +0 -4
  142. data/examples/16_writers_room/output/README.md +0 -69
  143. data/examples/16_writers_room/output/opus_001.md +0 -263
  144. data/examples/16_writers_room/output/opus_001_notes.log +0 -470
  145. data/examples/16_writers_room/output/opus_002.md +0 -245
  146. data/examples/16_writers_room/output/opus_002_notes.log +0 -546
  147. data/examples/16_writers_room/output/opus_002_screenplay.md +0 -7989
  148. data/examples/16_writers_room/output/opus_002_screenplay_notes.md +0 -993
  149. data/examples/16_writers_room/prompts/screenplay_writer.md +0 -66
  150. data/examples/16_writers_room/prompts/writer.md +0 -37
  151. data/examples/16_writers_room/room.rb +0 -186
  152. data/examples/16_writers_room/tools.rb +0 -173
  153. data/examples/16_writers_room/writer.rb +0 -121
  154. data/examples/16_writers_room/writers_room.rb +0 -256
  155. data/examples/17_skills.rb +0 -234
  156. data/examples/18_rails/.envrc +0 -3
  157. data/examples/18_rails/.gitignore +0 -5
  158. data/examples/18_rails/Gemfile +0 -11
  159. data/examples/18_rails/README.md +0 -67
  160. data/examples/18_rails/Rakefile +0 -4
  161. data/examples/18_rails/app/controllers/application_controller.rb +0 -4
  162. data/examples/18_rails/app/controllers/chat_controller.rb +0 -50
  163. data/examples/18_rails/app/jobs/application_job.rb +0 -4
  164. data/examples/18_rails/app/jobs/robot_run_job.rb +0 -25
  165. data/examples/18_rails/app/models/application_record.rb +0 -5
  166. data/examples/18_rails/app/models/robot_lab_result.rb +0 -36
  167. data/examples/18_rails/app/models/robot_lab_thread.rb +0 -23
  168. data/examples/18_rails/app/robots/chat_robot.rb +0 -25
  169. data/examples/18_rails/app/tools/time_tool.rb +0 -9
  170. data/examples/18_rails/app/views/chat/_user_message.html.erb +0 -1
  171. data/examples/18_rails/app/views/chat/index.html.erb +0 -67
  172. data/examples/18_rails/app/views/layouts/application.html.erb +0 -49
  173. data/examples/18_rails/bin/dev +0 -7
  174. data/examples/18_rails/bin/rails +0 -6
  175. data/examples/18_rails/bin/setup +0 -15
  176. data/examples/18_rails/config/application.rb +0 -33
  177. data/examples/18_rails/config/cable.yml +0 -2
  178. data/examples/18_rails/config/database.yml +0 -5
  179. data/examples/18_rails/config/environment.rb +0 -4
  180. data/examples/18_rails/config/initializers/robot_lab.rb +0 -11
  181. data/examples/18_rails/config/routes.rb +0 -6
  182. data/examples/18_rails/config.ru +0 -4
  183. data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +0 -32
  184. data/examples/19_token_tracking.rb +0 -138
  185. data/examples/20_circuit_breaker.rb +0 -147
  186. data/examples/21_learning_loop.rb +0 -181
  187. data/examples/22_context_compression.rb +0 -172
  188. data/examples/23_convergence.rb +0 -133
  189. data/examples/24_structured_delegation.rb +0 -151
  190. data/examples/25_history_search/conversation.jsonl +0 -30
  191. data/examples/25_history_search.rb +0 -133
  192. data/examples/26_document_store/api_versioning_adr.md +0 -52
  193. data/examples/26_document_store/incident_postmortem.md +0 -46
  194. data/examples/26_document_store/postgres_runbook.md +0 -49
  195. data/examples/26_document_store/redis_caching_guide.md +0 -48
  196. data/examples/26_document_store/sidekiq_guide.md +0 -51
  197. data/examples/26_document_store.rb +0 -140
  198. data/examples/27_incident_response/README.md +0 -65
  199. data/examples/27_incident_response/incident_response.rb +0 -261
  200. data/examples/28_mcp_discovery.rb +0 -113
  201. data/examples/29_ractor_tools.rb +0 -240
  202. data/examples/30_ractor_network.rb +0 -252
  203. data/examples/31_launch_assessment.rb +0 -246
  204. data/examples/32_newsletter_reader.rb +0 -193
  205. data/examples/33_stock_generator.rb +0 -80
  206. data/examples/33_stock_predictor.rb +0 -327
  207. data/examples/34_agentskills.rb +0 -76
  208. data/examples/35_hooks.rb +0 -266
  209. data/examples/README.md +0 -550
  210. data/examples/common.rb +0 -180
  211. data/examples/prompts/assistant.md +0 -5
  212. data/examples/prompts/audit_trail.md +0 -8
  213. data/examples/prompts/billing.md +0 -10
  214. data/examples/prompts/classifier.md +0 -7
  215. data/examples/prompts/comedian.md +0 -6
  216. data/examples/prompts/comedy_critic.md +0 -10
  217. data/examples/prompts/configurable.md +0 -9
  218. data/examples/prompts/dispatcher.md +0 -12
  219. data/examples/prompts/entity_extractor.md +0 -14
  220. data/examples/prompts/escalation.md +0 -42
  221. data/examples/prompts/frontmatter_mcp_test.md +0 -9
  222. data/examples/prompts/frontmatter_named_test.md +0 -5
  223. data/examples/prompts/frontmatter_tools_test.md +0 -6
  224. data/examples/prompts/general.md +0 -7
  225. data/examples/prompts/github_assistant.md +0 -14
  226. data/examples/prompts/helper.md +0 -4
  227. data/examples/prompts/incident_responder.md +0 -18
  228. data/examples/prompts/keyword_extractor.md +0 -11
  229. data/examples/prompts/llm_config_demo.md +0 -20
  230. data/examples/prompts/order_support.md +0 -35
  231. data/examples/prompts/os_advocate.md +0 -13
  232. data/examples/prompts/os_chief.md +0 -13
  233. data/examples/prompts/os_editor.md +0 -13
  234. data/examples/prompts/parameterized_main_test.md +0 -6
  235. data/examples/prompts/pii_redactor.md +0 -7
  236. data/examples/prompts/product_support.md +0 -37
  237. data/examples/prompts/runbook_protocol.md +0 -12
  238. data/examples/prompts/sentiment_analyzer.md +0 -12
  239. data/examples/prompts/skill_a_test.md +0 -4
  240. data/examples/prompts/skill_b_test.md +0 -4
  241. data/examples/prompts/skill_config_test.md +0 -5
  242. data/examples/prompts/skill_cycle_a_test.md +0 -6
  243. data/examples/prompts/skill_cycle_b_test.md +0 -6
  244. data/examples/prompts/skill_description_test.md +0 -4
  245. data/examples/prompts/skill_leaf_test.md +0 -4
  246. data/examples/prompts/skill_nested_test.md +0 -6
  247. data/examples/prompts/skill_refs_main_test.md +0 -6
  248. data/examples/prompts/skill_self_ref_test.md +0 -6
  249. data/examples/prompts/skill_with_mcp_test.md +0 -9
  250. data/examples/prompts/skill_with_params_test.md +0 -6
  251. data/examples/prompts/skill_with_robot_name_test.md +0 -5
  252. data/examples/prompts/skill_with_tools_test.md +0 -6
  253. data/examples/prompts/sre_compliance.md +0 -10
  254. data/examples/prompts/structured_output.md +0 -7
  255. data/examples/prompts/synthesizer.md +0 -17
  256. data/examples/prompts/technical.md +0 -10
  257. data/examples/prompts/template_with_skills_test.md +0 -6
  258. data/examples/prompts/triage.md +0 -22
  259. data/examples/ruboruby.md +0 -423
  260. data/examples/run_all.rb +0 -60
  261. data/examples/xyzzy.rb +0 -97
  262. data/lib/robot_lab/mcp/client.rb +0 -223
  263. data/lib/robot_lab/mcp/connection_poller.rb +0 -195
  264. data/lib/robot_lab/mcp/transports/base.rb +0 -64
  265. data/lib/robot_lab/mcp/transports/sse.rb +0 -120
  266. data/lib/robot_lab/mcp/transports/stdio.rb +0 -170
  267. data/lib/robot_lab/mcp/transports/streamable_http.rb +0 -141
  268. data/lib/robot_lab/mcp/transports/websocket.rb +0 -111
  269. data/logfile +0 -8
  270. data/mkdocs.yml +0 -209
  271. data/robot_concurrency.md +0 -38
  272. data/simple_acp_review.md +0 -298
  273. data/temp.md +0 -6
  274. 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 &copy; 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.