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,7 +4,7 @@ A simple conversational robot example.
4
4
 
5
5
  ## Overview
6
6
 
7
- This example demonstrates the minimal setup for a conversational robot that can respond to user messages.
7
+ This example demonstrates the minimal setup for a conversational robot that can respond to user messages using `robot.run("message")`.
8
8
 
9
9
  ## Complete Example
10
10
 
@@ -15,21 +15,16 @@ This example demonstrates the minimal setup for a conversational robot that can
15
15
  require "bundler/setup"
16
16
  require "robot_lab"
17
17
 
18
- # Configure RobotLab
19
- RobotLab.configure do |config|
20
- config.default_model = "claude-sonnet-4"
21
- end
22
-
23
18
  # Build a simple assistant
24
- assistant = RobotLab.build do
25
- name "assistant"
26
- description "A helpful conversational assistant"
27
-
28
- template <<~PROMPT
19
+ assistant = RobotLab.build(
20
+ name: "assistant",
21
+ description: "A helpful conversational assistant",
22
+ system_prompt: <<~PROMPT,
29
23
  You are a helpful, friendly assistant. You provide clear,
30
24
  concise answers to questions. Be conversational but informative.
31
25
  PROMPT
32
- end
26
+ model: "claude-sonnet-4"
27
+ )
33
28
 
34
29
  # Simple REPL
35
30
  puts "Chat with the assistant (type 'quit' to exit)"
@@ -42,13 +37,11 @@ loop do
42
37
  break if input.nil? || input.downcase == "quit"
43
38
  next if input.empty?
44
39
 
45
- # Create state and run
46
- state = RobotLab.create_state(message: input)
47
- result = assistant.run(state: state)
40
+ # Run the robot with the user's message
41
+ result = assistant.run(input)
48
42
 
49
43
  # Display response
50
- response = result.output.first&.content || "No response"
51
- puts "\nAssistant: #{response}"
44
+ puts "\nAssistant: #{result.last_text_content}"
52
45
  end
53
46
 
54
47
  puts "\nGoodbye!"
@@ -63,14 +56,11 @@ puts "\nGoodbye!"
63
56
  require "bundler/setup"
64
57
  require "robot_lab"
65
58
 
66
- RobotLab.configure do |config|
67
- config.default_model = "claude-sonnet-4"
68
- end
69
-
70
- assistant = RobotLab.build do
71
- name "assistant"
72
- template "You are a helpful assistant."
73
- end
59
+ assistant = RobotLab.build(
60
+ name: "assistant",
61
+ system_prompt: "You are a helpful assistant.",
62
+ model: "claude-sonnet-4"
63
+ )
74
64
 
75
65
  puts "Chat with streaming (type 'quit' to exit)"
76
66
  puts "-" * 50
@@ -82,11 +72,9 @@ loop do
82
72
  break if input.nil? || input.downcase == "quit"
83
73
  next if input.empty?
84
74
 
85
- state = RobotLab.create_state(message: input)
86
-
87
75
  print "\nAssistant: "
88
- assistant.run(state: state) do |event|
89
- print event.text if event.type == :text_delta
76
+ result = assistant.run(input) do |event|
77
+ print event.text if event.respond_to?(:text)
90
78
  end
91
79
  puts
92
80
  end
@@ -94,51 +82,61 @@ end
94
82
  puts "\nGoodbye!"
95
83
  ```
96
84
 
97
- ## With Conversation History
85
+ ## With Template
98
86
 
99
87
  ```ruby
100
88
  #!/usr/bin/env ruby
101
- # examples/chat_with_memory.rb
89
+ # examples/template_chat.rb
102
90
 
103
91
  require "bundler/setup"
104
92
  require "robot_lab"
105
93
 
106
- RobotLab.configure do |config|
107
- config.default_model = "claude-sonnet-4"
108
- end
94
+ # Build a robot using a prompt template file
95
+ # Template: prompts/assistant.md (Markdown with YAML front matter)
96
+ assistant = RobotLab.build(
97
+ name: "assistant",
98
+ template: :assistant,
99
+ context: { tone: "friendly", domain: "general" },
100
+ model: "claude-sonnet-4"
101
+ )
109
102
 
110
- assistant = RobotLab.build do
111
- name "assistant"
112
- template "You are a helpful assistant with memory of our conversation."
113
- end
103
+ puts "Chat with template-based assistant (type 'quit' to exit)"
104
+ puts "-" * 50
114
105
 
115
- # In-memory history store
116
- HISTORY = {}
117
-
118
- history_config = RobotLab::History::Config.new(
119
- create_thread: ->(state:, **) {
120
- id = SecureRandom.uuid
121
- HISTORY[id] = []
122
- { id: id }
123
- },
124
- get: ->(thread_id:, **) {
125
- HISTORY[thread_id] || []
126
- },
127
- append_results: ->(thread_id:, new_results:, **) {
128
- HISTORY[thread_id].concat(new_results.map(&:to_h))
129
- }
130
- )
106
+ loop do
107
+ print "\nYou: "
108
+ input = gets&.chomp
109
+
110
+ break if input.nil? || input.downcase == "quit"
111
+ next if input.empty?
131
112
 
132
- network = RobotLab.create_network do
133
- name "chat"
134
- history history_config
135
- add_robot assistant
113
+ result = assistant.run(input)
114
+ puts "\nAssistant: #{result.last_text_content}"
136
115
  end
137
116
 
117
+ puts "\nGoodbye!"
118
+ ```
119
+
120
+ ## With Memory
121
+
122
+ ```ruby
123
+ #!/usr/bin/env ruby
124
+ # examples/chat_with_memory.rb
125
+
126
+ require "bundler/setup"
127
+ require "robot_lab"
128
+
129
+ assistant = RobotLab.build(
130
+ name: "assistant",
131
+ system_prompt: "You are a helpful assistant. Use the user's name when you know it.",
132
+ model: "claude-sonnet-4"
133
+ )
134
+
138
135
  puts "Chat with memory (type 'quit' to exit)"
139
136
  puts "-" * 50
140
137
 
141
- thread_id = nil
138
+ # Store user info in the robot's inherent memory
139
+ assistant.memory[:user_name] = "Alice"
142
140
 
143
141
  loop do
144
142
  print "\nYou: "
@@ -147,20 +145,34 @@ loop do
147
145
  break if input.nil? || input.downcase == "quit"
148
146
  next if input.empty?
149
147
 
150
- message = thread_id ?
151
- RobotLab::UserMessage.new(input, thread_id: thread_id) :
152
- input
148
+ # The robot's persistent @chat maintains conversation history automatically
149
+ result = assistant.run(input)
150
+ puts "\nAssistant: #{result.last_text_content}"
151
+ end
152
+
153
+ puts "\nGoodbye!"
154
+ ```
153
155
 
154
- state = RobotLab.create_state(message: message)
155
- result = network.run(state: state)
156
+ ## Bare Robot with Chaining
156
157
 
157
- thread_id ||= result.state.thread_id
158
+ ```ruby
159
+ #!/usr/bin/env ruby
160
+ # examples/bare_robot.rb
158
161
 
159
- response = result.last_result&.output&.first&.content || "No response"
160
- puts "\nAssistant: #{response}"
161
- end
162
+ require "bundler/setup"
163
+ require "robot_lab"
162
164
 
163
- puts "\nGoodbye!"
165
+ # Build a bare robot with no template or prompt
166
+ robot = RobotLab.build(name: "bot")
167
+
168
+ # Configure via chaining
169
+ result = robot
170
+ .with_model("claude-sonnet-4")
171
+ .with_temperature(0.7)
172
+ .with_instructions("You are a pirate. Respond in pirate speak.")
173
+ .run("What is the weather like today?")
174
+
175
+ puts result.last_text_content
164
176
  ```
165
177
 
166
178
  ## Running
@@ -174,20 +186,20 @@ ruby examples/basic_chat.rb
174
186
 
175
187
  # Run with streaming
176
188
  ruby examples/streaming_chat.rb
177
-
178
- # Run with memory
179
- ruby examples/chat_with_memory.rb
180
189
  ```
181
190
 
182
191
  ## Key Concepts
183
192
 
184
- 1. **Robot Building**: Use `RobotLab.build` with a template
185
- 2. **State Creation**: Use `RobotLab.create_state` with a message
186
- 3. **Execution**: Call `robot.run(state: state)`
187
- 4. **Response**: Access via `result.output.first.content`
193
+ 1. **Robot Building**: Use `RobotLab.build(name:, system_prompt:)` or `RobotLab.build(name:, template:)` to create a robot
194
+ 2. **Execution**: Call `robot.run("message")` to send a message and get a response
195
+ 3. **Response**: Access the text via `result.last_text_content`
196
+ 4. **Streaming**: Pass a block to `robot.run("message") { |event| ... }`
197
+ 5. **Memory**: Access inherent memory via `robot.memory[:key]`
198
+ 6. **Chaining**: Configure with `with_*` methods that return `self`
199
+ 7. **Conversation History**: The persistent `@chat` maintains history across multiple `run` calls
188
200
 
189
201
  ## See Also
190
202
 
191
203
  - [Building Robots Guide](../guides/building-robots.md)
192
204
  - [Streaming Guide](../guides/streaming.md)
193
- - [History Guide](../guides/history.md)
205
+ - [Robot API Reference](../api/core/robot.md)
@@ -15,6 +15,8 @@ These examples show how to use RobotLab for common scenarios, from simple chatbo
15
15
  | [Tool Usage](tool-usage.md) | External API integration |
16
16
  | [MCP Server](mcp-server.md) | Creating an MCP tool server |
17
17
  | [Rails Application](rails-application.md) | Full Rails integration |
18
+ | [Message Bus](#message-bus) | Bidirectional robot communication with convergence |
19
+ | [Spawning Robots](#spawning-robots) | Dynamic specialist creation at runtime |
18
20
 
19
21
  ## Quick Links
20
22
 
@@ -29,80 +31,117 @@ These examples show how to use RobotLab for common scenarios, from simple chatbo
29
31
  - [Streaming Responses](basic-chat.md#with-streaming)
30
32
  - [Persistent Conversations](basic-chat.md#with-conversation-history)
31
33
  - [MCP Integration](mcp-server.md)
34
+ - [Message Bus Communication](#message-bus)
35
+ - [Spawning Robots](#spawning-robots)
32
36
 
33
37
  ## Hello World
34
38
 
35
39
  ```ruby
36
40
  require "robot_lab"
37
41
 
38
- RobotLab.configure do |config|
39
- config.default_model = "claude-sonnet-4"
40
- end
42
+ # Configuration is handled automatically via MywayConfig.
43
+ # Set API keys via environment variables:
44
+ # ROBOT_LAB_RUBY_LLM__ANTHROPIC_API_KEY=sk-ant-...
45
+ # Or via config files (~/.config/robot_lab/config.yml)
41
46
 
42
- robot = RobotLab.build do
43
- name "greeter"
44
- template "You are a friendly greeter. Say hello warmly."
45
- end
47
+ robot = RobotLab.build(
48
+ name: "greeter",
49
+ system_prompt: "You are a friendly greeter. Say hello warmly."
50
+ )
46
51
 
47
- state = RobotLab.create_state(message: "Hi there!")
48
- result = robot.run(state: state)
52
+ result = robot.run("Hi there!")
49
53
 
50
- puts result.output.first.content
54
+ puts result.last_text_content
51
55
  ```
52
56
 
53
57
  ## Robot with Tools
54
58
 
55
59
  ```ruby
56
- robot = RobotLab.build do
57
- name "calculator"
58
- template "You help with calculations."
59
-
60
- tool :calculate do
61
- description "Perform a calculation"
62
- parameter :expression, type: :string, required: true
63
- handler { |expression:, **_| eval(expression).to_s }
60
+ class CalculatorTool < RubyLLM::Tool
61
+ description "Perform a calculation"
62
+
63
+ param :expression, type: :string, desc: "Math expression to evaluate"
64
+
65
+ def execute(expression:)
66
+ eval(expression).to_s
64
67
  end
65
68
  end
66
69
 
67
- state = RobotLab.create_state(message: "What's 25 * 4?")
68
- result = robot.run(state: state)
70
+ robot = RobotLab.build(
71
+ name: "calculator",
72
+ system_prompt: "You help with calculations.",
73
+ local_tools: [CalculatorTool]
74
+ )
75
+
76
+ result = robot.run("What's 25 * 4?")
77
+ puts result.last_text_content
69
78
  ```
70
79
 
71
80
  ## Network with Routing
72
81
 
73
82
  ```ruby
74
- classifier = RobotLab.build do
75
- name "classifier"
76
- template "Classify: BILLING, TECHNICAL, or GENERAL"
83
+ classifier = RobotLab.build(
84
+ name: "classifier",
85
+ system_prompt: "Classify the request as BILLING or TECHNICAL. Respond with only the category."
86
+ )
87
+
88
+ billing = RobotLab.build(
89
+ name: "billing",
90
+ system_prompt: "You handle billing questions."
91
+ )
92
+
93
+ tech = RobotLab.build(
94
+ name: "tech",
95
+ system_prompt: "You handle technical issues."
96
+ )
97
+
98
+ network = RobotLab.create_network(name: "support") do
99
+ task :classifier, classifier, depends_on: :none
100
+ task :billing, billing, depends_on: :optional
101
+ task :tech, tech, depends_on: :optional
77
102
  end
78
103
 
79
- billing = RobotLab.build do
80
- name "billing"
81
- template "You handle billing questions."
82
- end
104
+ result = network.run(message: "I was charged twice for my subscription")
83
105
 
84
- tech = RobotLab.build do
85
- name "tech"
86
- template "You handle technical issues."
87
- end
106
+ # Access individual robot results via context
107
+ classifier_result = result.context[:classifier]
108
+ puts classifier_result.last_text_content
109
+ ```
88
110
 
89
- network = RobotLab.create_network do
90
- name "support"
91
- add_robot classifier
92
- add_robot billing
93
- add_robot tech
94
-
95
- router ->(args) {
96
- case args.call_count
97
- when 0 then :classifier
98
- when 1
99
- category = args.last_result&.output&.first&.content&.strip
100
- category == "BILLING" ? :billing : :tech
101
- end
102
- }
103
- end
111
+ ## Chaining Configuration
112
+
113
+ Robots support `with_*` methods that return `self` for chaining:
114
+
115
+ ```ruby
116
+ robot = RobotLab.build(name: "assistant")
117
+ .with_instructions("You are a helpful coding assistant.")
118
+ .with_temperature(0.3)
119
+ .with_model("gpt-4o")
120
+
121
+ result = robot.run("Explain Ruby blocks.")
122
+ puts result.last_text_content
123
+ ```
124
+
125
+ ## Using Templates
126
+
127
+ Templates are `.md` files with optional YAML front matter, managed by prompt_manager:
104
128
 
105
- result = network.run(state: state)
129
+ ```ruby
130
+ # Template file: prompts/support.md
131
+ # ---
132
+ # model: claude-sonnet-4
133
+ # temperature: 0.5
134
+ # ---
135
+ # You are a support assistant for {{ company_name }}.
136
+
137
+ robot = RobotLab.build(
138
+ name: "support",
139
+ template: :support,
140
+ context: { company_name: "Acme Corp" }
141
+ )
142
+
143
+ result = robot.run("How do I reset my password?")
144
+ puts result.last_text_content
106
145
  ```
107
146
 
108
147
  ## Running Examples
@@ -122,6 +161,142 @@ result = network.run(state: state)
122
161
  ruby examples/basic_chat.rb
123
162
  ```
124
163
 
164
+ Or use the provided rake tasks:
165
+
166
+ ```bash
167
+ bundle exec rake examples:all # Run all examples
168
+ bundle exec rake examples:run[1] # Run specific example by number
169
+ ```
170
+
171
+ ## Message Bus
172
+
173
+ Robots can communicate bidirectionally via a message bus, enabling convergence loops and negotiation patterns. This example demonstrates a comedy critic tasking a comedian to generate jokes until one passes:
174
+
175
+ ```ruby
176
+ ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
177
+ require "robot_lab"
178
+
179
+ MAX_ATTEMPTS = 5
180
+
181
+ class Comedian < RobotLab::Robot
182
+ TEMP_START = 0.2
183
+ TEMP_STEP = 0.2
184
+
185
+ def initialize(bus:)
186
+ super(name: "bob", template: :comedian, bus: bus, temperature: TEMP_START)
187
+ @attempts = 0
188
+ on_message do |message|
189
+ @attempts += 1
190
+ temp = [TEMP_START + TEMP_STEP * (@attempts - 1), 1.0].min
191
+ with_temperature(temp)
192
+ joke = run(message.content.to_s).last_text_content.strip
193
+ send_reply(to: message.from.to_sym, content: joke, in_reply_to: message.key)
194
+ end
195
+ end
196
+
197
+ attr_reader :attempts
198
+ end
199
+
200
+ class ComedyCritic < RobotLab::Robot
201
+ def initialize(bus:)
202
+ super(name: "alice", template: :comedy_critic, bus: bus)
203
+ @accepted = false
204
+ on_message do |message|
205
+ verdict = run("Evaluate this joke:\n\n#{message.content}").last_text_content.strip
206
+ @accepted = verdict.start_with?("FUNNY")
207
+ send_message(to: :bob, content: "Not funny enough. Try again.") unless @accepted
208
+ end
209
+ end
210
+
211
+ attr_reader :accepted
212
+ end
213
+
214
+ bus = TypedBus::MessageBus.new
215
+ bob = Comedian.new(bus: bus)
216
+ alice = ComedyCritic.new(bus: bus)
217
+
218
+ alice.send_message(to: :bob, content: "Tell me a funny robot joke.")
219
+ puts "Attempts: #{bob.attempts} / #{MAX_ATTEMPTS}"
220
+ puts "Accepted: #{alice.accepted}"
221
+ ```
222
+
223
+ Key patterns demonstrated:
224
+
225
+ - **Robot subclasses** with templates for prompt management
226
+ - **Auto-ack** via 1-arg `on_message` blocks
227
+ - **`send_reply(to:, content:, in_reply_to:)`** for correlated responses
228
+ - **Temperature ramping** (0.2 &rarr; 1.0) for increasing creativity
229
+ - **Convergence loop** that terminates when the critic approves
230
+
231
+ Run: `bundle exec ruby examples/12_message_bus.rb`
232
+
233
+ ## Spawning Robots
234
+
235
+ Robots can create new specialist robots at runtime using `spawn`. A dispatcher receives questions, decides what kind of specialist is needed, and spawns one on the fly. The bus is created lazily — no explicit setup required:
236
+
237
+ ```ruby
238
+ ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
239
+ require "robot_lab"
240
+
241
+ QUESTIONS = [
242
+ "Why did the Roman Empire fall?",
243
+ "Write a haiku about recursion.",
244
+ "What is the square root of 144?",
245
+ ].freeze
246
+
247
+ class Dispatcher < RobotLab::Robot
248
+ attr_reader :spawned
249
+
250
+ def initialize(bus: nil)
251
+ super(name: "dispatcher", template: :dispatcher, bus: bus)
252
+ @spawned = {}
253
+ @pending = {}
254
+
255
+ on_message do |message|
256
+ puts " Dispatcher <- :#{message.from} replied"
257
+ puts " | #{message.content.to_s.lines.first&.strip}"
258
+ @pending.delete(message.from)
259
+ end
260
+ end
261
+
262
+ def dispatch(question)
263
+ plan = run(question).last_text_content.strip
264
+ role, instruction = plan.split("\n", 2)
265
+ role = role.strip.downcase.gsub(/\s+/, "_")
266
+ instruction = instruction&.strip || "You are a helpful #{role}."
267
+
268
+ specialist = @spawned[role] ||= spawn(
269
+ name: role,
270
+ system_prompt: instruction
271
+ )
272
+
273
+ @pending[role] = question
274
+
275
+ specialist.send_message(to: :dispatcher, content:
276
+ specialist.run(question).last_text_content.strip
277
+ )
278
+ end
279
+ end
280
+
281
+ dispatcher = Dispatcher.new
282
+
283
+ QUESTIONS.each_with_index do |question, i|
284
+ puts "\nQuestion #{i + 1}: #{question}"
285
+ dispatcher.dispatch(question)
286
+ end
287
+
288
+ puts "\nSpecialists spawned: #{dispatcher.spawned.keys.join(', ')}"
289
+ ```
290
+
291
+ Key patterns demonstrated:
292
+
293
+ - **`spawn`** for dynamic robot creation (bus created lazily)
294
+ - **`on_message`** for reply handling
295
+ - **LLM-driven delegation** — the dispatcher asks its LLM what specialist to create
296
+ - **Specialist reuse** — spawned robots are cached and reused across questions
297
+
298
+ Run: `bundle exec ruby examples/13_spawn.rb`
299
+
125
300
  ## See Also
126
301
 
127
302
  - [Getting Started](../getting-started/index.md)