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
@@ -7,8 +7,9 @@ Networks coordinate multiple robots using [SimpleFlow](https://github.com/MadBom
7
7
  A network is a thin wrapper around `SimpleFlow::Pipeline`:
8
8
 
9
9
  - **Pipeline**: DAG-based execution engine
10
- - **Robots**: Named collection of task handlers
11
- - **Tasks**: Define dependencies and execution order
10
+ - **Robots**: Named collection of robot instances
11
+ - **Tasks**: Wrap robots with per-task configuration and define dependencies
12
+ - **Memory**: Shared reactive memory for inter-robot communication
12
13
 
13
14
  ```ruby
14
15
  network = RobotLab.create_network(name: "customer_service") do
@@ -18,9 +19,27 @@ network = RobotLab.create_network(name: "customer_service") do
18
19
  end
19
20
  ```
20
21
 
22
+ ## Creating Networks
23
+
24
+ Networks are created via `RobotLab.create_network` with a block DSL:
25
+
26
+ ```ruby
27
+ analyst = RobotLab.build(name: "analyst", system_prompt: "Analyze the input.")
28
+ writer = RobotLab.build(name: "writer", system_prompt: "Write a report.")
29
+ reviewer = RobotLab.build(name: "reviewer", system_prompt: "Review the report.")
30
+
31
+ network = RobotLab.create_network(name: "pipeline") do
32
+ task :analyst, analyst, depends_on: :none
33
+ task :writer, writer, depends_on: [:analyst]
34
+ task :reviewer, reviewer, depends_on: [:writer]
35
+ end
36
+
37
+ result = network.run(message: "Analyze this quarterly data")
38
+ ```
39
+
21
40
  ## Task Configuration
22
41
 
23
- Tasks can have per-task configuration that's deep-merged with network run params:
42
+ Tasks can have per-task configuration that is deep-merged with network run params:
24
43
 
25
44
  ```ruby
26
45
  network = RobotLab.create_network(name: "support") do
@@ -31,11 +50,23 @@ network = RobotLab.create_network(name: "support") do
31
50
  depends_on: :optional
32
51
  task :technical, technical_robot,
33
52
  context: { department: "technical" },
34
- mcp: [FilesystemServer],
53
+ mcp: [filesystem_server],
35
54
  depends_on: :optional
36
55
  end
37
56
  ```
38
57
 
58
+ ### Task Parameters
59
+
60
+ | Parameter | Type | Description |
61
+ |-----------|------|-------------|
62
+ | `name` | Symbol | Task/step name |
63
+ | `robot` | Robot | The robot instance |
64
+ | `context` | Hash | Task-specific context (deep-merged with run params) |
65
+ | `mcp` | Symbol, Array | MCP server config (`:none`, `:inherit`, or array) |
66
+ | `tools` | Symbol, Array | Tools config (`:none`, `:inherit`, or array) |
67
+ | `memory` | Memory, Hash, nil | Task-specific memory |
68
+ | `depends_on` | Symbol, Array | Dependencies (`:none`, `:optional`, or task names) |
69
+
39
70
  ## Execution Model
40
71
 
41
72
  ```mermaid
@@ -55,53 +86,86 @@ stateDiagram-v2
55
86
  | Type | Description |
56
87
  |------|-------------|
57
88
  | `:none` | No dependencies, runs first |
58
- | `[:task1, :task2]` | Waits for listed tasks |
59
- | `:optional` | Only runs when activated |
89
+ | `[:task1, :task2]` | Waits for listed tasks to complete |
90
+ | `:optional` | Only runs when explicitly activated |
60
91
 
61
92
  ## Robot#call Interface
62
93
 
63
- Each robot implements the SimpleFlow step interface:
94
+ Each robot implements the SimpleFlow step interface via `call(result)`:
64
95
 
65
96
  ```ruby
66
- class Robot
67
- def call(result)
68
- # Run the LLM
69
- robot_result = run(**extract_run_context(result))
97
+ # Inside Robot (simplified)
98
+ def call(result)
99
+ run_context = extract_run_context(result)
100
+ message = run_context.delete(:message)
70
101
 
71
- # Return new result with context
72
- result
73
- .with_context(@name.to_sym, robot_result)
74
- .continue(robot_result)
75
- end
102
+ robot_result = run(message, **run_context)
103
+
104
+ result
105
+ .with_context(@name.to_sym, robot_result)
106
+ .continue(robot_result)
76
107
  end
77
108
  ```
78
109
 
79
- ### Result Methods
110
+ ### extract_run_context
80
111
 
81
- | Method | Description |
82
- |--------|-------------|
83
- | `continue(value)` | Continue to next tasks |
84
- | `halt(value)` | Stop pipeline execution |
85
- | `with_context(key, val)` | Add data to context |
86
- | `activate(task_name)` | Enable optional task |
112
+ The `extract_run_context` method pulls parameters from the SimpleFlow result:
113
+
114
+ - Extracts `:mcp`, `:tools`, `:memory`, and `:network_memory` from `run_params`
115
+ - Merges the current result value into the context
116
+ - If the previous result value is a `RobotResult`, extracts its `last_text_content` as the message
117
+ - If it is a String, uses it directly as the message
118
+ - If it is a Hash, merges it with the run params
119
+
120
+ ## Task#call Interface
121
+
122
+ Each `Task` wraps a robot and enhances the SimpleFlow result before delegation:
123
+
124
+ ```ruby
125
+ # Inside Task (simplified)
126
+ def call(result)
127
+ # Deep merge task context with run_params
128
+ run_params = deep_merge(
129
+ result.context[:run_params] || {},
130
+ @context
131
+ )
132
+
133
+ # Add task-specific config
134
+ run_params[:mcp] = @mcp unless @mcp == :none
135
+ run_params[:tools] = @tools unless @tools == :none
136
+ run_params[:memory] = @memory if @memory
137
+
138
+ enhanced_result = result.with_context(:run_params, run_params)
139
+ @robot.call(enhanced_result)
140
+ end
141
+ ```
87
142
 
88
143
  ## SimpleFlow::Result
89
144
 
90
145
  The result object flows through the pipeline:
91
146
 
92
147
  ```ruby
93
- result.value # Current task's output
148
+ result.value # Current task's output (RobotResult)
94
149
  result.context # Accumulated context from all tasks
95
150
  result.halted? # Whether execution stopped early
96
151
  result.continued? # Whether execution continues
97
152
  ```
98
153
 
154
+ ### Result Methods
155
+
156
+ | Method | Description |
157
+ |--------|-------------|
158
+ | `continue(value)` | Continue to next tasks |
159
+ | `halt(value)` | Stop pipeline execution |
160
+ | `with_context(key, val)` | Add data to context |
161
+ | `activate(task_name)` | Enable an optional task |
162
+
99
163
  ### Context Structure
100
164
 
101
165
  ```ruby
102
166
  {
103
- run_params: { message: "...", customer_id: 123 },
104
- classifier: RobotResult,
167
+ run_params: { message: "...", customer_id: 123, network_memory: memory },
168
+ classifier: RobotResult, # Stored by Robot#call
105
169
  billing: RobotResult,
106
170
  # ... other task results
107
171
  }
@@ -109,18 +173,45 @@ result.continued? # Whether execution continues
109
173
 
110
174
  ## Optional Task Activation
111
175
 
112
- Optional tasks don't run automatically. They must be activated:
176
+ Optional tasks (those with `depends_on: :optional`) do not run automatically. They must be activated by a preceding task using `result.activate(:task_name)`.
177
+
178
+ This pattern is commonly used with a classifier robot that analyzes the input and routes to the appropriate handler:
179
+
180
+ ```ruby
181
+ classifier = RobotLab.build(
182
+ name: "classifier",
183
+ system_prompt: "Classify the request. Respond with: BILLING, TECHNICAL, or GENERAL."
184
+ )
185
+
186
+ billing = RobotLab.build(name: "billing", system_prompt: "Handle billing requests.")
187
+ technical = RobotLab.build(name: "technical", system_prompt: "Handle technical requests.")
188
+ general = RobotLab.build(name: "general", system_prompt: "Handle general requests.")
189
+
190
+ network = RobotLab.create_network(name: "support") do
191
+ task :classifier, classifier, depends_on: :none
192
+ task :billing, billing, depends_on: :optional
193
+ task :technical, technical, depends_on: :optional
194
+ task :general, general, depends_on: :optional
195
+ end
196
+ ```
197
+
198
+ ### Classifier Robot Pattern
199
+
200
+ To activate optional tasks, a robot subclass overrides `call` to inspect its own output and activate the appropriate downstream task:
113
201
 
114
202
  ```ruby
115
203
  class ClassifierRobot < RobotLab::Robot
116
204
  def call(result)
117
- robot_result = run(**extract_run_context(result))
205
+ run_context = extract_run_context(result)
206
+ message = run_context.delete(:message)
207
+
208
+ robot_result = run(message, **run_context)
118
209
 
119
210
  new_result = result
120
211
  .with_context(@name.to_sym, robot_result)
121
212
  .continue(robot_result)
122
213
 
123
- # Analyze output and activate appropriate task
214
+ # Analyze output and activate the appropriate optional task
124
215
  category = robot_result.last_text_content.to_s.downcase
125
216
 
126
217
  case category
@@ -135,6 +226,56 @@ class ClassifierRobot < RobotLab::Robot
135
226
  end
136
227
  ```
137
228
 
229
+ ## Shared Memory
230
+
231
+ All robots in a network share the network's memory during execution. The network injects its memory into the run context:
232
+
233
+ ```ruby
234
+ # Inside Network#run
235
+ def run(**run_context)
236
+ run_context[:network_memory] = @memory
237
+ initial_result = SimpleFlow::Result.new(
238
+ run_context,
239
+ context: { run_params: run_context }
240
+ )
241
+ @pipeline.call_parallel(initial_result)
242
+ end
243
+ ```
244
+
245
+ Robots use the shared memory for inter-robot communication:
246
+
247
+ ```ruby
248
+ # Robot A writes to shared memory
249
+ memory.set(:classification, "billing")
250
+
251
+ # Robot B reads from shared memory
252
+ category = memory.get(:classification, wait: true)
253
+ ```
254
+
255
+ See [Memory Management](state-management.md) for details on reactive features like subscriptions and blocking reads.
256
+
257
+ ## Broadcasting
258
+
259
+ Networks support a broadcast channel for network-wide announcements:
260
+
261
+ ```ruby
262
+ # Register handlers
263
+ network.on_broadcast do |message|
264
+ case message[:payload][:event]
265
+ when :pause
266
+ pause_current_work
267
+ when :resume
268
+ resume_work
269
+ end
270
+ end
271
+
272
+ # Send broadcasts
273
+ network.broadcast(event: :pause, reason: "rate limit hit")
274
+ network.broadcast(event: :phase_complete, phase: "analysis")
275
+ ```
276
+
277
+ Broadcasts are dispatched asynchronously and also written to memory at the `_network_broadcast` key, so robots can subscribe via `memory.subscribe(:_network_broadcast)`.
278
+
138
279
  ## Parallel Execution
139
280
 
140
281
  Tasks with the same dependencies can run in parallel:
@@ -143,7 +284,7 @@ Tasks with the same dependencies can run in parallel:
143
284
  network = RobotLab.create_network(name: "analysis", concurrency: :threads) do
144
285
  task :fetch, fetcher, depends_on: :none
145
286
 
146
- # These three run in parallel
287
+ # These three run in parallel after :fetch completes
147
288
  task :sentiment, sentiment_bot, depends_on: [:fetch]
148
289
  task :entities, entity_bot, depends_on: [:fetch]
149
290
  task :keywords, keyword_bot, depends_on: [:fetch]
@@ -163,28 +304,26 @@ end
163
304
 
164
305
  ## Data Flow
165
306
 
166
- 1. **Initial Value**: `network.run(**params)` creates initial result
307
+ 1. **Initial Value**: `network.run(**params)` creates an initial `SimpleFlow::Result` with the run context
167
308
  2. **Run Params**: Stored in `result.context[:run_params]`
168
- 3. **Task Results**: Each task adds to context
309
+ 3. **Task Results**: Each task adds its `RobotResult` to context under its task name
169
310
  4. **Final Value**: Last task's output becomes `result.value`
170
311
 
171
312
  ```ruby
172
- # Run with context
173
313
  result = network.run(
174
314
  message: "Help with billing",
175
315
  customer_id: 123
176
316
  )
177
317
 
178
- # Access the flow
179
- result.context[:run_params] # { message: "...", customer_id: 123 }
180
- result.context[:classifier] # First robot's RobotResult
181
- result.context[:billing] # Billing robot's RobotResult
182
- result.value # Final RobotResult
318
+ result.context[:run_params] #=> { message: "...", customer_id: 123, network_memory: ... }
319
+ result.context[:classifier] #=> RobotResult from classifier
320
+ result.context[:billing] #=> RobotResult from billing robot
321
+ result.value #=> Final RobotResult
183
322
  ```
184
323
 
185
324
  ## Visualization
186
325
 
187
- Networks provide visualization methods:
326
+ Networks provide visualization methods via the underlying SimpleFlow pipeline:
188
327
 
189
328
  ```ruby
190
329
  # ASCII representation
@@ -193,24 +332,32 @@ puts network.visualize
193
332
  # Mermaid diagram
194
333
  puts network.to_mermaid
195
334
 
335
+ # DOT format (Graphviz)
336
+ puts network.to_dot
337
+
196
338
  # Execution plan description
197
339
  puts network.execution_plan
198
340
  ```
199
341
 
200
- ## Network Configuration
342
+ ## Network Inspection
201
343
 
202
344
  ```ruby
203
- network = RobotLab.create_network(
204
- name: "support",
205
- concurrency: :threads # :auto, :threads, or :async
206
- ) do
207
- task :classifier, classifier, depends_on: :none
208
- task :handler, handler, depends_on: [:classifier]
209
- end
345
+ # Get a robot by name
346
+ network.robot(:classifier) #=> Robot
347
+ network[:classifier] #=> Robot (alias)
348
+
349
+ # List all robots
350
+ network.available_robots #=> [Robot, Robot, ...]
351
+
352
+ # Add a robot without a task
353
+ network.add_robot(extra_robot)
354
+
355
+ # Convert to hash
356
+ network.to_h
357
+ #=> { name: "support", robots: ["classifier", "billing"], tasks: [...], optional_tasks: [...] }
210
358
  ```
211
359
 
212
360
  ## Next Steps
213
361
 
214
- - [Creating Networks](../guides/creating-networks.md) - Practical patterns
215
- - [Robot Execution](robot-execution.md) - How robots process messages
216
- - [API Reference: Network](../api/core/network.md) - Complete API
362
+ - [Memory Management](state-management.md) - Shared memory and reactive features
363
+ - [Message Flow](message-flow.md) - How messages are processed within robots