robot_lab 0.0.1 → 0.0.4

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 (145) 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 +90 -0
  5. data/README.md +203 -46
  6. data/Rakefile +70 -1
  7. data/docs/api/core/index.md +12 -0
  8. data/docs/api/core/robot.md +478 -130
  9. data/docs/api/core/tool.md +205 -209
  10. data/docs/api/history/active-record-adapter.md +174 -94
  11. data/docs/api/history/config.md +186 -93
  12. data/docs/api/history/index.md +57 -61
  13. data/docs/api/history/thread-manager.md +123 -73
  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/streaming/context.md +157 -74
  19. data/docs/api/streaming/events.md +114 -166
  20. data/docs/api/streaming/index.md +74 -72
  21. data/docs/architecture/core-concepts.md +361 -112
  22. data/docs/architecture/index.md +97 -59
  23. data/docs/architecture/message-flow.md +138 -129
  24. data/docs/architecture/network-orchestration.md +197 -50
  25. data/docs/architecture/robot-execution.md +199 -146
  26. data/docs/architecture/state-management.md +255 -187
  27. data/docs/concepts.md +312 -48
  28. data/docs/examples/basic-chat.md +89 -77
  29. data/docs/examples/index.md +222 -47
  30. data/docs/examples/mcp-server.md +207 -203
  31. data/docs/examples/multi-robot-network.md +129 -35
  32. data/docs/examples/rails-application.md +159 -160
  33. data/docs/examples/tool-usage.md +295 -204
  34. data/docs/getting-started/configuration.md +275 -162
  35. data/docs/getting-started/index.md +1 -1
  36. data/docs/getting-started/installation.md +22 -13
  37. data/docs/getting-started/quick-start.md +166 -121
  38. data/docs/guides/building-robots.md +417 -212
  39. data/docs/guides/creating-networks.md +94 -24
  40. data/docs/guides/mcp-integration.md +152 -113
  41. data/docs/guides/memory.md +220 -164
  42. data/docs/guides/streaming.md +80 -110
  43. data/docs/guides/using-tools.md +259 -212
  44. data/docs/index.md +50 -37
  45. data/examples/01_simple_robot.rb +6 -9
  46. data/examples/02_tools.rb +6 -9
  47. data/examples/03_network.rb +13 -14
  48. data/examples/04_mcp.rb +5 -8
  49. data/examples/05_streaming.rb +5 -8
  50. data/examples/06_prompt_templates.rb +42 -37
  51. data/examples/07_network_memory.rb +13 -14
  52. data/examples/08_llm_config.rb +140 -0
  53. data/examples/09_chaining.rb +223 -0
  54. data/examples/10_memory.rb +331 -0
  55. data/examples/11_network_introspection.rb +230 -0
  56. data/examples/12_message_bus.rb +74 -0
  57. data/examples/13_spawn.rb +90 -0
  58. data/examples/14_rusty_circuit/comic.rb +143 -0
  59. data/examples/14_rusty_circuit/display.rb +203 -0
  60. data/examples/14_rusty_circuit/heckler.rb +57 -0
  61. data/examples/14_rusty_circuit/open_mic.rb +121 -0
  62. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +20 -0
  63. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +23 -0
  64. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +20 -0
  65. data/examples/14_rusty_circuit/scout.rb +173 -0
  66. data/examples/14_rusty_circuit/scout_notes.md +89 -0
  67. data/examples/14_rusty_circuit/show.log +234 -0
  68. data/examples/15_memory_network_and_bus/editor_in_chief.rb +24 -0
  69. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +206 -0
  70. data/examples/15_memory_network_and_bus/linux_writer.rb +80 -0
  71. data/examples/15_memory_network_and_bus/os_editor.rb +46 -0
  72. data/examples/15_memory_network_and_bus/os_writer.rb +46 -0
  73. data/examples/15_memory_network_and_bus/output/combined_article.md +13 -0
  74. data/examples/15_memory_network_and_bus/output/final_article.md +15 -0
  75. data/examples/15_memory_network_and_bus/output/linux_draft.md +5 -0
  76. data/examples/15_memory_network_and_bus/output/mac_draft.md +7 -0
  77. data/examples/15_memory_network_and_bus/output/memory.json +13 -0
  78. data/examples/15_memory_network_and_bus/output/revision_1.md +19 -0
  79. data/examples/15_memory_network_and_bus/output/revision_2.md +15 -0
  80. data/examples/15_memory_network_and_bus/output/windows_draft.md +7 -0
  81. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +13 -0
  82. data/examples/15_memory_network_and_bus/prompts/os_chief.md +13 -0
  83. data/examples/15_memory_network_and_bus/prompts/os_editor.md +13 -0
  84. data/examples/README.md +197 -0
  85. data/examples/prompts/{assistant/system.txt.erb → assistant.md} +3 -0
  86. data/examples/prompts/{billing/system.txt.erb → billing.md} +3 -0
  87. data/examples/prompts/{classifier/system.txt.erb → classifier.md} +3 -0
  88. data/examples/prompts/comedian.md +6 -0
  89. data/examples/prompts/comedy_critic.md +10 -0
  90. data/examples/prompts/configurable.md +9 -0
  91. data/examples/prompts/dispatcher.md +12 -0
  92. data/examples/prompts/{entity_extractor/system.txt.erb → entity_extractor.md} +3 -0
  93. data/examples/prompts/{escalation/system.txt.erb → escalation.md} +7 -0
  94. data/examples/prompts/frontmatter_mcp_test.md +9 -0
  95. data/examples/prompts/frontmatter_named_test.md +5 -0
  96. data/examples/prompts/frontmatter_tools_test.md +6 -0
  97. data/examples/prompts/{general/system.txt.erb → general.md} +3 -0
  98. data/examples/prompts/{github_assistant/system.txt.erb → github_assistant.md} +8 -0
  99. data/examples/prompts/{helper/system.txt.erb → helper.md} +3 -0
  100. data/examples/prompts/{keyword_extractor/system.txt.erb → keyword_extractor.md} +3 -0
  101. data/examples/prompts/llm_config_demo.md +20 -0
  102. data/examples/prompts/{order_support/system.txt.erb → order_support.md} +8 -0
  103. data/examples/prompts/os_advocate.md +13 -0
  104. data/examples/prompts/os_chief.md +13 -0
  105. data/examples/prompts/os_editor.md +13 -0
  106. data/examples/prompts/{product_support/system.txt.erb → product_support.md} +7 -0
  107. data/examples/prompts/{sentiment_analyzer/system.txt.erb → sentiment_analyzer.md} +3 -0
  108. data/examples/prompts/{synthesizer/system.txt.erb → synthesizer.md} +3 -0
  109. data/examples/prompts/{technical/system.txt.erb → technical.md} +3 -0
  110. data/examples/prompts/{triage/system.txt.erb → triage.md} +6 -0
  111. data/lib/generators/robot_lab/templates/initializer.rb.tt +1 -1
  112. data/lib/robot_lab/adapters/openai.rb +2 -1
  113. data/lib/robot_lab/ask_user.rb +75 -0
  114. data/lib/robot_lab/config/defaults.yml +121 -0
  115. data/lib/robot_lab/config.rb +183 -0
  116. data/lib/robot_lab/error.rb +6 -0
  117. data/lib/robot_lab/mcp/client.rb +1 -1
  118. data/lib/robot_lab/memory.rb +2 -2
  119. data/lib/robot_lab/robot.rb +523 -249
  120. data/lib/robot_lab/robot_message.rb +44 -0
  121. data/lib/robot_lab/robot_result.rb +1 -0
  122. data/lib/robot_lab/robotic_model.rb +1 -1
  123. data/lib/robot_lab/streaming/context.rb +1 -1
  124. data/lib/robot_lab/tool.rb +108 -172
  125. data/lib/robot_lab/tool_config.rb +1 -1
  126. data/lib/robot_lab/tool_manifest.rb +2 -18
  127. data/lib/robot_lab/version.rb +1 -1
  128. data/lib/robot_lab.rb +66 -55
  129. metadata +107 -116
  130. data/examples/prompts/assistant/user.txt.erb +0 -1
  131. data/examples/prompts/billing/user.txt.erb +0 -1
  132. data/examples/prompts/classifier/user.txt.erb +0 -1
  133. data/examples/prompts/entity_extractor/user.txt.erb +0 -3
  134. data/examples/prompts/escalation/user.txt.erb +0 -34
  135. data/examples/prompts/general/user.txt.erb +0 -1
  136. data/examples/prompts/github_assistant/user.txt.erb +0 -1
  137. data/examples/prompts/helper/user.txt.erb +0 -1
  138. data/examples/prompts/keyword_extractor/user.txt.erb +0 -3
  139. data/examples/prompts/order_support/user.txt.erb +0 -22
  140. data/examples/prompts/product_support/user.txt.erb +0 -32
  141. data/examples/prompts/sentiment_analyzer/user.txt.erb +0 -3
  142. data/examples/prompts/synthesizer/user.txt.erb +0 -15
  143. data/examples/prompts/technical/user.txt.erb +0 -1
  144. data/examples/prompts/triage/user.txt.erb +0 -17
  145. data/lib/robot_lab/configuration.rb +0 -143
@@ -4,7 +4,7 @@ Customer service system with intelligent routing using SimpleFlow pipelines.
4
4
 
5
5
  ## Overview
6
6
 
7
- This example demonstrates a multi-robot network where a classifier routes customer inquiries to specialized support robots using optional task activation.
7
+ This example demonstrates a multi-robot network where a classifier routes customer inquiries to specialized support robots using SimpleFlow's optional task activation.
8
8
 
9
9
  ## Complete Example
10
10
 
@@ -15,14 +15,12 @@ This example demonstrates a multi-robot network where a classifier routes custom
15
15
  require "bundler/setup"
16
16
  require "robot_lab"
17
17
 
18
- RobotLab.configure do |config|
19
- config.default_model = "claude-sonnet-4"
20
- end
21
-
22
18
  # Custom classifier that routes to specialists
23
19
  class ClassifierRobot < RobotLab::Robot
24
20
  def call(result)
25
- robot_result = run(**extract_run_context(result))
21
+ context = extract_run_context(result)
22
+ message = context.delete(:message)
23
+ robot_result = run(message, **context)
26
24
 
27
25
  new_result = result
28
26
  .with_context(@name.to_sym, robot_result)
@@ -44,7 +42,7 @@ end
44
42
  classifier = ClassifierRobot.new(
45
43
  name: "classifier",
46
44
  description: "Classifies customer inquiries",
47
- system_prompt: <<~PROMPT
45
+ system_prompt: <<~PROMPT,
48
46
  You are a customer inquiry classifier. Analyze the customer's message
49
47
  and respond with exactly ONE of these categories:
50
48
 
@@ -55,13 +53,14 @@ classifier = ClassifierRobot.new(
55
53
 
56
54
  Respond with ONLY the category name, nothing else.
57
55
  PROMPT
56
+ model: "claude-sonnet-4"
58
57
  )
59
58
 
60
59
  # Billing specialist
61
60
  billing_agent = RobotLab.build(
62
61
  name: "billing_agent",
63
62
  description: "Handles billing inquiries",
64
- system_prompt: <<~PROMPT
63
+ system_prompt: <<~PROMPT,
65
64
  You are a billing support specialist. You help customers with:
66
65
  - Payment issues and refunds
67
66
  - Invoice questions
@@ -70,13 +69,14 @@ billing_agent = RobotLab.build(
70
69
 
71
70
  Be helpful, empathetic, and provide clear next steps.
72
71
  PROMPT
72
+ model: "claude-sonnet-4"
73
73
  )
74
74
 
75
75
  # Technical support
76
76
  tech_agent = RobotLab.build(
77
77
  name: "tech_agent",
78
78
  description: "Handles technical issues",
79
- system_prompt: <<~PROMPT
79
+ system_prompt: <<~PROMPT,
80
80
  You are a technical support specialist. You help customers with:
81
81
  - Bug reports and troubleshooting
82
82
  - Feature explanations
@@ -85,13 +85,14 @@ tech_agent = RobotLab.build(
85
85
 
86
86
  Ask clarifying questions when needed. Provide step-by-step solutions.
87
87
  PROMPT
88
+ model: "claude-sonnet-4"
88
89
  )
89
90
 
90
91
  # Account specialist
91
92
  account_agent = RobotLab.build(
92
93
  name: "account_agent",
93
94
  description: "Handles account issues",
94
- system_prompt: <<~PROMPT
95
+ system_prompt: <<~PROMPT,
95
96
  You are an account support specialist. You help customers with:
96
97
  - Login and authentication issues
97
98
  - Profile and settings changes
@@ -100,13 +101,14 @@ account_agent = RobotLab.build(
100
101
 
101
102
  Prioritize security while being helpful.
102
103
  PROMPT
104
+ model: "claude-sonnet-4"
103
105
  )
104
106
 
105
107
  # General support
106
108
  general_agent = RobotLab.build(
107
109
  name: "general_agent",
108
110
  description: "Handles general inquiries",
109
- system_prompt: <<~PROMPT
111
+ system_prompt: <<~PROMPT,
110
112
  You are a general support agent. You help customers with:
111
113
  - Product information
112
114
  - General questions
@@ -115,6 +117,7 @@ general_agent = RobotLab.build(
115
117
 
116
118
  Be friendly and informative.
117
119
  PROMPT
120
+ model: "claude-sonnet-4"
118
121
  )
119
122
 
120
123
  # Create the network with optional task routing
@@ -161,16 +164,53 @@ test_inquiries.each do |inquiry|
161
164
  end
162
165
  ```
163
166
 
164
- ## With Context Passing
167
+ ## ClassifierRobot Pattern
168
+
169
+ The key to conditional routing is overriding the `call` method. The base `Robot#call` extracts the message from the `SimpleFlow::Result` and calls `run`. A classifier overrides this to inspect the output and activate the appropriate optional task:
165
170
 
166
171
  ```ruby
167
- # Enhanced version with additional context
172
+ class ClassifierRobot < RobotLab::Robot
173
+ def call(result)
174
+ # 1. Extract run context from the SimpleFlow result
175
+ context = extract_run_context(result)
176
+
177
+ # 2. Pull out the message (it's a key in the context hash)
178
+ message = context.delete(:message)
179
+
180
+ # 3. Run the robot to get a classification
181
+ robot_result = run(message, **context)
182
+
183
+ # 4. Store our result and continue the pipeline
184
+ new_result = result
185
+ .with_context(@name.to_sym, robot_result)
186
+ .continue(robot_result)
187
+
188
+ # 5. Activate the appropriate optional task based on output
189
+ category = robot_result.last_text_content.to_s.strip.downcase
190
+ case category
191
+ when /billing/ then new_result.activate(:billing)
192
+ when /technical/ then new_result.activate(:technical)
193
+ else new_result.activate(:general)
194
+ end
195
+ end
196
+ end
197
+ ```
198
+
199
+ !!! note "extract_run_context"
200
+ The `extract_run_context(result)` method is a protected helper on `Robot`. It extracts `run_params` from the SimpleFlow result context, handles value propagation from previous steps, and separates robot-specific params (`mcp:`, `tools:`, `memory:`, `network_memory:`) from the message and other context.
168
201
 
202
+ ## With Context Passing
203
+
204
+ Enhanced version where the classifier passes additional context to specialists:
205
+
206
+ ```ruby
169
207
  class ContextAwareClassifier < RobotLab::Robot
170
208
  def call(result)
171
- robot_result = run(**extract_run_context(result))
209
+ context = extract_run_context(result)
210
+ message = context.delete(:message)
211
+ robot_result = run(message, **context)
172
212
 
173
- # Store classification in context for specialist
213
+ # Store classification and original message in context for specialist
174
214
  new_result = result
175
215
  .with_context(@name.to_sym, robot_result)
176
216
  .with_context(:classification, robot_result.last_text_content.strip)
@@ -193,11 +233,10 @@ class BillingAgent < RobotLab::Robot
193
233
  classification = result.context[:classification]
194
234
  original_message = result.context[:original_message]
195
235
 
196
- robot_result = run(
197
- **extract_run_context(result),
198
- classification: classification,
199
- customer_message: original_message
200
- )
236
+ context = extract_run_context(result)
237
+ message = context.delete(:message)
238
+
239
+ robot_result = run(message, **context)
201
240
 
202
241
  result.with_context(@name.to_sym, robot_result).continue(robot_result)
203
242
  end
@@ -206,8 +245,9 @@ end
206
245
 
207
246
  ## Per-Task Configuration
208
247
 
248
+ Tasks can have individual context, tools, and MCP servers:
249
+
209
250
  ```ruby
210
- # Tasks with individual context and tools
211
251
  network = RobotLab.create_network(name: "support") do
212
252
  task :classifier, classifier, depends_on: :none
213
253
  task :billing_agent, billing_agent,
@@ -216,15 +256,33 @@ network = RobotLab.create_network(name: "support") do
216
256
  depends_on: :optional
217
257
  task :tech_agent, tech_agent,
218
258
  context: { department: "technical" },
219
- mcp: [FilesystemServer],
259
+ mcp: [filesystem_server],
220
260
  depends_on: :optional
221
261
  end
222
262
  ```
223
263
 
264
+ The `Task` wrapper deep-merges per-task context with the network's run params before delegating to the robot's `call` method.
265
+
224
266
  ## Pipeline Pattern
225
267
 
268
+ Sequential processing pipeline where each robot depends on the previous:
269
+
226
270
  ```ruby
227
- # Sequential processing pipeline
271
+ extractor = RobotLab.build(
272
+ name: "extractor",
273
+ system_prompt: "Extract key information from documents."
274
+ )
275
+
276
+ analyzer = RobotLab.build(
277
+ name: "analyzer",
278
+ system_prompt: "Analyze extracted data and provide insights."
279
+ )
280
+
281
+ formatter = RobotLab.build(
282
+ name: "formatter",
283
+ system_prompt: "Format analysis results into a clear report."
284
+ )
285
+
228
286
  network = RobotLab.create_network(name: "document_processor") do
229
287
  task :extract, extractor, depends_on: :none
230
288
  task :analyze, analyzer, depends_on: [:extract]
@@ -237,35 +295,69 @@ puts result.value.last_text_content
237
295
 
238
296
  ## Parallel Analysis Pattern
239
297
 
298
+ Fan-out / fan-in pattern where multiple robots analyze in parallel and a synthesizer merges results:
299
+
240
300
  ```ruby
241
- # Fan-out / fan-in pattern
242
- network = RobotLab.create_network(name: "multi_analysis", concurrency: :threads) do
301
+ network = RobotLab.create_network(name: "multi_analysis") do
243
302
  task :prepare, preparer, depends_on: :none
244
303
 
245
- # These run in parallel
304
+ # These run in parallel (all depend on :prepare)
246
305
  task :sentiment, sentiment_analyzer, depends_on: [:prepare]
247
306
  task :entities, entity_extractor, depends_on: [:prepare]
248
307
  task :keywords, keyword_extractor, depends_on: [:prepare]
249
308
 
250
- # Waits for all three
309
+ # Waits for all three to complete
251
310
  task :summarize, summarizer, depends_on: [:sentiment, :entities, :keywords]
252
311
  end
253
312
 
254
313
  result = network.run(message: "Analyze this text")
255
314
 
256
- # Access parallel results
315
+ # Access parallel results from context
257
316
  puts "Sentiment: #{result.context[:sentiment].last_text_content}"
258
317
  puts "Entities: #{result.context[:entities].last_text_content}"
259
318
  puts "Keywords: #{result.context[:keywords].last_text_content}"
260
319
  puts "Summary: #{result.value.last_text_content}"
261
320
  ```
262
321
 
322
+ ## Shared Memory in Networks
323
+
324
+ Networks provide a shared `Memory` instance that all robots can read and write. This is especially useful for parallel robots that need to coordinate:
325
+
326
+ ```ruby
327
+ class AnalysisRobot < RobotLab::Robot
328
+ def initialize(memory_key:, **opts)
329
+ super(**opts)
330
+ @memory_key = memory_key
331
+ end
332
+
333
+ def call(result)
334
+ context = extract_run_context(result)
335
+ network_memory = context.delete(:network_memory)
336
+ message = context.delete(:message)
337
+
338
+ robot_result = run(message, network_memory: network_memory, **context)
339
+
340
+ # Write parsed results to shared memory
341
+ if network_memory
342
+ network_memory.current_writer = @name
343
+ network_memory.set(@memory_key, robot_result.last_text_content)
344
+ end
345
+
346
+ result.with_context(@name.to_sym, robot_result).continue(robot_result)
347
+ end
348
+ end
349
+ ```
350
+
263
351
  ## Conditional Halting
264
352
 
353
+ Use `result.halt` to stop the pipeline early:
354
+
265
355
  ```ruby
266
356
  class ValidatorRobot < RobotLab::Robot
267
357
  def call(result)
268
- robot_result = run(**extract_run_context(result))
358
+ context = extract_run_context(result)
359
+ message = context.delete(:message)
360
+ robot_result = run(message, **context)
269
361
 
270
362
  if robot_result.last_text_content.include?("INVALID")
271
363
  # Halt the pipeline early
@@ -298,12 +390,14 @@ ruby examples/customer_service.rb
298
390
 
299
391
  ## Key Concepts
300
392
 
301
- 1. **SimpleFlow Pipeline**: DAG-based execution with dependency management
302
- 2. **Optional Tasks**: Activated dynamically based on classification
303
- 3. **Robot#call**: Custom routing logic in classifier robots
304
- 4. **Context Flow**: Data passed through `result.context`
305
- 5. **Parallel Execution**: Tasks with same dependencies run concurrently
306
- 6. **Per-Task Configuration**: Each task can have its own context, tools, and MCP servers
393
+ 1. **SimpleFlow Pipeline**: DAG-based execution with dependency management via `depends_on:`
394
+ 2. **Optional Tasks**: Use `depends_on: :optional` for tasks activated dynamically by classifiers
395
+ 3. **Robot#call Override**: Custom routing logic in classifier robots that override the `call` method
396
+ 4. **extract_run_context**: Helper method to extract message and params from `SimpleFlow::Result`
397
+ 5. **Context Flow**: Data passed through `result.context` and accessed by downstream robots
398
+ 6. **Parallel Execution**: Tasks with the same dependencies run concurrently
399
+ 7. **Shared Memory**: Network memory (`network_memory:`) enables inter-robot communication
400
+ 8. **Per-Task Configuration**: Each task can have its own context, tools, and MCP servers via `Task`
307
401
 
308
402
  ## See Also
309
403