robot_lab 0.0.1 → 0.0.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.
Files changed (187) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/deploy-github-pages.yml +9 -9
  3. data/.irbrc +6 -0
  4. data/CHANGELOG.md +140 -0
  5. data/README.md +263 -48
  6. data/Rakefile +71 -1
  7. data/docs/api/core/index.md +53 -46
  8. data/docs/api/core/memory.md +200 -154
  9. data/docs/api/core/network.md +13 -3
  10. data/docs/api/core/robot.md +490 -130
  11. data/docs/api/core/state.md +55 -73
  12. data/docs/api/core/tool.md +205 -209
  13. data/docs/api/index.md +7 -28
  14. data/docs/api/mcp/client.md +119 -48
  15. data/docs/api/mcp/index.md +75 -60
  16. data/docs/api/mcp/server.md +120 -136
  17. data/docs/api/mcp/transports.md +172 -184
  18. data/docs/api/messages/index.md +35 -20
  19. data/docs/api/messages/text-message.md +67 -21
  20. data/docs/api/messages/tool-call-message.md +80 -41
  21. data/docs/api/messages/tool-result-message.md +119 -50
  22. data/docs/api/messages/user-message.md +48 -24
  23. data/docs/api/streaming/context.md +157 -74
  24. data/docs/api/streaming/events.md +114 -166
  25. data/docs/api/streaming/index.md +74 -72
  26. data/docs/architecture/core-concepts.md +360 -116
  27. data/docs/architecture/index.md +97 -59
  28. data/docs/architecture/message-flow.md +138 -129
  29. data/docs/architecture/network-orchestration.md +197 -50
  30. data/docs/architecture/robot-execution.md +199 -146
  31. data/docs/architecture/state-management.md +255 -187
  32. data/docs/concepts.md +311 -49
  33. data/docs/examples/basic-chat.md +89 -77
  34. data/docs/examples/index.md +222 -47
  35. data/docs/examples/mcp-server.md +207 -203
  36. data/docs/examples/multi-robot-network.md +129 -35
  37. data/docs/examples/rails-application.md +159 -160
  38. data/docs/examples/tool-usage.md +295 -204
  39. data/docs/getting-started/configuration.md +347 -154
  40. data/docs/getting-started/index.md +1 -1
  41. data/docs/getting-started/installation.md +22 -13
  42. data/docs/getting-started/quick-start.md +166 -121
  43. data/docs/guides/building-robots.md +418 -212
  44. data/docs/guides/creating-networks.md +143 -24
  45. data/docs/guides/index.md +0 -5
  46. data/docs/guides/mcp-integration.md +152 -113
  47. data/docs/guides/memory.md +220 -164
  48. data/docs/guides/rails-integration.md +244 -162
  49. data/docs/guides/streaming.md +137 -187
  50. data/docs/guides/using-tools.md +259 -212
  51. data/docs/index.md +46 -41
  52. data/examples/01_simple_robot.rb +6 -9
  53. data/examples/02_tools.rb +6 -9
  54. data/examples/03_network.rb +19 -17
  55. data/examples/04_mcp.rb +5 -8
  56. data/examples/05_streaming.rb +5 -8
  57. data/examples/06_prompt_templates.rb +42 -37
  58. data/examples/07_network_memory.rb +13 -14
  59. data/examples/08_llm_config.rb +169 -0
  60. data/examples/09_chaining.rb +262 -0
  61. data/examples/10_memory.rb +331 -0
  62. data/examples/11_network_introspection.rb +253 -0
  63. data/examples/12_message_bus.rb +74 -0
  64. data/examples/13_spawn.rb +90 -0
  65. data/examples/14_rusty_circuit/comic.rb +143 -0
  66. data/examples/14_rusty_circuit/display.rb +203 -0
  67. data/examples/14_rusty_circuit/heckler.rb +63 -0
  68. data/examples/14_rusty_circuit/open_mic.rb +123 -0
  69. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +20 -0
  70. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +23 -0
  71. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +20 -0
  72. data/examples/14_rusty_circuit/scout.rb +156 -0
  73. data/examples/14_rusty_circuit/scout_notes.md +89 -0
  74. data/examples/14_rusty_circuit/show.log +234 -0
  75. data/examples/15_memory_network_and_bus/editor_in_chief.rb +24 -0
  76. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +206 -0
  77. data/examples/15_memory_network_and_bus/linux_writer.rb +80 -0
  78. data/examples/15_memory_network_and_bus/os_editor.rb +46 -0
  79. data/examples/15_memory_network_and_bus/os_writer.rb +46 -0
  80. data/examples/15_memory_network_and_bus/output/combined_article.md +13 -0
  81. data/examples/15_memory_network_and_bus/output/final_article.md +15 -0
  82. data/examples/15_memory_network_and_bus/output/linux_draft.md +5 -0
  83. data/examples/15_memory_network_and_bus/output/mac_draft.md +7 -0
  84. data/examples/15_memory_network_and_bus/output/memory.json +13 -0
  85. data/examples/15_memory_network_and_bus/output/revision_1.md +19 -0
  86. data/examples/15_memory_network_and_bus/output/revision_2.md +15 -0
  87. data/examples/15_memory_network_and_bus/output/windows_draft.md +7 -0
  88. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +13 -0
  89. data/examples/15_memory_network_and_bus/prompts/os_chief.md +13 -0
  90. data/examples/15_memory_network_and_bus/prompts/os_editor.md +13 -0
  91. data/examples/16_writers_room/display.rb +158 -0
  92. data/examples/16_writers_room/output/.gitignore +2 -0
  93. data/examples/16_writers_room/output/opus_001.md +263 -0
  94. data/examples/16_writers_room/output/opus_001_notes.log +470 -0
  95. data/examples/16_writers_room/prompts/writer.md +37 -0
  96. data/examples/16_writers_room/room.rb +150 -0
  97. data/examples/16_writers_room/tools.rb +162 -0
  98. data/examples/16_writers_room/writer.rb +121 -0
  99. data/examples/16_writers_room/writers_room.rb +162 -0
  100. data/examples/README.md +197 -0
  101. data/examples/prompts/{assistant/system.txt.erb → assistant.md} +3 -0
  102. data/examples/prompts/{billing/system.txt.erb → billing.md} +3 -0
  103. data/examples/prompts/{classifier/system.txt.erb → classifier.md} +3 -0
  104. data/examples/prompts/comedian.md +6 -0
  105. data/examples/prompts/comedy_critic.md +10 -0
  106. data/examples/prompts/configurable.md +9 -0
  107. data/examples/prompts/dispatcher.md +12 -0
  108. data/examples/prompts/{entity_extractor/system.txt.erb → entity_extractor.md} +3 -0
  109. data/examples/prompts/{escalation/system.txt.erb → escalation.md} +7 -0
  110. data/examples/prompts/frontmatter_mcp_test.md +9 -0
  111. data/examples/prompts/frontmatter_named_test.md +5 -0
  112. data/examples/prompts/frontmatter_tools_test.md +6 -0
  113. data/examples/prompts/{general/system.txt.erb → general.md} +3 -0
  114. data/examples/prompts/{github_assistant/system.txt.erb → github_assistant.md} +8 -0
  115. data/examples/prompts/{helper/system.txt.erb → helper.md} +3 -0
  116. data/examples/prompts/{keyword_extractor/system.txt.erb → keyword_extractor.md} +3 -0
  117. data/examples/prompts/llm_config_demo.md +20 -0
  118. data/examples/prompts/{order_support/system.txt.erb → order_support.md} +8 -0
  119. data/examples/prompts/os_advocate.md +13 -0
  120. data/examples/prompts/os_chief.md +13 -0
  121. data/examples/prompts/os_editor.md +13 -0
  122. data/examples/prompts/{product_support/system.txt.erb → product_support.md} +7 -0
  123. data/examples/prompts/{sentiment_analyzer/system.txt.erb → sentiment_analyzer.md} +3 -0
  124. data/examples/prompts/{synthesizer/system.txt.erb → synthesizer.md} +3 -0
  125. data/examples/prompts/{technical/system.txt.erb → technical.md} +3 -0
  126. data/examples/prompts/{triage/system.txt.erb → triage.md} +6 -0
  127. data/lib/generators/robot_lab/templates/initializer.rb.tt +0 -13
  128. data/lib/robot_lab/ask_user.rb +75 -0
  129. data/lib/robot_lab/config/defaults.yml +121 -0
  130. data/lib/robot_lab/config.rb +183 -0
  131. data/lib/robot_lab/error.rb +6 -0
  132. data/lib/robot_lab/mcp/client.rb +1 -1
  133. data/lib/robot_lab/memory.rb +10 -34
  134. data/lib/robot_lab/network.rb +13 -20
  135. data/lib/robot_lab/robot/bus_messaging.rb +239 -0
  136. data/lib/robot_lab/robot/mcp_management.rb +88 -0
  137. data/lib/robot_lab/robot/template_rendering.rb +130 -0
  138. data/lib/robot_lab/robot.rb +240 -330
  139. data/lib/robot_lab/robot_message.rb +44 -0
  140. data/lib/robot_lab/robot_result.rb +1 -0
  141. data/lib/robot_lab/run_config.rb +184 -0
  142. data/lib/robot_lab/state_proxy.rb +2 -12
  143. data/lib/robot_lab/streaming/context.rb +1 -1
  144. data/lib/robot_lab/task.rb +8 -1
  145. data/lib/robot_lab/tool.rb +108 -172
  146. data/lib/robot_lab/tool_config.rb +1 -1
  147. data/lib/robot_lab/tool_manifest.rb +2 -18
  148. data/lib/robot_lab/utils.rb +39 -0
  149. data/lib/robot_lab/version.rb +1 -1
  150. data/lib/robot_lab.rb +89 -57
  151. data/mkdocs.yml +0 -11
  152. metadata +121 -135
  153. data/docs/api/adapters/anthropic.md +0 -121
  154. data/docs/api/adapters/gemini.md +0 -133
  155. data/docs/api/adapters/index.md +0 -104
  156. data/docs/api/adapters/openai.md +0 -134
  157. data/docs/api/history/active-record-adapter.md +0 -195
  158. data/docs/api/history/config.md +0 -191
  159. data/docs/api/history/index.md +0 -132
  160. data/docs/api/history/thread-manager.md +0 -144
  161. data/docs/guides/history.md +0 -359
  162. data/examples/prompts/assistant/user.txt.erb +0 -1
  163. data/examples/prompts/billing/user.txt.erb +0 -1
  164. data/examples/prompts/classifier/user.txt.erb +0 -1
  165. data/examples/prompts/entity_extractor/user.txt.erb +0 -3
  166. data/examples/prompts/escalation/user.txt.erb +0 -34
  167. data/examples/prompts/general/user.txt.erb +0 -1
  168. data/examples/prompts/github_assistant/user.txt.erb +0 -1
  169. data/examples/prompts/helper/user.txt.erb +0 -1
  170. data/examples/prompts/keyword_extractor/user.txt.erb +0 -3
  171. data/examples/prompts/order_support/user.txt.erb +0 -22
  172. data/examples/prompts/product_support/user.txt.erb +0 -32
  173. data/examples/prompts/sentiment_analyzer/user.txt.erb +0 -3
  174. data/examples/prompts/synthesizer/user.txt.erb +0 -15
  175. data/examples/prompts/technical/user.txt.erb +0 -1
  176. data/examples/prompts/triage/user.txt.erb +0 -17
  177. data/lib/robot_lab/adapters/anthropic.rb +0 -163
  178. data/lib/robot_lab/adapters/base.rb +0 -85
  179. data/lib/robot_lab/adapters/gemini.rb +0 -193
  180. data/lib/robot_lab/adapters/openai.rb +0 -159
  181. data/lib/robot_lab/adapters/registry.rb +0 -81
  182. data/lib/robot_lab/configuration.rb +0 -143
  183. data/lib/robot_lab/errors.rb +0 -70
  184. data/lib/robot_lab/history/active_record_adapter.rb +0 -146
  185. data/lib/robot_lab/history/config.rb +0 -115
  186. data/lib/robot_lab/history/thread_manager.rb +0 -93
  187. data/lib/robot_lab/robotic_model.rb +0 -324
@@ -38,6 +38,20 @@ network = RobotLab.create_network(name: "parallel", concurrency: :threads) do
38
38
  end
39
39
  ```
40
40
 
41
+ ### Shared Memory
42
+
43
+ Networks provide a shared memory accessible to all robots:
44
+
45
+ ```ruby
46
+ network = RobotLab.create_network(name: "pipeline") do
47
+ task :first, robot1, depends_on: :none
48
+ end
49
+
50
+ # Pre-populate shared memory
51
+ network.memory[:project] = "Q4 Report"
52
+ network.memory[:user_id] = 123
53
+ ```
54
+
41
55
  ## Adding Tasks
42
56
 
43
57
  ### Sequential Tasks
@@ -72,7 +86,7 @@ end
72
86
 
73
87
  ### Optional Tasks
74
88
 
75
- Optional tasks only run when explicitly activated:
89
+ Optional tasks only run when explicitly activated by a preceding robot:
76
90
 
77
91
  ```ruby
78
92
  network = RobotLab.create_network(name: "router") do
@@ -85,7 +99,7 @@ end
85
99
 
86
100
  ## Per-Task Configuration
87
101
 
88
- Tasks can have individual context and configuration that's deep-merged with the network's run parameters:
102
+ Tasks can have individual context and configuration that is deep-merged with the network's run parameters:
89
103
 
90
104
  ```ruby
91
105
  network = RobotLab.create_network(name: "support") do
@@ -105,9 +119,10 @@ end
105
119
  | Option | Description |
106
120
  |--------|-------------|
107
121
  | `context` | Hash merged with run params (task values override) |
108
- | `mcp` | MCP servers for this task |
109
- | `tools` | Tools available to this task |
122
+ | `mcp` | MCP servers for this task (`:none`, `:inherit`, or array) |
123
+ | `tools` | Tools available to this task (`:none`, `:inherit`, or array) |
110
124
  | `memory` | Task-specific memory |
125
+ | `config` | Per-task `RunConfig` (merged on top of network's config) |
111
126
  | `depends_on` | `:none`, `[:task1]`, or `:optional` |
112
127
 
113
128
  ## Conditional Routing
@@ -117,7 +132,9 @@ Use optional tasks with custom Robot subclasses for intelligent routing:
117
132
  ```ruby
118
133
  class ClassifierRobot < RobotLab::Robot
119
134
  def call(result)
120
- robot_result = run(**extract_run_context(result))
135
+ context = extract_run_context(result)
136
+ message = context.delete(:message)
137
+ robot_result = run(message, **context)
121
138
 
122
139
  new_result = result
123
140
  .with_context(@name.to_sym, robot_result)
@@ -194,6 +211,25 @@ result.halted? # Whether execution was halted early
194
211
  result.continued? # Whether execution continued normally
195
212
  ```
196
213
 
214
+ ## Broadcasting
215
+
216
+ Networks support a broadcast channel for network-wide announcements:
217
+
218
+ ```ruby
219
+ # Register a broadcast handler
220
+ network.on_broadcast do |message|
221
+ case message[:payload][:event]
222
+ when :pause
223
+ puts "Pausing: #{message[:payload][:reason]}"
224
+ when :phase_complete
225
+ puts "Phase complete: #{message[:payload][:phase]}"
226
+ end
227
+ end
228
+
229
+ # Send broadcasts during execution
230
+ network.broadcast(event: :phase_complete, phase: "analysis")
231
+ ```
232
+
197
233
  ## Patterns
198
234
 
199
235
  ### Classifier Pattern
@@ -203,7 +239,10 @@ Route to specialists based on classification:
203
239
  ```ruby
204
240
  class SupportClassifier < RobotLab::Robot
205
241
  def call(result)
206
- robot_result = run(**extract_run_context(result))
242
+ context = extract_run_context(result)
243
+ message = context.delete(:message)
244
+ robot_result = run(message, **context)
245
+
207
246
  new_result = result
208
247
  .with_context(@name.to_sym, robot_result)
209
248
  .continue(robot_result)
@@ -259,7 +298,9 @@ A robot can halt execution early:
259
298
  ```ruby
260
299
  class ValidatorRobot < RobotLab::Robot
261
300
  def call(result)
262
- robot_result = run(**extract_run_context(result))
301
+ context = extract_run_context(result)
302
+ message = context.delete(:message)
303
+ robot_result = run(message, **context)
263
304
 
264
305
  if robot_result.last_text_content.include?("INVALID")
265
306
  # Stop the pipeline
@@ -274,6 +315,30 @@ class ValidatorRobot < RobotLab::Robot
274
315
  end
275
316
  ```
276
317
 
318
+ ### Data Passing Between Tasks
319
+
320
+ Access previous task results via context:
321
+
322
+ ```ruby
323
+ class ResponderRobot < RobotLab::Robot
324
+ def call(result)
325
+ # Get classifier's output
326
+ classification = result.context[:classifier]&.last_text_content
327
+
328
+ context = extract_run_context(result)
329
+ message = context.delete(:message)
330
+
331
+ # Use classification in the message or context
332
+ robot_result = run(
333
+ "Classification: #{classification}\n\nUser message: #{message}",
334
+ **context
335
+ )
336
+
337
+ result.with_context(@name.to_sym, robot_result).continue(robot_result)
338
+ end
339
+ end
340
+ ```
341
+
277
342
  ## Visualization
278
343
 
279
344
  ### ASCII Visualization
@@ -290,6 +355,13 @@ puts network.to_mermaid
290
355
  # => Mermaid graph definition
291
356
  ```
292
357
 
358
+ ### DOT Format (Graphviz)
359
+
360
+ ```ruby
361
+ puts network.to_dot
362
+ # => Graphviz DOT format
363
+ ```
364
+
293
365
  ### Execution Plan
294
366
 
295
367
  ```ruby
@@ -305,9 +377,58 @@ network.robots # => Hash of name => Robot
305
377
  network.robot(:billing) # => Robot instance
306
378
  network["billing"] # => Robot instance (alias)
307
379
  network.available_robots # => Array of Robot instances
380
+ network.memory # => Memory instance (shared)
308
381
  network.to_h # => Hash representation
309
382
  ```
310
383
 
384
+ ## Configuration Inheritance
385
+
386
+ Networks accept a `config:` parameter that establishes default LLM settings for all member robots. This is useful when you want consistent behavior across a pipeline without configuring each robot individually.
387
+
388
+ ### Network-Wide Defaults
389
+
390
+ ```ruby
391
+ # All robots in this network use the same model and temperature
392
+ shared = RobotLab::RunConfig.new(model: "claude-sonnet-4", temperature: 0.5)
393
+
394
+ network = RobotLab.create_network(name: "pipeline", config: shared) do
395
+ task :analyzer, analyzer_robot, depends_on: :none
396
+ task :writer, writer_robot, depends_on: [:analyzer]
397
+ task :reviewer, reviewer_robot, depends_on: [:writer]
398
+ end
399
+ ```
400
+
401
+ ### Per-Task Overrides
402
+
403
+ Individual tasks can override the network's config with their own `config:`:
404
+
405
+ ```ruby
406
+ creative_config = RobotLab::RunConfig.new(temperature: 0.9)
407
+
408
+ network = RobotLab.create_network(name: "pipeline", config: shared) do
409
+ task :analyzer, analyzer_robot, depends_on: :none
410
+ task :writer, writer_robot,
411
+ config: creative_config, # writer gets higher temperature
412
+ depends_on: [:analyzer]
413
+ task :reviewer, reviewer_robot, depends_on: [:writer]
414
+ end
415
+ ```
416
+
417
+ ### Inheritance Chain
418
+
419
+ The full configuration hierarchy (most-specific wins):
420
+
421
+ ```
422
+ RobotLab.config (global)
423
+ -> Network config
424
+ -> Task config
425
+ -> Robot config (from constructor)
426
+ -> Template front matter
427
+ -> Constructor kwargs (model:, temperature:, etc.)
428
+ ```
429
+
430
+ Each layer only overrides values it explicitly sets. Unset values pass through from the parent.
431
+
311
432
  ## Best Practices
312
433
 
313
434
  ### 1. Keep Robots Focused
@@ -323,25 +444,14 @@ task :respond, responder, depends_on: [:classify]
323
444
  task :do_everything, mega_robot, depends_on: :none
324
445
  ```
325
446
 
326
- ### 2. Use Context for Data Passing
447
+ ### 2. Use Per-Task Context
327
448
 
328
- Access previous results via context:
449
+ Pass task-specific configuration through context:
329
450
 
330
451
  ```ruby
331
- class ResponderRobot < RobotLab::Robot
332
- def call(result)
333
- # Get classifier's output
334
- classification = result.context[:classifier]&.last_text_content
335
-
336
- # Use it in this robot's run
337
- robot_result = run(
338
- **extract_run_context(result),
339
- classification: classification
340
- )
341
-
342
- result.with_context(@name.to_sym, robot_result).continue(robot_result)
343
- end
344
- end
452
+ task :billing, billing_robot,
453
+ context: { department: "billing", max_refund: 500 },
454
+ depends_on: :optional
345
455
  ```
346
456
 
347
457
  ### 3. Handle Missing Results
@@ -359,8 +469,17 @@ def call(result)
359
469
  end
360
470
  ```
361
471
 
472
+ ### 4. Reset Memory Between Runs
473
+
474
+ If reusing a network, reset shared memory between runs:
475
+
476
+ ```ruby
477
+ network.reset_memory
478
+ result = network.run(message: "New request")
479
+ ```
480
+
362
481
  ## Next Steps
363
482
 
364
483
  - [Using Tools](using-tools.md) - Add capabilities to robots
365
- - [Memory Guide](memory.md) - Persistent memory across runs
484
+ - [Memory Guide](memory.md) - Shared memory patterns
366
485
  - [API Reference: Network](../api/core/network.md) - Complete API
data/docs/guides/index.md CHANGED
@@ -34,10 +34,6 @@ If you're new to RobotLab, start here:
34
34
 
35
35
  Real-time streaming of LLM responses
36
36
 
37
- - [:octicons-database-24: **Conversation History**](history.md)
38
-
39
- Persist and restore conversation threads
40
-
41
37
  - [:octicons-cpu-24: **Memory System**](memory.md)
42
38
 
43
39
  Share data between robots with the memory system
@@ -63,6 +59,5 @@ If you're new to RobotLab, start here:
63
59
  | [Using Tools](using-tools.md) | Add custom capabilities | 10 min |
64
60
  | [MCP Integration](mcp-integration.md) | External tool servers | 10 min |
65
61
  | [Streaming](streaming.md) | Real-time responses | 5 min |
66
- | [History](history.md) | Conversation persistence | 10 min |
67
62
  | [Memory](memory.md) | Shared data store | 5 min |
68
63
  | [Rails Integration](rails-integration.md) | Rails application setup | 15 min |
@@ -12,13 +12,15 @@ MCP is a protocol that allows LLM applications to connect to external servers th
12
12
 
13
13
  ## Configuring MCP Servers
14
14
 
15
- ### At Network Level
15
+ ### At Robot Level
16
16
 
17
- ```ruby
18
- network = RobotLab.create_network do
19
- name "dev_assistant"
17
+ Use the `mcp:` parameter on `RobotLab.build` to connect a robot to MCP servers:
20
18
 
21
- mcp [
19
+ ```ruby
20
+ robot = RobotLab.build(
21
+ name: "coder",
22
+ template: :developer,
23
+ mcp: [
22
24
  {
23
25
  name: "filesystem",
24
26
  transport: {
@@ -31,40 +33,72 @@ network = RobotLab.create_network do
31
33
  name: "github",
32
34
  transport: {
33
35
  type: "stdio",
34
- command: "mcp-server-github"
36
+ command: "mcp-server-github",
37
+ env: { "GITHUB_TOKEN" => ENV["GITHUB_TOKEN"] }
35
38
  }
36
39
  }
37
40
  ]
38
- end
41
+ )
39
42
  ```
40
43
 
41
- ### At Robot Level
44
+ ### In Template Front Matter
45
+
46
+ MCP servers can be declared directly in a template's YAML front matter, making the template fully self-contained:
47
+
48
+ ```markdown title="prompts/github_assistant.md"
49
+ ---
50
+ description: GitHub assistant with MCP tool access
51
+ mcp:
52
+ - name: github
53
+ transport: stdio
54
+ command: npx
55
+ args: ["-y", "@modelcontextprotocol/server-github"]
56
+ ---
57
+ You are a helpful GitHub assistant with access to GitHub tools via MCP.
58
+ ```
42
59
 
43
60
  ```ruby
44
- robot = RobotLab.build do
45
- name "coder"
61
+ # MCP config comes from the template — no mcp: parameter needed
62
+ robot = RobotLab.build(template: :github_assistant)
63
+ ```
46
64
 
47
- # Use network's MCP servers
48
- mcp :inherit
65
+ Constructor `mcp:` overrides frontmatter `mcp:` when provided.
49
66
 
50
- # Or specific servers
51
- mcp [
52
- { name: "filesystem", transport: { type: "stdio", command: "mcp-fs" } }
53
- ]
67
+ ### Hierarchical Configuration
54
68
 
55
- # Or disable MCP
56
- mcp :none
57
- end
58
- ```
69
+ The `mcp:` parameter supports three modes:
59
70
 
60
- ### Global Configuration
71
+ | Value | Behavior |
72
+ |-------|----------|
73
+ | `:none` | No MCP servers (default) |
74
+ | `:inherit` | Inherit from network or global config |
75
+ | `[...]` | Explicit array of server configurations |
61
76
 
62
77
  ```ruby
63
- RobotLab.configure do |config|
64
- config.mcp = [
65
- { name: "common_tools", transport: { type: "stdio", command: "common-mcp" } }
66
- ]
67
- end
78
+ # Inherit from network/config
79
+ robot = RobotLab.build(
80
+ name: "reader",
81
+ system_prompt: "You help read files.",
82
+ mcp: :inherit
83
+ )
84
+
85
+ # Disable MCP explicitly
86
+ robot = RobotLab.build(
87
+ name: "calculator",
88
+ system_prompt: "You do math.",
89
+ mcp: :none
90
+ )
91
+ ```
92
+
93
+ ### Resolution Order
94
+
95
+ MCP configuration resolves through a hierarchy: **runtime > robot build > network > global config**. Each level can override the previous:
96
+
97
+ ```
98
+ Global (RobotLab.config.mcp)
99
+ -> Network (task mcp: [...])
100
+ -> Robot (mcp: :inherit | :none | [...])
101
+ -> Runtime (robot.run("msg", mcp: [...]))
68
102
  ```
69
103
 
70
104
  ## Transport Types
@@ -134,68 +168,54 @@ Streamable HTTP transport with session support:
134
168
 
135
169
  ## Using MCP Tools
136
170
 
137
- Once configured, MCP tools are automatically available to robots:
171
+ Once configured, MCP tools are automatically discovered and made available to the robot. The robot connects to MCP servers on its first `run` call and discovers tools dynamically:
138
172
 
139
173
  ```ruby
140
- network = RobotLab.create_network do
141
- mcp [
174
+ robot = RobotLab.build(
175
+ name: "helper",
176
+ system_prompt: <<~PROMPT
177
+ You can help users with GitHub tasks.
178
+ Use available tools to search repositories, create issues, etc.
179
+ PROMPT,
180
+ mcp: [
142
181
  { name: "github", transport: { type: "stdio", command: "mcp-server-github" } }
143
182
  ]
183
+ )
144
184
 
145
- add_robot RobotLab.build {
146
- name "helper"
147
- template <<~PROMPT
148
- You can help users with GitHub tasks.
149
- Use available tools to search repositories, create issues, etc.
150
- PROMPT
151
- }
152
- end
153
-
154
- # The robot can now use GitHub MCP tools
155
- state = RobotLab.create_state(message: "Find repositories about machine learning")
156
- network.run(state: state)
185
+ # MCP tools are automatically available
186
+ result = robot.run("Find repositories about machine learning")
187
+ puts result.last_text_content
157
188
  ```
158
189
 
159
190
  ## Filtering MCP Tools
160
191
 
161
- Restrict which MCP tools are available:
192
+ Use the `tools:` parameter to restrict which tools (including MCP-discovered tools) are available to a robot:
162
193
 
163
194
  ```ruby
164
- robot = RobotLab.build do
165
- name "reader"
166
- mcp :inherit
167
-
168
- # Only allow specific MCP tools
169
- tools %w[read_file search_code list_directory]
170
- end
171
- ```
172
-
173
- ## MCP Server Configuration
174
-
175
- ### Server Object
176
-
177
- ```ruby
178
- server = RobotLab::MCP::Server.new(
179
- name: "my_server",
180
- transport: {
181
- type: "stdio",
182
- command: "my-mcp-server"
183
- }
195
+ robot = RobotLab.build(
196
+ name: "reader",
197
+ system_prompt: "You help read and search files.",
198
+ mcp: [
199
+ { name: "filesystem", transport: { type: "stdio", command: "mcp-server-fs" } }
200
+ ],
201
+ tools: %w[read_file search_files list_directory] # Only allow specific tools
184
202
  )
185
-
186
- server.name # => "my_server"
187
- server.transport_type # => "stdio"
188
- server.to_h # Hash representation
189
203
  ```
190
204
 
191
- ### Client Object
205
+ ## MCP in Networks
192
206
 
193
- ```ruby
194
- client = RobotLab::MCP::Client.new(server: server)
195
- client.connect
207
+ When running robots in a network, use per-task MCP configuration:
196
208
 
197
- client.connected? # => true
198
- client.to_h # Client info
209
+ ```ruby
210
+ network = RobotLab.create_network(name: "dev_pipeline") do
211
+ task :planner, planner_robot, depends_on: :none
212
+ task :coder, coder_robot,
213
+ mcp: [
214
+ { name: "filesystem", transport: { type: "stdio", command: "mcp-server-fs" } }
215
+ ],
216
+ depends_on: [:planner]
217
+ task :reviewer, reviewer_robot, depends_on: [:coder]
218
+ end
199
219
  ```
200
220
 
201
221
  ## Common MCP Servers
@@ -245,54 +265,70 @@ Tools: `search_repositories`, `create_issue`, `get_file_contents`, etc.
245
265
 
246
266
  Tools: `query`, `list_tables`, `describe_table`
247
267
 
268
+ ## MCP Server and Client Objects
269
+
270
+ For programmatic access, you can work with MCP objects directly:
271
+
272
+ ```ruby
273
+ # Server configuration
274
+ server = RobotLab::MCP::Server.new(
275
+ name: "my_server",
276
+ transport: {
277
+ type: "stdio",
278
+ command: "my-mcp-server"
279
+ }
280
+ )
281
+
282
+ # Client connection
283
+ client = RobotLab::MCP::Client.new(server)
284
+ client.connect
285
+
286
+ client.connected? # => true
287
+ client.list_tools # => Array of tool definitions
288
+ client.call_tool("search", { query: "ruby" })
289
+ client.list_resources # => Array of resource definitions
290
+ client.disconnect
291
+ ```
292
+
248
293
  ## Error Handling
249
294
 
250
295
  ### Connection Errors
251
296
 
252
297
  ```ruby
253
298
  begin
254
- network.run(state: state)
299
+ result = robot.run("Search for repos")
255
300
  rescue RobotLab::MCPError => e
256
301
  puts "MCP Error: #{e.message}"
257
- # Handle gracefully
258
302
  end
259
303
  ```
260
304
 
261
- ### Missing Dependencies
262
-
263
- ```ruby
264
- # If async-websocket not installed
265
- rescue RobotLab::MCPError => e
266
- if e.message.include?("async-websocket")
267
- puts "Install async-websocket gem for WebSocket support"
268
- end
269
- end
270
- ```
305
+ !!! tip
306
+ MCP connection failures are logged as warnings but do not raise errors by default. The robot will continue without MCP tools if a server is unreachable.
271
307
 
272
308
  ## Disconnecting
273
309
 
274
- Robots automatically disconnect from MCP servers when done:
310
+ Robots can be manually disconnected from MCP servers:
275
311
 
276
312
  ```ruby
277
- robot.disconnect # Manually disconnect
313
+ robot.disconnect # Disconnect all MCP clients
278
314
  ```
279
315
 
280
- Networks handle this automatically at the end of a run.
281
-
282
316
  ## Patterns
283
317
 
284
318
  ### Development vs Production
285
319
 
286
320
  ```ruby
287
- network = RobotLab.create_network do
288
- mcp_config = if Rails.env.development?
289
- [{ name: "local_fs", transport: { type: "stdio", command: "mcp-fs", args: ["--root", "."] } }]
290
- else
291
- [{ name: "s3", transport: { type: "stdio", command: "mcp-s3" } }]
292
- end
293
-
294
- mcp mcp_config
321
+ mcp_config = if Rails.env.development?
322
+ [{ name: "local_fs", transport: { type: "stdio", command: "mcp-fs", args: ["--root", "."] } }]
323
+ else
324
+ [{ name: "s3", transport: { type: "stdio", command: "mcp-s3" } }]
295
325
  end
326
+
327
+ robot = RobotLab.build(
328
+ name: "file_handler",
329
+ system_prompt: "You manage files.",
330
+ mcp: mcp_config
331
+ )
296
332
  ```
297
333
 
298
334
  ### Dynamic Server Selection
@@ -305,9 +341,11 @@ def mcp_servers_for_user(user)
305
341
  servers
306
342
  end
307
343
 
308
- network = RobotLab.create_network do
309
- mcp mcp_servers_for_user(current_user)
310
- end
344
+ robot = RobotLab.build(
345
+ name: "assistant",
346
+ system_prompt: "You help the user with connected services.",
347
+ mcp: mcp_servers_for_user(current_user)
348
+ )
311
349
  ```
312
350
 
313
351
  ## Best Practices
@@ -330,24 +368,25 @@ end
330
368
 
331
369
  ### 2. Limit Tool Access
332
370
 
371
+ Restrict which MCP tools are available to a robot using the `tools:` parameter:
372
+
333
373
  ```ruby
334
- # Don't expose all tools
335
- robot = RobotLab.build do
336
- mcp :inherit
337
- tools %w[read_file search_files] # No write access
338
- end
374
+ robot = RobotLab.build(
375
+ name: "reader",
376
+ system_prompt: "You read and search files.",
377
+ mcp: [{ name: "fs", transport: { type: "stdio", command: "mcp-fs" } }],
378
+ tools: %w[read_file search_files] # No write access
379
+ )
339
380
  ```
340
381
 
341
- ### 3. Handle Disconnections
382
+ ### 3. Use Appropriate Transports
342
383
 
343
- ```ruby
344
- begin
345
- result = network.run(state: state)
346
- rescue RobotLab::MCPError
347
- # Retry without MCP
348
- result = network.run(state: state, mcp: :none)
349
- end
350
- ```
384
+ | Transport | Best For |
385
+ |-----------|----------|
386
+ | `stdio` | Local servers, CLI tools |
387
+ | `websocket` | Persistent connections, bidirectional |
388
+ | `sse` | Server push, event streams |
389
+ | `streamable_http` | Remote APIs, session-based |
351
390
 
352
391
  ## Next Steps
353
392