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,129 +4,235 @@ 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)
69
109
 
70
110
  ```ruby
71
- tool = RobotLab::Tool.new(
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.create Factory
134
+
135
+ For simpler tools that do not need a class:
136
+
137
+ ```ruby
138
+ tool = RobotLab::Tool.create(
139
+ name: "get_time",
140
+ description: "Get the current time"
141
+ ) { |_args| Time.now.to_s }
142
+ ```
143
+
144
+ With parameter schema:
145
+
146
+ ```ruby
147
+ tool = RobotLab::Tool.create(
72
148
  name: "get_weather",
73
- description: "Get current weather for a location",
149
+ description: "Get weather for a location",
74
150
  parameters: {
75
- location: {
76
- type: "string",
77
- description: "City name",
78
- required: true
151
+ type: "object",
152
+ properties: {
153
+ location: { type: "string", description: "City name" }
79
154
  },
80
- unit: {
81
- type: "string",
82
- enum: ["celsius", "fahrenheit"],
83
- default: "celsius"
84
- }
85
- },
86
- handler: ->(location:, unit: "celsius", **_context) {
87
- WeatherAPI.current(location, unit: unit)
155
+ required: ["location"]
88
156
  }
89
- )
157
+ ) { |args| WeatherAPI.current(args[:location]) }
90
158
  ```
91
159
 
92
160
  ### Tool Execution
93
161
 
94
162
  When an LLM decides to use a tool:
95
163
 
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`
100
- 5. Result is sent back to the LLM for continued processing
164
+ 1. LLM generates a tool call with tool name and arguments
165
+ 2. `@chat` (RubyLLM) identifies the tool from its registered tools
166
+ 3. Calls the `execute` method with keyword arguments
167
+ 4. Result is sent back to the LLM for continued processing
168
+ 5. Loop repeats until the LLM produces a final text response
169
+
170
+ ### Error Handling
171
+
172
+ Tool errors are captured and returned to the LLM:
173
+
174
+ ```ruby
175
+ def execute(order_id:)
176
+ order = ORDERS[order_id]
177
+ if order
178
+ order
179
+ else
180
+ { error: "Order not found" }
181
+ end
182
+ end
183
+ ```
101
184
 
102
- ### Handler Context
185
+ ## Memory
103
186
 
104
- Tool handlers receive context about the current execution:
187
+ Memory is a reactive key-value store used by robots and networks.
188
+
189
+ ### Standalone vs Network Memory
190
+
191
+ - **Standalone**: Each robot has its own inherent `Memory` instance (`robot.memory`)
192
+ - **In a Network**: All robots share the network's `Memory` instance
105
193
 
106
194
  ```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
- }
195
+ # Standalone memory
196
+ robot.memory[:user_id] = 123
197
+ robot.memory[:user_id] # => 123
198
+
199
+ # Network memory is passed automatically
200
+ network = RobotLab.create_network(name: "pipeline") do
201
+ task :robot_a, robot_a, depends_on: :none
202
+ task :robot_b, robot_b, depends_on: [:robot_a]
203
+ end
204
+ # Both robot_a and robot_b share network.memory during execution
112
205
  ```
113
206
 
114
- !!! tip "Ignoring Context"
115
- Use `**_context` to accept but ignore context parameters:
116
- ```ruby
117
- handler: ->(location:, **_context) { ... }
118
- ```
207
+ ### Reserved Keys
208
+
209
+ | Key | Type | Description |
210
+ |-----|------|-------------|
211
+ | `:data` | `Hash` | Runtime data (accessible via `memory.data.key_name`) |
212
+ | `:results` | `Array` | Accumulated robot results |
213
+ | `:messages` | `Array` | Conversation history |
214
+ | `:session_id` | `String` | Session identifier |
215
+ | `:cache` | `Module` | Semantic cache (RubyLLM::SemanticCache) |
119
216
 
120
- ## ToolManifest
217
+ ### Reactive Features
121
218
 
122
- When you need to modify tool metadata without changing functionality:
219
+ Memory supports pub/sub semantics for inter-robot communication:
123
220
 
124
221
  ```ruby
125
- manifest = RobotLab::ToolManifest.new(
126
- tool: existing_tool,
127
- name: "custom_name", # Override name
128
- description: "New description" # Override description
129
- )
222
+ # Write a value (notifies subscribers, wakes waiters)
223
+ memory.set(:sentiment, { score: 0.8 })
224
+
225
+ # Read a value (non-blocking)
226
+ memory.get(:sentiment) # => { score: 0.8 } or nil
227
+
228
+ # Blocking read (waits until value exists)
229
+ memory.get(:sentiment, wait: true) # Blocks indefinitely
230
+ memory.get(:sentiment, wait: 30) # Blocks up to 30 seconds
231
+
232
+ # Subscribe to changes
233
+ memory.subscribe(:sentiment) do |change|
234
+ puts "#{change.key} = #{change.value} (written by #{change.writer})"
235
+ end
130
236
  ```
131
237
 
132
238
  ## Message Types
@@ -136,27 +242,22 @@ RobotLab uses a type hierarchy for messages:
136
242
  ```mermaid
137
243
  classDiagram
138
244
  Message <|-- TextMessage
139
- Message <|-- ToolMessage
140
- ToolMessage <|-- ToolCallMessage
141
- ToolMessage <|-- ToolResultMessage
245
+ Message <|-- ToolCallMessage
246
+ Message <|-- ToolResultMessage
142
247
 
143
248
  class Message {
144
249
  +String type
145
250
  +String role
146
- +String content
251
+ +content
147
252
  +String stop_reason
148
- }
149
-
150
- class TextMessage {
151
253
  +text?()
152
- +user?()
153
- +assistant?()
254
+ +tool_call?()
255
+ +tool_result?()
256
+ +stopped?()
154
257
  }
155
258
 
156
- class ToolMessage {
157
- +String id
158
- +String name
159
- +Hash input
259
+ class TextMessage {
260
+ +String content
160
261
  }
161
262
 
162
263
  class ToolCallMessage {
@@ -166,6 +267,8 @@ classDiagram
166
267
  class ToolResultMessage {
167
268
  +ToolMessage tool
168
269
  +Hash content
270
+ +success?()
271
+ +error?()
169
272
  }
170
273
  ```
171
274
 
@@ -176,7 +279,7 @@ classDiagram
176
279
  | `user` | Input from the user |
177
280
  | `assistant` | Response from the LLM |
178
281
  | `system` | System instructions |
179
- | `tool` | Tool call or result |
282
+ | `tool_result` | Tool execution result |
180
283
 
181
284
  ### Stop Reasons
182
285
 
@@ -184,60 +287,201 @@ classDiagram
184
287
  |--------|-------------|
185
288
  | `stop` | Natural completion |
186
289
  | `tool` | Tool call requested |
187
- | `max_tokens` | Token limit reached |
188
290
 
189
291
  ## RobotResult
190
292
 
191
293
  The output from a robot execution:
192
294
 
193
295
  ```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
296
+ result = robot.run("Hello!")
297
+
298
+ result.robot_name # => "support_agent"
299
+ result.output # => [TextMessage, ...]
300
+ result.tool_calls # => [ToolResultMessage, ...]
301
+ result.stop_reason # => "stop"
302
+ result.created_at # => Time
303
+ result.id # => UUID string
201
304
  ```
202
305
 
203
306
  ### Accessing Response Content
204
307
 
205
308
  ```ruby
206
- # Get text response
207
- text = result.output.select(&:text?).map(&:content).join
309
+ # Get last text response (most common)
310
+ text = result.last_text_content
208
311
 
209
312
  # Check if tools were called
210
- has_tools = result.tool_calls.any?
313
+ has_tools = result.has_tool_calls?
314
+
315
+ # Check if execution completed naturally
316
+ result.stopped?
211
317
 
212
- # Get all tool names called
213
- tool_names = result.tool_calls.map(&:name)
318
+ # Serialization
319
+ result.export # => Hash (excludes debug fields)
320
+ result.to_h # => Hash (includes debug fields)
321
+ result.to_json # => JSON string
322
+ ```
323
+
324
+ ## Configuration
325
+
326
+ RobotLab uses `MywayConfig` for configuration. There is no `RobotLab.configure` block. Configuration is loaded from:
327
+
328
+ 1. Bundled defaults (`lib/robot_lab/config/defaults.yml`)
329
+ 2. Environment-specific overrides
330
+ 3. XDG config files (`~/.config/robot_lab/config.yml`)
331
+ 4. Project config (`./config/robot_lab.yml`)
332
+ 5. Environment variables (`ROBOT_LAB_*` prefix)
333
+
334
+ Access via `RobotLab.config`:
335
+
336
+ ```ruby
337
+ RobotLab.config.ruby_llm.model # => "claude-sonnet-4"
338
+ RobotLab.config.ruby_llm.request_timeout # => 120
214
339
  ```
215
340
 
216
341
  ## Configuration Hierarchy
217
342
 
218
- RobotLab uses a cascading configuration system:
343
+ Tools and MCP servers use a cascading configuration system:
344
+
345
+ ```
346
+ RobotLab.config (global)
347
+ |
348
+ +-- mcp: [server1, server2]
349
+ +-- tools: [tool1, tool2]
350
+ |
351
+ +-- Network
352
+ | |
353
+ | +-- mcp: :inherit | :none | [servers]
354
+ | +-- tools: :inherit | :none | [tools]
355
+ | |
356
+ | +-- Task (per-step config)
357
+ | | +-- context: { department: "billing" }
358
+ | | +-- mcp: :none | :inherit | [servers]
359
+ | | +-- tools: :none | :inherit | [tools]
360
+ | |
361
+ | +-- Robot (build-time config)
362
+ | |
363
+ | +-- mcp: :inherit | :none | [servers]
364
+ | +-- tools: :inherit | :none | [tools]
365
+ | |
366
+ | +-- run() call (runtime config)
367
+ | +-- mcp: :none | [servers]
368
+ | +-- tools: :none | [tools]
369
+ ```
370
+
371
+ Resolution order: **runtime > robot build-time > task > network > global config**.
372
+
373
+ The `:inherit` value pulls from the parent level. `:none` explicitly disables.
374
+
375
+ ## Message Bus
376
+
377
+ 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.
378
+
379
+ ### How It Works
380
+
381
+ 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.
382
+
383
+ ```ruby
384
+ bus = TypedBus::MessageBus.new
385
+
386
+ bob = RobotLab.build(name: "bob", system_prompt: "You tell jokes.", bus: bus)
387
+ alice = RobotLab.build(name: "alice", system_prompt: "You evaluate jokes.", bus: bus)
388
+ ```
389
+
390
+ ### RobotMessage
391
+
392
+ `RobotMessage` is an immutable `Data.define` value object used as the typed envelope:
393
+
394
+ | Field | Type | Description |
395
+ |-------|------|-------------|
396
+ | `id` | `Integer` | Per-robot sequential counter |
397
+ | `from` | `String` | Sender's robot name (= channel name) |
398
+ | `content` | `String`, `Hash` | Message payload |
399
+ | `in_reply_to` | `String`, `nil` | Composite key of the original message (e.g., `"alice:1"`) |
400
+
401
+ Methods: `key` returns `"from:id"` composite identity; `reply?` returns true when `in_reply_to` is set.
219
402
 
403
+ ### Sending and Receiving
404
+
405
+ ```ruby
406
+ # Send a message to another robot
407
+ alice.send_message(to: :bob, content: "Tell me a joke.")
408
+
409
+ # Handle incoming messages with auto-ack (1 arg)
410
+ bob.on_message do |message|
411
+ joke = bob.run(message.content.to_s).last_text_content
412
+ bob.send_reply(to: message.from.to_sym, content: joke, in_reply_to: message.key)
413
+ end
414
+ ```
415
+
416
+ Block arity controls delivery handling: 1 argument auto-acks; 2 arguments give manual control over `delivery.ack!`/`delivery.nack!`.
417
+
418
+ ### Dynamic Spawning
419
+
420
+ Robots can create new robots at runtime using `spawn`. The bus is created lazily — no upfront wiring required:
421
+
422
+ ```ruby
423
+ dispatcher = RobotLab.build(name: "dispatcher", system_prompt: "You delegate work.")
424
+
425
+ # spawn creates a child on the same bus (bus created automatically)
426
+ helper = dispatcher.spawn(name: "helper", system_prompt: "You answer questions.")
427
+
428
+ # The child can immediately communicate with the parent
429
+ answer = helper.run("What is 2+2?").last_text_content
430
+ helper.send_message(to: :dispatcher, content: answer)
431
+ ```
432
+
433
+ Robots can also join a bus after creation using `with_bus`:
434
+
435
+ ```ruby
436
+ bot = RobotLab.build(name: "late_joiner", system_prompt: "Hello.")
437
+ bot.with_bus(existing_bus) # now connected to the bus
220
438
  ```
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]
439
+
440
+ **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:
441
+
442
+ ```ruby
443
+ worker1 = dispatcher.spawn(name: "worker", system_prompt: "Worker 1")
444
+ worker2 = dispatcher.spawn(name: "worker", system_prompt: "Worker 2")
445
+ dispatcher.send_message(to: :worker, content: "Do this task")
446
+ # Both worker1 and worker2 receive the message
235
447
  ```
236
448
 
237
- The `:inherit` value pulls from the parent level.
449
+ ### Bus vs Network
450
+
451
+ | Feature | Network | Message Bus |
452
+ |---------|---------|-------------|
453
+ | Execution model | DAG (acyclic) | Cyclic, bidirectional |
454
+ | Communication | Sequential pipeline | Pub/sub channels |
455
+ | Memory | Shared network memory | Independent per-robot |
456
+ | Use case | Linear workflows | Negotiation, convergence |
457
+
458
+ The bus is purely additive — robots without `bus:` work exactly as before.
459
+
460
+ ## Network
461
+
462
+ A Network orchestrates multiple robots in a pipeline workflow using SimpleFlow:
463
+
464
+ ```ruby
465
+ network = RobotLab.create_network(name: "support") do
466
+ task :classifier, classifier_robot, depends_on: :none
467
+ task :billing, billing_robot, depends_on: :optional
468
+ task :technical, technical_robot, depends_on: :optional
469
+ end
470
+
471
+ result = network.run(message: "I need help with billing")
472
+ ```
473
+
474
+ Networks provide:
475
+
476
+ - **DAG-based execution** via SimpleFlow with `depends_on:` for sequencing
477
+ - **Parallel execution** for tasks with the same dependencies
478
+ - **Optional tasks** activated dynamically by classifier robots
479
+ - **Shared memory** for inter-robot communication
480
+ - **Per-task configuration** via the `Task` wrapper
481
+ - **Broadcast messaging** for network-wide announcements
238
482
 
239
483
  ## Next Steps
240
484
 
241
485
  - [Robot Execution](robot-execution.md) - Detailed execution flow
242
486
  - [Network Orchestration](network-orchestration.md) - Multi-robot coordination
243
- - [State Management](state-management.md) - Managing conversation state
487
+ - [Using Tools](../guides/using-tools.md) - Creating and using tools