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,87 +4,159 @@ This page provides an in-depth look at RobotLab's fundamental building blocks.
4
4
 
5
5
  ## Robot
6
6
 
7
- A Robot is the primary unit of computation in RobotLab. It wraps an LLM with:
7
+ A Robot is the primary unit of computation in RobotLab. It is a subclass of `RubyLLM::Agent` that wraps a persistent `@chat` with:
8
8
 
9
9
  - A unique identity (name, description)
10
- - A personality (system prompt/template)
10
+ - A personality (system prompt and/or template)
11
11
  - Capabilities (tools, MCP connections)
12
- - A specific model configuration
12
+ - Model and inference configuration
13
+ - Inherent memory (key-value store)
13
14
 
14
15
  ### Robot Anatomy
15
16
 
16
17
  ```ruby
17
- robot = RobotLab.build do
18
- name "support_agent" # Unique identifier
19
- description "Handles support requests" # Used for routing hints
20
- model "claude-sonnet-4" # LLM model
21
-
22
- # System prompt - defines personality
23
- template <<~PROMPT
18
+ robot = RobotLab.build(
19
+ name: "support_agent", # Unique identifier
20
+ description: "Handles support requests", # Used for routing hints
21
+ model: "claude-sonnet-4", # LLM model
22
+ system_prompt: <<~PROMPT, # Inline system prompt
24
23
  You are a friendly customer support agent for Acme Corp.
25
24
  Always be polite and helpful. If you don't know something,
26
25
  say so honestly.
27
26
  PROMPT
27
+ local_tools: [OrderLookup, RefundProcessor], # RubyLLM::Tool subclasses
28
+ mcp: :inherit, # Use network's MCP servers
29
+ temperature: 0.7 # Inference parameter
30
+ )
31
+ ```
28
32
 
29
- # Tools - extend capabilities
30
- tool :lookup_account do
31
- description "Look up customer account"
32
- parameter :email, type: :string, required: true
33
- handler { |email:, **_| Account.find_by_email(email)&.to_h }
34
- end
33
+ Or with a template:
35
34
 
36
- # MCP - external tool servers
37
- mcp :inherit # Use network's MCP servers
38
- end
35
+ ```ruby
36
+ robot = RobotLab.build(
37
+ name: "support_agent",
38
+ template: :support, # Loads prompts/support.md
39
+ context: { company: "Acme Corp" }, # Template variables
40
+ local_tools: [OrderLookup]
41
+ )
39
42
  ```
40
43
 
41
44
  ### Robot Lifecycle
42
45
 
43
46
  ```mermaid
44
47
  stateDiagram-v2
45
- [*] --> Created: RobotLab.build
46
- Created --> Running: robot.run(state)
47
- Running --> ToolExecution: tool_call
48
- ToolExecution --> Running: result
49
- Running --> Completed: stop_reason
50
- Completed --> [*]
48
+ [*] --> Created: RobotLab.build / Robot.new
49
+ Created --> Running: robot.run("message")
50
+ Running --> ToolLoop: tool_call from LLM
51
+ ToolLoop --> Running: tool result sent back
52
+ Running --> Completed: final text response
53
+ Completed --> Running: robot.run("next message")
54
+ Completed --> [*]: robot.disconnect
51
55
  ```
52
56
 
57
+ The persistent `@chat` maintains conversation history across multiple `run` calls, making the robot stateful.
58
+
53
59
  ### Robot Properties
54
60
 
55
61
  | Property | Type | Description |
56
62
  |----------|------|-------------|
57
- | `name` | String | Unique identifier within network |
58
- | `description` | String | What the robot does |
59
- | `model` | String | LLM model to use |
60
- | `template` | String | System prompt |
61
- | `local_tools` | Array | Defined tools |
62
- | `mcp_clients` | Array | Connected MCP clients |
63
+ | `name` | `String` | Unique identifier within network |
64
+ | `description` | `String`, `nil` | What the robot does |
65
+ | `model` | `String` | LLM model ID (resolved from chat) |
66
+ | `template` | `Symbol`, `nil` | Prompt template identifier |
67
+ | `system_prompt` | `String`, `nil` | Inline system prompt |
68
+ | `local_tools` | `Array` | Locally defined tools |
69
+ | `mcp_clients` | `Hash` | Connected MCP clients by server name |
70
+ | `mcp_tools` | `Array` | Tools discovered from MCP servers |
71
+ | `memory` | `Memory` | Inherent key-value memory |
72
+ | `bus` | `TypedBus::MessageBus`, `nil` | Message bus instance |
73
+ | `outbox` | `Hash` | Sent messages tracked with status and replies |
74
+ | `mcp_config` | `Symbol`, `Array` | Build-time MCP configuration |
75
+ | `tools_config` | `Symbol`, `Array` | Build-time tools configuration |
76
+
77
+ ### Running a Robot
78
+
79
+ The primary method is `robot.run("message")`:
80
+
81
+ ```ruby
82
+ result = robot.run("What is the weather in Berlin?")
83
+ puts result.last_text_content
84
+ ```
85
+
86
+ With runtime overrides:
87
+
88
+ ```ruby
89
+ result = robot.run("Analyze this",
90
+ memory: { data: report },
91
+ mcp: :none,
92
+ tools: :none
93
+ )
94
+ ```
95
+
96
+ With streaming:
97
+
98
+ ```ruby
99
+ robot.run("Tell me a story") do |event|
100
+ print event.text if event.respond_to?(:text)
101
+ end
102
+ ```
63
103
 
64
104
  ## Tool
65
105
 
66
- Tools give robots the ability to interact with external systems.
106
+ Tools give robots the ability to interact with external systems. There are two patterns for defining tools.
67
107
 
68
- ### Tool Structure
108
+ ### RubyLLM::Tool Subclass (Primary)
109
+
110
+ ```ruby
111
+ class GetWeather < RubyLLM::Tool
112
+ description "Get current weather for a location"
113
+
114
+ param :location, type: "string", desc: "City name"
115
+ param :unit, type: "string", desc: "celsius or fahrenheit"
116
+
117
+ def execute(location:, unit: "celsius")
118
+ WeatherAPI.current(location, unit: unit)
119
+ end
120
+ end
121
+ ```
122
+
123
+ Tools defined as `RubyLLM::Tool` subclasses are passed to robots via `local_tools:`:
124
+
125
+ ```ruby
126
+ robot = RobotLab.build(
127
+ name: "weather_bot",
128
+ system_prompt: "You provide weather information.",
129
+ local_tools: [GetWeather]
130
+ )
131
+ ```
132
+
133
+ ### RobotLab::Tool Inline
134
+
135
+ For simpler tools that do not need a class:
136
+
137
+ ```ruby
138
+ tool = RobotLab::Tool.new(
139
+ name: "get_time",
140
+ description: "Get the current time",
141
+ handler: ->(_input, **_opts) { Time.now.to_s }
142
+ )
143
+ ```
144
+
145
+ With parameter schema:
69
146
 
70
147
  ```ruby
71
148
  tool = RobotLab::Tool.new(
72
149
  name: "get_weather",
73
- description: "Get current weather for a location",
150
+ description: "Get weather for a location",
74
151
  parameters: {
75
- location: {
76
- type: "string",
77
- description: "City name",
78
- required: true
152
+ type: "object",
153
+ properties: {
154
+ location: { type: "string", description: "City name" }
79
155
  },
80
- unit: {
81
- type: "string",
82
- enum: ["celsius", "fahrenheit"],
83
- default: "celsius"
84
- }
156
+ required: ["location"]
85
157
  },
86
- handler: ->(location:, unit: "celsius", **_context) {
87
- WeatherAPI.current(location, unit: unit)
158
+ handler: ->(input, **_opts) {
159
+ WeatherAPI.current(input[:location])
88
160
  }
89
161
  )
90
162
  ```
@@ -93,40 +165,79 @@ tool = RobotLab::Tool.new(
93
165
 
94
166
  When an LLM decides to use a tool:
95
167
 
96
- 1. LLM generates a `ToolCallMessage` with tool name and arguments
97
- 2. RobotLab validates arguments against the tool's schema
98
- 3. Tool handler is called with validated arguments
99
- 4. Result is wrapped in a `ToolResultMessage`
168
+ 1. LLM generates a tool call with tool name and arguments
169
+ 2. `@chat` (RubyLLM) identifies the tool from its registered tools
170
+ 3. For `RubyLLM::Tool`: calls the `execute` method with keyword arguments
171
+ 4. For `RobotLab::Tool`: calls the `call` method with input hash and context
100
172
  5. Result is sent back to the LLM for continued processing
173
+ 6. Loop repeats until the LLM produces a final text response
174
+
175
+ ### Error Handling
176
+
177
+ Tool errors are captured and returned to the LLM:
101
178
 
102
- ### Handler Context
179
+ ```ruby
180
+ def execute(order_id:)
181
+ order = ORDERS[order_id]
182
+ if order
183
+ order
184
+ else
185
+ { error: "Order not found" }
186
+ end
187
+ end
188
+ ```
103
189
 
104
- Tool handlers receive context about the current execution:
190
+ ## Memory
191
+
192
+ Memory is a reactive key-value store used by robots and networks.
193
+
194
+ ### Standalone vs Network Memory
195
+
196
+ - **Standalone**: Each robot has its own inherent `Memory` instance (`robot.memory`)
197
+ - **In a Network**: All robots share the network's `Memory` instance
105
198
 
106
199
  ```ruby
107
- handler: ->(param:, robot:, network:, state:) {
108
- # robot - The Robot instance executing the tool
109
- # network - The Network (or NetworkRun) context
110
- # state - Current State with data, results, memory
111
- }
200
+ # Standalone memory
201
+ robot.memory[:user_id] = 123
202
+ robot.memory[:user_id] # => 123
203
+
204
+ # Network memory is passed automatically
205
+ network = RobotLab.create_network(name: "pipeline") do
206
+ task :robot_a, robot_a, depends_on: :none
207
+ task :robot_b, robot_b, depends_on: [:robot_a]
208
+ end
209
+ # Both robot_a and robot_b share network.memory during execution
112
210
  ```
113
211
 
114
- !!! tip "Ignoring Context"
115
- Use `**_context` to accept but ignore context parameters:
116
- ```ruby
117
- handler: ->(location:, **_context) { ... }
118
- ```
212
+ ### Reserved Keys
119
213
 
120
- ## ToolManifest
214
+ | Key | Type | Description |
215
+ |-----|------|-------------|
216
+ | `:data` | `Hash` | Runtime data (accessible via `memory.data.key_name`) |
217
+ | `:results` | `Array` | Accumulated robot results |
218
+ | `:messages` | `Array` | Conversation history |
219
+ | `:session_id` | `String` | Session identifier |
220
+ | `:cache` | `Module` | Semantic cache (RubyLLM::SemanticCache) |
121
221
 
122
- When you need to modify tool metadata without changing functionality:
222
+ ### Reactive Features
223
+
224
+ Memory supports pub/sub semantics for inter-robot communication:
123
225
 
124
226
  ```ruby
125
- manifest = RobotLab::ToolManifest.new(
126
- tool: existing_tool,
127
- name: "custom_name", # Override name
128
- description: "New description" # Override description
129
- )
227
+ # Write a value (notifies subscribers, wakes waiters)
228
+ memory.set(:sentiment, { score: 0.8 })
229
+
230
+ # Read a value (non-blocking)
231
+ memory.get(:sentiment) # => { score: 0.8 } or nil
232
+
233
+ # Blocking read (waits until value exists)
234
+ memory.get(:sentiment, wait: true) # Blocks indefinitely
235
+ memory.get(:sentiment, wait: 30) # Blocks up to 30 seconds
236
+
237
+ # Subscribe to changes
238
+ memory.subscribe(:sentiment) do |change|
239
+ puts "#{change.key} = #{change.value} (written by #{change.writer})"
240
+ end
130
241
  ```
131
242
 
132
243
  ## Message Types
@@ -136,27 +247,22 @@ RobotLab uses a type hierarchy for messages:
136
247
  ```mermaid
137
248
  classDiagram
138
249
  Message <|-- TextMessage
139
- Message <|-- ToolMessage
140
- ToolMessage <|-- ToolCallMessage
141
- ToolMessage <|-- ToolResultMessage
250
+ Message <|-- ToolCallMessage
251
+ Message <|-- ToolResultMessage
142
252
 
143
253
  class Message {
144
254
  +String type
145
255
  +String role
146
- +String content
256
+ +content
147
257
  +String stop_reason
148
- }
149
-
150
- class TextMessage {
151
258
  +text?()
152
- +user?()
153
- +assistant?()
259
+ +tool_call?()
260
+ +tool_result?()
261
+ +stopped?()
154
262
  }
155
263
 
156
- class ToolMessage {
157
- +String id
158
- +String name
159
- +Hash input
264
+ class TextMessage {
265
+ +String content
160
266
  }
161
267
 
162
268
  class ToolCallMessage {
@@ -166,6 +272,8 @@ classDiagram
166
272
  class ToolResultMessage {
167
273
  +ToolMessage tool
168
274
  +Hash content
275
+ +success?()
276
+ +error?()
169
277
  }
170
278
  ```
171
279
 
@@ -176,7 +284,7 @@ classDiagram
176
284
  | `user` | Input from the user |
177
285
  | `assistant` | Response from the LLM |
178
286
  | `system` | System instructions |
179
- | `tool` | Tool call or result |
287
+ | `tool_result` | Tool execution result |
180
288
 
181
289
  ### Stop Reasons
182
290
 
@@ -184,60 +292,201 @@ classDiagram
184
292
  |--------|-------------|
185
293
  | `stop` | Natural completion |
186
294
  | `tool` | Tool call requested |
187
- | `max_tokens` | Token limit reached |
188
295
 
189
296
  ## RobotResult
190
297
 
191
298
  The output from a robot execution:
192
299
 
193
300
  ```ruby
194
- result = robot.run(state: state, network: network)
195
-
196
- result.robot_name # => "support_agent"
197
- result.output # => [TextMessage, ...]
198
- result.tool_calls # => [ToolMessage, ...]
199
- result.stop_reason # => "stop"
200
- result.created_at # => Time
301
+ result = robot.run("Hello!")
302
+
303
+ result.robot_name # => "support_agent"
304
+ result.output # => [TextMessage, ...]
305
+ result.tool_calls # => [ToolResultMessage, ...]
306
+ result.stop_reason # => "stop"
307
+ result.created_at # => Time
308
+ result.id # => UUID string
201
309
  ```
202
310
 
203
311
  ### Accessing Response Content
204
312
 
205
313
  ```ruby
206
- # Get text response
207
- text = result.output.select(&:text?).map(&:content).join
314
+ # Get last text response (most common)
315
+ text = result.last_text_content
208
316
 
209
317
  # Check if tools were called
210
- has_tools = result.tool_calls.any?
318
+ has_tools = result.has_tool_calls?
319
+
320
+ # Check if execution completed naturally
321
+ result.stopped?
322
+
323
+ # Serialization
324
+ result.export # => Hash (excludes debug fields)
325
+ result.to_h # => Hash (includes debug fields)
326
+ result.to_json # => JSON string
327
+ ```
328
+
329
+ ## Configuration
330
+
331
+ RobotLab uses `MywayConfig` for configuration. There is no `RobotLab.configure` block. Configuration is loaded from:
332
+
333
+ 1. Bundled defaults (`lib/robot_lab/config/defaults.yml`)
334
+ 2. Environment-specific overrides
335
+ 3. XDG config files (`~/.config/robot_lab/config.yml`)
336
+ 4. Project config (`./config/robot_lab.yml`)
337
+ 5. Environment variables (`ROBOT_LAB_*` prefix)
211
338
 
212
- # Get all tool names called
213
- tool_names = result.tool_calls.map(&:name)
339
+ Access via `RobotLab.config`:
340
+
341
+ ```ruby
342
+ RobotLab.config.ruby_llm.model # => "claude-sonnet-4"
343
+ RobotLab.config.ruby_llm.request_timeout # => 120
214
344
  ```
215
345
 
216
346
  ## Configuration Hierarchy
217
347
 
218
- RobotLab uses a cascading configuration system:
348
+ Tools and MCP servers use a cascading configuration system:
219
349
 
220
350
  ```
221
- Global (RobotLab.configure)
222
-
223
- ├── mcp: [server1, server2]
224
- ├── tools: [tool1, tool2]
225
-
226
- └── Network
227
-
228
- ├── mcp: :inherit | :none | [servers]
229
- ├── tools: :inherit | :none | [tools]
230
-
231
- └── Robot
232
-
233
- ├── mcp: :inherit | :none | [servers]
234
- └── tools: :inherit | :none | [tools]
351
+ RobotLab.config (global)
352
+ |
353
+ +-- mcp: [server1, server2]
354
+ +-- tools: [tool1, tool2]
355
+ |
356
+ +-- Network
357
+ | |
358
+ | +-- mcp: :inherit | :none | [servers]
359
+ | +-- tools: :inherit | :none | [tools]
360
+ | |
361
+ | +-- Task (per-step config)
362
+ | | +-- context: { department: "billing" }
363
+ | | +-- mcp: :none | :inherit | [servers]
364
+ | | +-- tools: :none | :inherit | [tools]
365
+ | |
366
+ | +-- Robot (build-time config)
367
+ | |
368
+ | +-- mcp: :inherit | :none | [servers]
369
+ | +-- tools: :inherit | :none | [tools]
370
+ | |
371
+ | +-- run() call (runtime config)
372
+ | +-- mcp: :none | [servers]
373
+ | +-- tools: :none | [tools]
235
374
  ```
236
375
 
237
- The `:inherit` value pulls from the parent level.
376
+ Resolution order: **runtime > robot build-time > task > network > global config**.
377
+
378
+ The `:inherit` value pulls from the parent level. `:none` explicitly disables.
379
+
380
+ ## Message Bus
381
+
382
+ The **Message Bus** provides bidirectional, cyclic communication between robots, independent of the Network pipeline. While Networks enforce DAG-based (acyclic) execution, the bus enables negotiation loops, convergence patterns, and multi-turn dialogues.
383
+
384
+ ### How It Works
385
+
386
+ Robots connect to a shared `TypedBus::MessageBus` via the `bus:` parameter. Each robot gets a typed channel (accepting only `RobotMessage` objects) named after its `name`. Messages are delivered asynchronously via the `async` gem's fiber scheduler.
387
+
388
+ ```ruby
389
+ bus = TypedBus::MessageBus.new
390
+
391
+ bob = RobotLab.build(name: "bob", system_prompt: "You tell jokes.", bus: bus)
392
+ alice = RobotLab.build(name: "alice", system_prompt: "You evaluate jokes.", bus: bus)
393
+ ```
394
+
395
+ ### RobotMessage
396
+
397
+ `RobotMessage` is an immutable `Data.define` value object used as the typed envelope:
398
+
399
+ | Field | Type | Description |
400
+ |-------|------|-------------|
401
+ | `id` | `Integer` | Per-robot sequential counter |
402
+ | `from` | `String` | Sender's robot name (= channel name) |
403
+ | `content` | `String`, `Hash` | Message payload |
404
+ | `in_reply_to` | `String`, `nil` | Composite key of the original message (e.g., `"alice:1"`) |
405
+
406
+ Methods: `key` returns `"from:id"` composite identity; `reply?` returns true when `in_reply_to` is set.
407
+
408
+ ### Sending and Receiving
409
+
410
+ ```ruby
411
+ # Send a message to another robot
412
+ alice.send_message(to: :bob, content: "Tell me a joke.")
413
+
414
+ # Handle incoming messages with auto-ack (1 arg)
415
+ bob.on_message do |message|
416
+ joke = bob.run(message.content.to_s).last_text_content
417
+ bob.reply(message, joke)
418
+ end
419
+ ```
420
+
421
+ Block arity controls delivery handling: 1 argument auto-acks; 2 arguments give manual control over `delivery.ack!`/`delivery.nack!`.
422
+
423
+ ### Dynamic Spawning
424
+
425
+ Robots can create new robots at runtime using `spawn`. The bus is created lazily — no upfront wiring required:
426
+
427
+ ```ruby
428
+ dispatcher = RobotLab.build(name: "dispatcher", system_prompt: "You delegate work.")
429
+
430
+ # spawn creates a child on the same bus (bus created automatically)
431
+ helper = dispatcher.spawn(name: "helper", system_prompt: "You answer questions.")
432
+
433
+ # The child can immediately communicate with the parent
434
+ answer = helper.run("What is 2+2?").last_text_content
435
+ helper.send_message(to: :dispatcher, content: answer)
436
+ ```
437
+
438
+ Robots can also join a bus after creation using `with_bus`:
439
+
440
+ ```ruby
441
+ bot = RobotLab.build(name: "late_joiner", system_prompt: "Hello.")
442
+ bot.with_bus(existing_bus) # now connected to the bus
443
+ ```
444
+
445
+ **Fan-out messaging**: Multiple robots with the same name all subscribe to the same channel. Messages sent to that name are delivered to all subscribers:
446
+
447
+ ```ruby
448
+ worker1 = dispatcher.spawn(name: "worker", system_prompt: "Worker 1")
449
+ worker2 = dispatcher.spawn(name: "worker", system_prompt: "Worker 2")
450
+ dispatcher.send_message(to: :worker, content: "Do this task")
451
+ # Both worker1 and worker2 receive the message
452
+ ```
453
+
454
+ ### Bus vs Network
455
+
456
+ | Feature | Network | Message Bus |
457
+ |---------|---------|-------------|
458
+ | Execution model | DAG (acyclic) | Cyclic, bidirectional |
459
+ | Communication | Sequential pipeline | Pub/sub channels |
460
+ | Memory | Shared network memory | Independent per-robot |
461
+ | Use case | Linear workflows | Negotiation, convergence |
462
+
463
+ The bus is purely additive — robots without `bus:` work exactly as before.
464
+
465
+ ## Network
466
+
467
+ A Network orchestrates multiple robots in a pipeline workflow using SimpleFlow:
468
+
469
+ ```ruby
470
+ network = RobotLab.create_network(name: "support") do
471
+ task :classifier, classifier_robot, depends_on: :none
472
+ task :billing, billing_robot, depends_on: :optional
473
+ task :technical, technical_robot, depends_on: :optional
474
+ end
475
+
476
+ result = network.run(message: "I need help with billing")
477
+ ```
478
+
479
+ Networks provide:
480
+
481
+ - **DAG-based execution** via SimpleFlow with `depends_on:` for sequencing
482
+ - **Parallel execution** for tasks with the same dependencies
483
+ - **Optional tasks** activated dynamically by classifier robots
484
+ - **Shared memory** for inter-robot communication
485
+ - **Per-task configuration** via the `Task` wrapper
486
+ - **Broadcast messaging** for network-wide announcements
238
487
 
239
488
  ## Next Steps
240
489
 
241
490
  - [Robot Execution](robot-execution.md) - Detailed execution flow
242
491
  - [Network Orchestration](network-orchestration.md) - Multi-robot coordination
243
- - [State Management](state-management.md) - Managing conversation state
492
+ - [Using Tools](../guides/using-tools.md) - Creating and using tools