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
@@ -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,552 @@ 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.
210
+
211
+ ### Template with System Prompt
154
212
 
155
- Pass variables to templates:
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 |
192
262
 
193
- ## Tool Whitelist
263
+ See the [MCP Integration](mcp-integration.md) guide for transport types and advanced patterns.
194
264
 
195
- Restrict available tools:
265
+ ## Chaining Configuration
266
+
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
+ ```
221
319
 
222
- puts result.output.first.content
320
+ ### With Runtime Memory
321
+
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 registering callbacks before calling `run`:
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.on_new_message do |message|
347
+ print message.content if message.content
348
+ end
349
+
350
+ robot.on_tool_call do |tool_call|
351
+ puts "\nCalling tool: #{tool_call.name}"
352
+ end
353
+
354
+ result = robot.run("Tell me a story")
255
355
  ```
256
356
 
257
357
  ## Robot Patterns
258
358
 
259
359
  ### Classifier Robot
260
360
 
261
- Route requests to specialized handlers:
361
+ Route requests to specialized handlers. Subclass `RobotLab::Robot` and override `call` for custom pipeline behavior:
262
362
 
263
363
  ```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
364
+ class ClassifierRobot < RobotLab::Robot
365
+ def call(result)
366
+ context = extract_run_context(result)
367
+ message = context.delete(:message)
368
+ robot_result = run(message, **context)
369
+
370
+ new_result = result
371
+ .with_context(@name.to_sym, robot_result)
372
+ .continue(robot_result)
373
+
374
+ category = robot_result.last_text_content.to_s.strip.downcase
375
+
376
+ case category
377
+ when /billing/ then new_result.activate(:billing)
378
+ when /technical/ then new_result.activate(:technical)
379
+ else new_result.activate(:general)
380
+ end
381
+ end
382
+ end
273
383
 
384
+ classifier = ClassifierRobot.new(
385
+ name: "classifier",
386
+ system_prompt: <<~PROMPT
387
+ Classify the user's message into exactly one category:
388
+ - billing
389
+ - technical
390
+ - general
274
391
  Respond with only the category name, nothing else.
275
392
  PROMPT
276
- end
393
+ )
277
394
  ```
278
395
 
279
396
  ### Specialist Robot
280
397
 
281
- Handle specific domains:
398
+ Handle specific domains with template and tools:
282
399
 
283
400
  ```ruby
284
- billing_specialist = RobotLab.build do
285
- name "billing_specialist"
286
- description "Handles billing and payment inquiries"
401
+ billing_specialist = RobotLab.build(
402
+ name: "billing_specialist",
403
+ description: "Handles billing and payment inquiries",
404
+ template: :billing,
405
+ context: { department: "billing" },
406
+ local_tools: [InvoiceLookup, RefundProcessor]
407
+ )
408
+ ```
287
409
 
288
- template <<~PROMPT
289
- You are a billing specialist. You help customers with:
290
- - Invoice questions
291
- - Payment issues
292
- - Subscription management
410
+ ### Summarizer Robot
411
+
412
+ Condense information:
293
413
 
294
- Always verify the customer's account before making changes.
414
+ ```ruby
415
+ summarizer = RobotLab.build(
416
+ name: "summarizer",
417
+ description: "Summarizes conversations and documents",
418
+ system_prompt: <<~PROMPT
419
+ Create concise summaries of the provided content.
420
+ Focus on key points and actionable items.
421
+ Use bullet points for clarity.
295
422
  PROMPT
423
+ )
424
+ ```
425
+
426
+ ### Bus-Connected Robot
427
+
428
+ Enable bidirectional communication between robots using a message bus. This pattern supports negotiation loops and convergence:
429
+
430
+ ```ruby
431
+ bus = TypedBus::MessageBus.new
432
+
433
+ class Comedian < RobotLab::Robot
434
+ def initialize(bus:)
435
+ super(name: "bob", template: :comedian, bus: bus)
436
+ on_message do |message|
437
+ joke = run(message.content.to_s).last_text_content.strip
438
+ send_reply(to: message.from.to_sym, content: joke, in_reply_to: message.key)
439
+ end
440
+ end
441
+ end
296
442
 
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) }
443
+ class ComedyCritic < RobotLab::Robot
444
+ def initialize(bus:)
445
+ super(name: "alice", template: :comedy_critic, bus: bus)
446
+ @accepted = false
447
+ on_message do |message|
448
+ verdict = run("Evaluate: #{message.content}").last_text_content.strip
449
+ @accepted = verdict.start_with?("FUNNY")
450
+ send_message(to: :bob, content: "Try again.") unless @accepted
451
+ end
301
452
  end
453
+ attr_reader :accepted
302
454
  end
455
+
456
+ bob = Comedian.new(bus: bus)
457
+ alice = ComedyCritic.new(bus: bus)
458
+ alice.send_message(to: :bob, content: "Tell me a funny robot joke.")
303
459
  ```
304
460
 
305
- ### Summarizer Robot
461
+ The `on_message` block arity controls delivery handling:
462
+ - **1 argument** `|message|` — auto-acknowledges before calling
463
+ - **2 arguments** `|delivery, message|` — manual `delivery.ack!` / `delivery.nack!`
306
464
 
307
- Condense information:
465
+ See [Message Bus](../architecture/core-concepts.md#message-bus) for details.
466
+
467
+ ### Spawning Robots Dynamically
468
+
469
+ Create new robots at runtime using `spawn`. The bus is created lazily — no upfront wiring required:
308
470
 
309
471
  ```ruby
310
- summarizer = RobotLab.build do
311
- name "summarizer"
312
- description "Summarizes conversations and documents"
472
+ class Dispatcher < RobotLab::Robot
473
+ attr_reader :spawned
313
474
 
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
475
+ def initialize(bus: nil)
476
+ super(name: "dispatcher", template: :dispatcher, bus: bus)
477
+ @spawned = {}
478
+
479
+ on_message do |message|
480
+ puts "#{message.from} replied: #{message.content.to_s.lines.first&.strip}"
481
+ end
482
+ end
483
+
484
+ def dispatch(question)
485
+ # Ask LLM what specialist to create
486
+ plan = run(question).last_text_content.strip
487
+ role, instruction = plan.split("\n", 2)
488
+ role = role.strip.downcase.gsub(/\s+/, "_")
489
+
490
+ # Spawn (or reuse) a specialist
491
+ specialist = @spawned[role] ||= spawn(
492
+ name: role,
493
+ system_prompt: instruction&.strip || "You are a helpful #{role}."
494
+ )
495
+
496
+ # Have the specialist answer and reply
497
+ answer = specialist.run(question).last_text_content.strip
498
+ specialist.send_message(to: :dispatcher, content: answer)
499
+ end
319
500
  end
320
501
  ```
321
502
 
322
- ## Best Practices
503
+ Key features of `spawn`:
504
+
505
+ - Creates a child robot on the same bus as the parent
506
+ - Creates a bus lazily if the parent doesn't have one
507
+ - Spawned robots can immediately send and receive messages
508
+ - Multiple robots with the same name enable fan-out messaging
323
509
 
324
- ### 1. Clear, Focused Templates
510
+ Robots can also join a bus after creation:
325
511
 
326
512
  ```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
513
+ bot = RobotLab.build(name: "latecomer", system_prompt: "Hello.")
514
+ bot.with_bus(existing_bus) # now connected and can send/receive messages
515
+ ```
333
516
 
334
- Provide specific line numbers and suggestions.
335
- PROMPT
517
+ ## Configuration
336
518
 
337
- # Bad: Vague and unfocused
338
- template "You help with code stuff."
519
+ RobotLab uses `MywayConfig` for configuration. Access the config object directly -- there is no `RobotLab.configure` block:
520
+
521
+ ```ruby
522
+ RobotLab.config.ruby_llm.model # => "claude-sonnet-4"
523
+ RobotLab.config.ruby_llm.request_timeout # => 120
339
524
  ```
340
525
 
341
- ### 2. Descriptive Tool Definitions
526
+ Configuration is loaded from:
527
+
528
+ - Bundled defaults (`lib/robot_lab/config/defaults.yml`)
529
+ - Environment-specific overrides (development, test, production)
530
+ - XDG config files (`~/.config/robot_lab/config.yml`)
531
+ - Project config (`./config/robot_lab.yml`)
532
+ - Environment variables (`ROBOT_LAB_*` prefix)
533
+
534
+ ## Best Practices
535
+
536
+ ### 1. Clear, Focused Prompts
342
537
 
343
538
  ```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
539
+ # Good: Specific and focused
540
+ robot = RobotLab.build(
541
+ name: "reviewer",
542
+ system_prompt: <<~PROMPT
543
+ You are a code reviewer. Review code for:
544
+ - Security vulnerabilities
545
+ - Performance issues
546
+ - Best practice violations
547
+
548
+ Provide specific line numbers and suggestions.
549
+ PROMPT
550
+ )
350
551
 
351
- # Bad: Missing context
352
- tool :search do
353
- parameter :q, type: :string
354
- end
552
+ # Bad: Vague and unfocused
553
+ robot = RobotLab.build(
554
+ name: "reviewer",
555
+ system_prompt: "You help with code stuff."
556
+ )
355
557
  ```
356
558
 
357
- ### 3. Handle Tool Errors Gracefully
559
+ ### 2. Use Templates for Reusable Prompts
560
+
561
+ Templates keep prompts in version-controlled files and allow parameterization:
358
562
 
359
563
  ```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
564
+ robot = RobotLab.build(
565
+ name: "support",
566
+ template: :support,
567
+ context: { company: "TechCo", language: "English" }
568
+ )
370
569
  ```
371
570
 
571
+ ### 3. Handle Tool Errors Gracefully
572
+
573
+ See [Using Tools: Error Handling](using-tools.md#error-handling) for patterns.
574
+
372
575
  ## Next Steps
373
576
 
374
577
  - [Creating Networks](creating-networks.md) - Orchestrate multiple robots
578
+ - [Message Bus](../architecture/core-concepts.md#message-bus) - Bidirectional robot communication
579
+ - [Dynamic Spawning](../architecture/core-concepts.md#dynamic-spawning) - Robots creating robots at runtime
375
580
  - [Using Tools](using-tools.md) - Advanced tool patterns
581
+ - [Memory Guide](memory.md) - Share data between runs and robots
376
582
  - [API Reference: Robot](../api/core/robot.md) - Complete API documentation