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,24 +4,26 @@ This guide covers everything you need to know about creating robots in RobotLab.
4
4
 
5
5
  ## Basic Robot
6
6
 
7
- Create a simple robot with the builder DSL:
7
+ Create a robot using the `RobotLab.build` factory method with keyword arguments:
8
8
 
9
9
  ```ruby
10
- robot = RobotLab.build do
11
- name "assistant"
12
- description "A helpful AI assistant"
13
- template "You are a helpful assistant."
14
- end
10
+ robot = RobotLab.build(
11
+ name: "assistant",
12
+ system_prompt: "You are a helpful assistant."
13
+ )
14
+
15
+ result = robot.run("Hello!")
16
+ puts result.last_text_content
15
17
  ```
16
18
 
17
19
  ## Robot Properties
18
20
 
19
21
  ### Name
20
22
 
21
- A unique identifier used for routing and logging:
23
+ A unique identifier used for routing and logging. If omitted, an auto-generated name is used:
22
24
 
23
25
  ```ruby
24
- name "support_agent"
26
+ robot = RobotLab.build(name: "support_agent", system_prompt: "...")
25
27
  ```
26
28
 
27
29
  ### Description
@@ -29,348 +31,551 @@ name "support_agent"
29
31
  Describes what the robot does (useful for routing decisions):
30
32
 
31
33
  ```ruby
32
- description "Handles customer support inquiries about orders and refunds"
34
+ robot = RobotLab.build(
35
+ name: "support_agent",
36
+ description: "Handles customer support inquiries about orders and refunds",
37
+ system_prompt: "..."
38
+ )
33
39
  ```
34
40
 
35
41
  ### Model
36
42
 
37
- The LLM model to use:
43
+ The LLM model to use. Defaults to the value in `RobotLab.config.ruby_llm.model`:
38
44
 
39
45
  ```ruby
40
- model "claude-sonnet-4" # Anthropic
41
- model "gpt-4o" # OpenAI
42
- model "gemini-1.5-pro" # Google
46
+ robot = RobotLab.build(
47
+ name: "writer",
48
+ model: "claude-sonnet-4",
49
+ system_prompt: "You are a creative writer."
50
+ )
43
51
  ```
44
52
 
45
- ### Template (System Prompt)
53
+ ### System Prompt
46
54
 
47
- Instructions that define the robot's personality and behavior:
55
+ An inline string that defines the robot's personality and behavior:
48
56
 
49
57
  ```ruby
50
- template <<~PROMPT
51
- You are a customer support specialist for TechCo.
58
+ robot = RobotLab.build(
59
+ name: "support",
60
+ system_prompt: <<~PROMPT
61
+ You are a customer support specialist for TechCo.
52
62
 
53
- Your responsibilities:
54
- - Answer questions about products and services
55
- - Help resolve order issues
56
- - Provide friendly, professional assistance
63
+ Your responsibilities:
64
+ - Answer questions about products and services
65
+ - Help resolve order issues
66
+ - Provide friendly, professional assistance
57
67
 
58
- Always be polite and acknowledge the customer's concerns.
59
- PROMPT
68
+ Always be polite and acknowledge the customer's concerns.
69
+ PROMPT
70
+ )
60
71
  ```
61
72
 
62
- ## Adding Tools
73
+ ## Template Files
63
74
 
64
- Give robots capabilities with tools:
75
+ Templates are `.md` files managed by [prompt_manager](https://github.com/MadBomber/prompt_manager). Reference a template by symbol; RobotLab resolves it through the configured template path.
65
76
 
66
77
  ```ruby
67
- robot = RobotLab.build do
68
- name "order_assistant"
69
-
70
- tool :lookup_order do
71
- description "Look up order details by order ID"
72
- parameter :order_id, type: :string, required: true, description: "The order ID"
73
- handler do |order_id:, **_context|
74
- Order.find_by(id: order_id)&.to_h || { error: "Order not found" }
75
- end
76
- end
78
+ # Reference template by symbol (loads prompts/support.md)
79
+ robot = RobotLab.build(
80
+ name: "support",
81
+ template: :support,
82
+ context: { company: "TechCo", tone: "friendly" }
83
+ )
84
+ ```
77
85
 
78
- tool :check_inventory do
79
- description "Check product inventory"
80
- parameter :product_id, type: :string, required: true
81
- parameter :warehouse, type: :string, default: "main"
82
- handler do |product_id:, warehouse:, **_context|
83
- Inventory.check(product_id, warehouse: warehouse)
84
- end
85
- end
86
- end
86
+ ### Template Format
87
+
88
+ Templates use `.md` files with YAML front matter:
89
+
90
+ ```markdown title="prompts/support.md"
91
+ ---
92
+ description: Customer support assistant
93
+ parameters:
94
+ company: "Acme"
95
+ tone: "professional"
96
+ model: claude-sonnet-4
97
+ temperature: 0.7
98
+ ---
99
+ You are a support agent for <%= company %>.
100
+ Your tone should be <%= tone %>.
87
101
  ```
88
102
 
89
- ### Tool Parameters
103
+ ### Front Matter Configuration
104
+
105
+ The following YAML front matter keys are applied to the robot's chat automatically:
106
+
107
+ **LLM Configuration:**
108
+
109
+ | Key | Description |
110
+ |-----|-------------|
111
+ | `model` | Override the LLM model |
112
+ | `temperature` | Controls randomness (0.0 - 1.0) |
113
+ | `top_p` | Nucleus sampling threshold |
114
+ | `top_k` | Top-k sampling |
115
+ | `max_tokens` | Maximum tokens in response |
116
+ | `presence_penalty` | Penalize based on presence |
117
+ | `frequency_penalty` | Penalize based on frequency |
118
+ | `stop` | Stop sequences |
119
+
120
+ **Robot Identity and Capabilities:**
121
+
122
+ | Key | Description |
123
+ |-----|-------------|
124
+ | `robot_name` | Override the robot's name (when constructor uses the default) |
125
+ | `description` | Human-readable description of the robot |
126
+ | `tools` | Array of tool class names (resolved via `Object.const_get`) |
127
+ | `mcp` | Array of MCP server configurations |
128
+
129
+ Constructor-provided values always take precedence over frontmatter values.
130
+
131
+ ### Self-Contained Templates
132
+
133
+ Templates can declare everything a robot needs — identity, tools, MCP servers, and LLM config — making the `.md` file a complete robot definition:
134
+
135
+ ```markdown title="prompts/github_assistant.md"
136
+ ---
137
+ description: GitHub assistant with MCP tool access
138
+ robot_name: github_bot
139
+ mcp:
140
+ - name: github
141
+ transport: stdio
142
+ command: npx
143
+ args: ["-y", "@modelcontextprotocol/server-github"]
144
+ model: claude-sonnet-4
145
+ temperature: 0.3
146
+ ---
147
+ You are a helpful GitHub assistant with access to GitHub tools via MCP.
148
+ Use the available tools to help answer questions about GitHub repositories.
149
+ ```
90
150
 
91
- Define parameters with types and descriptions:
151
+ Build the robot with minimal constructor arguments:
92
152
 
93
153
  ```ruby
94
- tool :search do
95
- parameter :query, type: :string, required: true, description: "Search query"
96
- parameter :limit, type: :integer, default: 10, description: "Max results"
97
- parameter :category, type: :string, enum: %w[books movies music]
98
- end
154
+ # Template provides name, description, MCP config, model, and temperature
155
+ robot = RobotLab.build(template: :github_assistant)
99
156
  ```
100
157
 
101
- | Option | Description |
102
- |--------|-------------|
103
- | `type` | Parameter type (`:string`, `:integer`, `:boolean`, `:number`, `:array`, `:object`) |
104
- | `required` | Whether the parameter is required |
105
- | `default` | Default value if not provided |
106
- | `description` | Description for the LLM |
107
- | `enum` | List of allowed values |
158
+ ### Tools in Front Matter
108
159
 
109
- ### Tool Handler Context
160
+ Declare tool classes by name in the `tools:` key. RobotLab resolves each string to a Ruby constant and instantiates it:
110
161
 
111
- Handlers receive execution context:
162
+ ```markdown title="prompts/order_support.md"
163
+ ---
164
+ description: Order support specialist
165
+ tools:
166
+ - OrderLookup
167
+ - RefundProcessor
168
+ ---
169
+ You help customers with order inquiries and refunds.
170
+ ```
112
171
 
113
172
  ```ruby
114
- handler do |param1:, param2:, robot:, network:, state:|
115
- # robot - The Robot instance
116
- # network - The NetworkRun
117
- # state - Current State
173
+ # Tools are loaded from frontmatter — no local_tools: needed
174
+ robot = RobotLab.build(template: :order_support)
175
+ ```
118
176
 
119
- # Access state data
120
- user_id = state.data[:user_id]
177
+ Tool classes must be defined and loaded before the robot is built. If a tool name cannot be resolved, it is skipped with a warning.
121
178
 
122
- # Use memory
123
- state.memory.remember("last_search", param1)
179
+ Constructor `local_tools:` overrides frontmatter `tools:` when provided:
124
180
 
125
- # Return result (will be sent to LLM)
126
- { success: true, data: result }
127
- end
181
+ ```ruby
182
+ # Constructor tools take precedence over frontmatter tools
183
+ robot = RobotLab.build(
184
+ template: :order_support,
185
+ local_tools: [OrderLookup] # Only OrderLookup, not RefundProcessor
186
+ )
128
187
  ```
129
188
 
130
- !!! tip "Ignoring Context"
131
- Use `**_context` to accept but ignore context:
132
- ```ruby
133
- handler { |query:, **_context| search(query) }
134
- ```
189
+ ### MCP in Front Matter
135
190
 
136
- ## Template Files
191
+ Declare MCP server configurations directly in the template:
137
192
 
138
- Load templates from files:
193
+ ```markdown title="prompts/developer.md"
194
+ ---
195
+ description: Developer assistant with filesystem access
196
+ mcp:
197
+ - name: filesystem
198
+ transport: stdio
199
+ command: mcp-server-filesystem
200
+ args: ["--root", "/home/user/projects"]
201
+ ---
202
+ You are a developer assistant with filesystem access.
203
+ ```
139
204
 
140
205
  ```ruby
141
- # Configure template path
142
- RobotLab.configure do |config|
143
- config.template_path = "prompts" # or "app/prompts" in Rails
144
- end
145
-
146
- # Reference template by name
147
- robot = RobotLab.build do
148
- name "support"
149
- template "support_agent" # Loads prompts/support_agent.erb
150
- end
206
+ robot = RobotLab.build(template: :developer)
151
207
  ```
152
208
 
153
- ### Template Variables
209
+ Constructor `mcp:` overrides frontmatter `mcp:` when provided.
154
210
 
155
- Pass variables to templates:
211
+ ### Template with System Prompt
212
+
213
+ You can combine a template and an inline system prompt. Both are applied to the chat -- the template first, then the system prompt is appended as additional instructions:
156
214
 
157
215
  ```ruby
158
- robot = RobotLab.build do
159
- name "support"
160
- template "support_agent", company: "TechCo", tone: "friendly"
161
- end
216
+ robot = RobotLab.build(
217
+ name: "support",
218
+ template: :support,
219
+ context: { company: "TechCo" },
220
+ system_prompt: "Always respond in Spanish."
221
+ )
162
222
  ```
163
223
 
164
- ```erb title="prompts/support_agent.erb"
165
- You are a support agent for <%= company %>.
166
- Your tone should be <%= tone %>.
224
+ ## Adding Tools
225
+
226
+ Give robots capabilities via the `local_tools:` parameter. Tools can be `RubyLLM::Tool` subclasses or `RobotLab::Tool` instances:
227
+
228
+ ```ruby
229
+ robot = RobotLab.build(
230
+ name: "order_assistant",
231
+ system_prompt: "You help customers with orders.",
232
+ local_tools: [OrderLookup, InventoryCheck]
233
+ )
167
234
  ```
168
235
 
236
+ See the [Using Tools](using-tools.md) guide for details on defining tools.
237
+
169
238
  ## MCP Configuration
170
239
 
171
- Connect to MCP servers:
240
+ Connect to MCP (Model Context Protocol) servers via the `mcp:` parameter:
172
241
 
173
242
  ```ruby
174
- robot = RobotLab.build do
175
- name "coder"
176
-
177
- # Use specific MCP servers
178
- mcp [
243
+ robot = RobotLab.build(
244
+ name: "coder",
245
+ template: :developer,
246
+ mcp: [
179
247
  {
180
248
  name: "filesystem",
181
249
  transport: { type: "stdio", command: "mcp-server-fs", args: ["--root", "/data"] }
182
250
  }
183
251
  ]
252
+ )
253
+ ```
184
254
 
185
- # Or inherit from network
186
- mcp :inherit
255
+ MCP configuration supports hierarchical resolution:
187
256
 
188
- # Or disable MCP
189
- mcp :none
190
- end
191
- ```
257
+ | Value | Behavior |
258
+ |-------|----------|
259
+ | `:none` | No MCP servers (default) |
260
+ | `:inherit` | Use parent network/config MCP servers |
261
+ | `[...]` | Explicit array of server configurations |
262
+
263
+ See the [MCP Integration](mcp-integration.md) guide for transport types and advanced patterns.
192
264
 
193
- ## Tool Whitelist
265
+ ## Chaining Configuration
194
266
 
195
- Restrict available tools:
267
+ Robots support `with_*` method chaining for runtime reconfiguration. Each method returns `self` for fluent usage:
196
268
 
197
269
  ```ruby
198
- robot = RobotLab.build do
199
- name "reader"
270
+ robot = RobotLab.build(name: "bot")
200
271
 
201
- # Only allow specific tools
202
- tools %w[read_file list_directory]
272
+ result = robot
273
+ .with_instructions("Be concise and direct.")
274
+ .with_temperature(0.9)
275
+ .with_model("claude-sonnet-4")
276
+ .run("Summarize quantum computing in one sentence.")
277
+ ```
203
278
 
204
- # Or inherit from network
205
- tools :inherit
279
+ ### Available Chain Methods
206
280
 
207
- # Or disable all inherited tools
208
- tools :none
209
- end
210
- ```
281
+ | Method | Description |
282
+ |--------|-------------|
283
+ | `with_model(id)` | Change the LLM model |
284
+ | `with_instructions(text)` | Set system instructions |
285
+ | `with_temperature(val)` | Set temperature |
286
+ | `with_top_p(val)` | Set nucleus sampling |
287
+ | `with_top_k(val)` | Set top-k sampling |
288
+ | `with_max_tokens(val)` | Set max output tokens |
289
+ | `with_presence_penalty(val)` | Set presence penalty |
290
+ | `with_frequency_penalty(val)` | Set frequency penalty |
291
+ | `with_stop(sequences)` | Set stop sequences |
292
+ | `with_tool(tool)` | Add a single tool |
293
+ | `with_tools(*tools)` | Add multiple tools |
294
+ | `with_template(id, **ctx)` | Apply a prompt template |
295
+ | `with_schema(schema)` | Set structured output schema |
296
+ | `with_thinking(config)` | Enable extended thinking |
297
+ | `with_bus(bus)` | Connect to a message bus (creates one if nil) |
211
298
 
212
299
  ## Running Robots
213
300
 
214
301
  ### Standalone
215
302
 
216
- Run a robot directly:
303
+ Run a robot directly with a string message:
304
+
305
+ ```ruby
306
+ result = robot.run("Hello!")
307
+ puts result.last_text_content
308
+ ```
309
+
310
+ The `run` method returns a `RobotResult` with:
217
311
 
218
312
  ```ruby
219
- state = RobotLab.create_state(message: "Hello!")
220
- result = robot.run(state: state, network: nil)
313
+ result.last_text_content # => "Hi there! How can I help?"
314
+ result.output # => Array of output messages
315
+ result.tool_calls # => Array of tool call results
316
+ result.robot_name # => "assistant"
317
+ result.stop_reason # => stop reason from the LLM
318
+ ```
319
+
320
+ ### With Runtime Memory
221
321
 
222
- puts result.output.first.content
322
+ Inject memory values for a single run:
323
+
324
+ ```ruby
325
+ result = robot.run("What's my account status?", memory: { user_id: 123 })
223
326
  ```
224
327
 
225
328
  ### In a Network
226
329
 
227
- Run through a network for full orchestration:
330
+ Run through a network for orchestration:
228
331
 
229
332
  ```ruby
230
- network = RobotLab.create_network do
231
- add_robot robot
333
+ network = RobotLab.create_network(name: "pipeline") do
334
+ task :assistant, robot, depends_on: :none
232
335
  end
233
336
 
234
- state = RobotLab.create_state(message: "Hello!")
235
- result = network.run(state: state)
337
+ result = network.run(message: "Hello!")
338
+ puts result.value.last_text_content
236
339
  ```
237
340
 
238
341
  ### With Streaming
239
342
 
240
- Stream responses in real-time:
343
+ Stream responses in real-time by passing a block:
241
344
 
242
345
  ```ruby
243
- robot.run(
244
- state: state,
245
- network: network,
246
- streaming: ->(event) {
247
- case event[:event]
248
- when "delta"
249
- print event[:data][:content]
250
- when "tool_call"
251
- puts "\nCalling tool: #{event[:data][:name]}"
252
- end
253
- }
254
- )
346
+ robot.run("Tell me a story") do |event|
347
+ case event[:event]
348
+ when "text.delta"
349
+ print event[:data][:content]
350
+ when "tool_call"
351
+ puts "\nCalling tool: #{event[:data][:name]}"
352
+ end
353
+ end
255
354
  ```
256
355
 
257
356
  ## Robot Patterns
258
357
 
259
358
  ### Classifier Robot
260
359
 
261
- Route requests to specialized handlers:
360
+ Route requests to specialized handlers. Subclass `RobotLab::Robot` and override `call` for custom pipeline behavior:
262
361
 
263
362
  ```ruby
264
- classifier = RobotLab.build do
265
- name "classifier"
266
- description "Classifies incoming requests"
267
-
268
- template <<~PROMPT
269
- Analyze the user's message and classify it into exactly one category:
270
- - BILLING: Questions about invoices, payments, subscriptions
271
- - TECHNICAL: Technical issues, bugs, how-to questions
272
- - GENERAL: General inquiries, feedback, other
363
+ class ClassifierRobot < RobotLab::Robot
364
+ def call(result)
365
+ context = extract_run_context(result)
366
+ message = context.delete(:message)
367
+ robot_result = run(message, **context)
368
+
369
+ new_result = result
370
+ .with_context(@name.to_sym, robot_result)
371
+ .continue(robot_result)
372
+
373
+ category = robot_result.last_text_content.to_s.strip.downcase
374
+
375
+ case category
376
+ when /billing/ then new_result.activate(:billing)
377
+ when /technical/ then new_result.activate(:technical)
378
+ else new_result.activate(:general)
379
+ end
380
+ end
381
+ end
273
382
 
383
+ classifier = ClassifierRobot.new(
384
+ name: "classifier",
385
+ system_prompt: <<~PROMPT
386
+ Classify the user's message into exactly one category:
387
+ - billing
388
+ - technical
389
+ - general
274
390
  Respond with only the category name, nothing else.
275
391
  PROMPT
276
- end
392
+ )
277
393
  ```
278
394
 
279
395
  ### Specialist Robot
280
396
 
281
- Handle specific domains:
397
+ Handle specific domains with template and tools:
282
398
 
283
399
  ```ruby
284
- billing_specialist = RobotLab.build do
285
- name "billing_specialist"
286
- description "Handles billing and payment inquiries"
400
+ billing_specialist = RobotLab.build(
401
+ name: "billing_specialist",
402
+ description: "Handles billing and payment inquiries",
403
+ template: :billing,
404
+ context: { department: "billing" },
405
+ local_tools: [InvoiceLookup, RefundProcessor]
406
+ )
407
+ ```
287
408
 
288
- template <<~PROMPT
289
- You are a billing specialist. You help customers with:
290
- - Invoice questions
291
- - Payment issues
292
- - Subscription management
409
+ ### Summarizer Robot
293
410
 
294
- Always verify the customer's account before making changes.
411
+ Condense information:
412
+
413
+ ```ruby
414
+ summarizer = RobotLab.build(
415
+ name: "summarizer",
416
+ description: "Summarizes conversations and documents",
417
+ system_prompt: <<~PROMPT
418
+ Create concise summaries of the provided content.
419
+ Focus on key points and actionable items.
420
+ Use bullet points for clarity.
295
421
  PROMPT
422
+ )
423
+ ```
296
424
 
297
- tool :get_invoices do
298
- description "Get customer's recent invoices"
299
- parameter :customer_id, type: :string, required: true
300
- handler { |customer_id:, **_| Invoice.where(customer_id: customer_id).limit(10) }
425
+ ### Bus-Connected Robot
426
+
427
+ Enable bidirectional communication between robots using a message bus. This pattern supports negotiation loops and convergence:
428
+
429
+ ```ruby
430
+ bus = TypedBus::MessageBus.new
431
+
432
+ class Comedian < RobotLab::Robot
433
+ def initialize(bus:)
434
+ super(name: "bob", template: :comedian, bus: bus)
435
+ on_message do |message|
436
+ joke = run(message.content.to_s).last_text_content.strip
437
+ reply(message, joke)
438
+ end
439
+ end
440
+ end
441
+
442
+ class ComedyCritic < RobotLab::Robot
443
+ def initialize(bus:)
444
+ super(name: "alice", template: :comedy_critic, bus: bus)
445
+ @accepted = false
446
+ on_message do |message|
447
+ verdict = run("Evaluate: #{message.content}").last_text_content.strip
448
+ @accepted = verdict.start_with?("FUNNY")
449
+ send_message(to: :bob, content: "Try again.") unless @accepted
450
+ end
301
451
  end
452
+ attr_reader :accepted
302
453
  end
454
+
455
+ bob = Comedian.new(bus: bus)
456
+ alice = ComedyCritic.new(bus: bus)
457
+ alice.send_message(to: :bob, content: "Tell me a funny robot joke.")
303
458
  ```
304
459
 
305
- ### Summarizer Robot
460
+ The `on_message` block arity controls delivery handling:
461
+ - **1 argument** `|message|` — auto-acknowledges before calling
462
+ - **2 arguments** `|delivery, message|` — manual `delivery.ack!` / `delivery.nack!`
306
463
 
307
- Condense information:
464
+ See [Message Bus](../architecture/core-concepts.md#message-bus) for details.
465
+
466
+ ### Spawning Robots Dynamically
467
+
468
+ Create new robots at runtime using `spawn`. The bus is created lazily — no upfront wiring required:
308
469
 
309
470
  ```ruby
310
- summarizer = RobotLab.build do
311
- name "summarizer"
312
- description "Summarizes conversations and documents"
471
+ class Dispatcher < RobotLab::Robot
472
+ attr_reader :spawned
313
473
 
314
- template <<~PROMPT
315
- Create concise summaries of the provided content.
316
- Focus on key points and actionable items.
317
- Use bullet points for clarity.
318
- PROMPT
474
+ def initialize(bus: nil)
475
+ super(name: "dispatcher", template: :dispatcher, bus: bus)
476
+ @spawned = {}
477
+
478
+ on_message do |message|
479
+ puts "#{message.from} replied: #{message.content.to_s.lines.first&.strip}"
480
+ end
481
+ end
482
+
483
+ def dispatch(question)
484
+ # Ask LLM what specialist to create
485
+ plan = run(question).last_text_content.strip
486
+ role, instruction = plan.split("\n", 2)
487
+ role = role.strip.downcase.gsub(/\s+/, "_")
488
+
489
+ # Spawn (or reuse) a specialist
490
+ specialist = @spawned[role] ||= spawn(
491
+ name: role,
492
+ system_prompt: instruction&.strip || "You are a helpful #{role}."
493
+ )
494
+
495
+ # Have the specialist answer and reply
496
+ answer = specialist.run(question).last_text_content.strip
497
+ specialist.send_message(to: :dispatcher, content: answer)
498
+ end
319
499
  end
320
500
  ```
321
501
 
322
- ## Best Practices
502
+ Key features of `spawn`:
503
+
504
+ - Creates a child robot on the same bus as the parent
505
+ - Creates a bus lazily if the parent doesn't have one
506
+ - Spawned robots can immediately send and receive messages
507
+ - Multiple robots with the same name enable fan-out messaging
323
508
 
324
- ### 1. Clear, Focused Templates
509
+ Robots can also join a bus after creation:
325
510
 
326
511
  ```ruby
327
- # Good: Specific and focused
328
- template <<~PROMPT
329
- You are a code reviewer. Review code for:
330
- - Security vulnerabilities
331
- - Performance issues
332
- - Best practice violations
512
+ bot = RobotLab.build(name: "latecomer", system_prompt: "Hello.")
513
+ bot.with_bus(existing_bus) # now connected and can send/receive messages
514
+ ```
333
515
 
334
- Provide specific line numbers and suggestions.
335
- PROMPT
516
+ ## Configuration
336
517
 
337
- # Bad: Vague and unfocused
338
- template "You help with code stuff."
518
+ RobotLab uses `MywayConfig` for configuration. Access the config object directly -- there is no `RobotLab.configure` block:
519
+
520
+ ```ruby
521
+ RobotLab.config.ruby_llm.model # => "claude-sonnet-4"
522
+ RobotLab.config.ruby_llm.request_timeout # => 120
339
523
  ```
340
524
 
341
- ### 2. Descriptive Tool Definitions
525
+ Configuration is loaded from:
526
+
527
+ - Bundled defaults (`lib/robot_lab/config/defaults.yml`)
528
+ - Environment-specific overrides (development, test, production)
529
+ - XDG config files (`~/.config/robot_lab/config.yml`)
530
+ - Project config (`./config/robot_lab.yml`)
531
+ - Environment variables (`ROBOT_LAB_*` prefix)
532
+
533
+ ## Best Practices
534
+
535
+ ### 1. Clear, Focused Prompts
342
536
 
343
537
  ```ruby
344
- # Good: Clear description and parameter docs
345
- tool :search_users do
346
- description "Search for users by email, name, or ID. Returns up to 10 matches."
347
- parameter :query, type: :string, required: true,
348
- description: "Email address, partial name, or user ID"
349
- end
538
+ # Good: Specific and focused
539
+ robot = RobotLab.build(
540
+ name: "reviewer",
541
+ system_prompt: <<~PROMPT
542
+ You are a code reviewer. Review code for:
543
+ - Security vulnerabilities
544
+ - Performance issues
545
+ - Best practice violations
546
+
547
+ Provide specific line numbers and suggestions.
548
+ PROMPT
549
+ )
350
550
 
351
- # Bad: Missing context
352
- tool :search do
353
- parameter :q, type: :string
354
- end
551
+ # Bad: Vague and unfocused
552
+ robot = RobotLab.build(
553
+ name: "reviewer",
554
+ system_prompt: "You help with code stuff."
555
+ )
355
556
  ```
356
557
 
357
- ### 3. Handle Tool Errors Gracefully
558
+ ### 2. Use Templates for Reusable Prompts
559
+
560
+ Templates keep prompts in version-controlled files and allow parameterization:
358
561
 
359
562
  ```ruby
360
- handler do |user_id:, **_|
361
- user = User.find_by(id: user_id)
362
- if user
363
- { success: true, user: user.to_h }
364
- else
365
- { success: false, error: "User not found", user_id: user_id }
366
- end
367
- rescue ActiveRecord::ConnectionError => e
368
- { success: false, error: "Database unavailable", retry: true }
369
- end
563
+ robot = RobotLab.build(
564
+ name: "support",
565
+ template: :support,
566
+ context: { company: "TechCo", language: "English" }
567
+ )
370
568
  ```
371
569
 
570
+ ### 3. Handle Tool Errors Gracefully
571
+
572
+ See [Using Tools: Error Handling](using-tools.md#error-handling) for patterns.
573
+
372
574
  ## Next Steps
373
575
 
374
576
  - [Creating Networks](creating-networks.md) - Orchestrate multiple robots
577
+ - [Message Bus](../architecture/core-concepts.md#message-bus) - Bidirectional robot communication
578
+ - [Dynamic Spawning](../architecture/core-concepts.md#dynamic-spawning) - Robots creating robots at runtime
375
579
  - [Using Tools](using-tools.md) - Advanced tool patterns
580
+ - [Memory Guide](memory.md) - Share data between runs and robots
376
581
  - [API Reference: Robot](../api/core/robot.md) - Complete API documentation