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
@@ -1,269 +1,629 @@
1
1
  # Robot
2
2
 
3
- LLM-powered agent with personality, tools, and model configuration.
3
+ LLM-powered agent with template-based prompts, tools, memory, and MCP integration.
4
4
 
5
- ## Class: `RobotLab::Robot`
5
+ ## Class Hierarchy
6
6
 
7
- ```ruby
8
- robot = RobotLab.build do
9
- name "assistant"
10
- description "A helpful assistant"
11
- model "claude-sonnet-4"
12
- template "You are helpful."
13
- end
7
+ ```
8
+ RubyLLM::Agent
9
+ └── RobotLab::Robot
10
+ └── Your custom subclasses (e.g., ClassifierRobot)
14
11
  ```
15
12
 
16
- ## Attributes
13
+ `Robot` inherits from `RubyLLM::Agent`, which creates a persistent `@chat` on initialization. The robot adds template-based prompts, shared memory, hierarchical MCP configuration, and SimpleFlow pipeline integration on top of the base agent.
17
14
 
18
- ### name
15
+ ## Constructor
19
16
 
20
17
  ```ruby
21
- robot.name # => String
18
+ Robot.new(
19
+ name:,
20
+ template: nil,
21
+ system_prompt: nil,
22
+ context: {},
23
+ description: nil,
24
+ local_tools: [],
25
+ model: nil,
26
+ mcp_servers: [],
27
+ mcp: :none,
28
+ tools: :none,
29
+ on_tool_call: nil,
30
+ on_tool_result: nil,
31
+ enable_cache: true,
32
+ bus: nil,
33
+ temperature: nil,
34
+ top_p: nil,
35
+ top_k: nil,
36
+ max_tokens: nil,
37
+ presence_penalty: nil,
38
+ frequency_penalty: nil,
39
+ stop: nil,
40
+ config: nil
41
+ )
42
+ ```
43
+
44
+ ### Parameters
45
+
46
+ | Name | Type | Default | Description |
47
+ |------|------|---------|-------------|
48
+ | `name` | `String` | **required** | Unique identifier for the robot |
49
+ | `template` | `Symbol`, `nil` | `nil` | Prompt template (e.g., `:assistant` loads `prompts/assistant.md`) |
50
+ | `system_prompt` | `String`, `nil` | `nil` | Inline system prompt (appended after template if both given) |
51
+ | `context` | `Hash`, `Proc` | `{}` | Variables passed to the template |
52
+ | `description` | `String`, `nil` | `nil` | Human-readable description of what the robot does |
53
+ | `local_tools` | `Array` | `[]` | Tools defined locally (`RubyLLM::Tool` subclasses or `RobotLab::Tool` instances) |
54
+ | `model` | `String`, `nil` | `nil` | LLM model ID (falls back to `RobotLab.config.ruby_llm.model`) |
55
+ | `mcp_servers` | `Array` | `[]` | Legacy MCP server configurations |
56
+ | `mcp` | `Symbol`, `Array` | `:none` | Hierarchical MCP config (`:none`, `:inherit`, or server array) |
57
+ | `tools` | `Symbol`, `Array` | `:none` | Hierarchical tools config (`:none`, `:inherit`, or tool name array) |
58
+ | `on_tool_call` | `Proc`, `nil` | `nil` | Callback invoked when a tool is called |
59
+ | `on_tool_result` | `Proc`, `nil` | `nil` | Callback invoked when a tool returns a result |
60
+ | `enable_cache` | `Boolean` | `true` | Whether to enable semantic caching |
61
+ | `bus` | `TypedBus::MessageBus`, `nil` | `nil` | Optional message bus for inter-robot communication |
62
+ | `config` | `RunConfig`, `nil` | `nil` | Shared config merged with explicit kwargs (see [RunConfig](#runconfig)) |
63
+ | `temperature` | `Float`, `nil` | `nil` | Controls randomness (0.0-1.0) |
64
+ | `top_p` | `Float`, `nil` | `nil` | Nucleus sampling threshold |
65
+ | `top_k` | `Integer`, `nil` | `nil` | Top-k sampling |
66
+ | `max_tokens` | `Integer`, `nil` | `nil` | Maximum tokens in response |
67
+ | `presence_penalty` | `Float`, `nil` | `nil` | Penalize based on presence |
68
+ | `frequency_penalty` | `Float`, `nil` | `nil` | Penalize based on frequency |
69
+ | `stop` | `String`, `Array`, `nil` | `nil` | Stop sequences |
70
+
71
+ When both `config:` and explicit kwargs (e.g., `temperature:`) are provided, explicit kwargs always win.
72
+
73
+ ## Factory Method
74
+
75
+ ```ruby
76
+ robot = RobotLab.build(
77
+ name: "robot", # Defaults to "robot"
78
+ template: nil,
79
+ system_prompt: nil,
80
+ context: {},
81
+ enable_cache: true,
82
+ bus: nil, # Optional TypedBus::MessageBus
83
+ **options # All other Robot.new parameters
84
+ )
85
+ # => RobotLab::Robot
22
86
  ```
23
87
 
24
- Unique identifier for the robot within a network.
88
+ If `name` is omitted, it defaults to `"robot"`.
25
89
 
26
- ### description
90
+ ## Attributes (Read-Only)
91
+
92
+ | Attribute | Type | Description |
93
+ |-----------|------|-------------|
94
+ | `name` | `String` | Unique identifier |
95
+ | `description` | `String`, `nil` | Human-readable description |
96
+ | `template` | `Symbol`, `nil` | Prompt template identifier |
97
+ | `system_prompt` | `String`, `nil` | Inline system prompt |
98
+ | `local_tools` | `Array` | Locally defined tools |
99
+ | `mcp_clients` | `Hash<String, MCP::Client>` | Connected MCP clients, keyed by server name |
100
+ | `mcp_tools` | `Array<Tool>` | Tools discovered from MCP servers |
101
+ | `memory` | `Memory` | Inherent memory (used when standalone, not in network) |
102
+ | `bus` | `TypedBus::MessageBus`, `nil` | Message bus instance (nil if not configured) |
103
+ | `outbox` | `Hash` | Sent messages tracked by composite key with status and replies |
104
+ | `config` | `RunConfig` | Effective RunConfig (merged from constructor kwargs and passed-in config) |
105
+ | `mcp_config` | `Symbol`, `Array` | Build-time MCP configuration (raw, unresolved) |
106
+ | `tools_config` | `Symbol`, `Array` | Build-time tools configuration (raw, unresolved) |
107
+
108
+ ## Attributes (Read-Write)
109
+
110
+ | Attribute | Type | Default | Description |
111
+ |-----------|------|---------|-------------|
112
+ | `input` | `IO`, `nil` | `nil` | Input stream for user interaction (falls back to `$stdin`) |
113
+ | `output` | `IO`, `nil` | `nil` | Output stream for user interaction (falls back to `$stdout`) |
114
+
115
+ Used by tools like [`AskUser`](tool.md#built-in-askuser) that need terminal IO. Set to `StringIO` for testing.
116
+
117
+ ## Methods
118
+
119
+ ### run
27
120
 
28
121
  ```ruby
29
- robot.description # => String
122
+ result = robot.run(message, **kwargs)
123
+ # => RobotResult
30
124
  ```
31
125
 
32
- Human-readable description of what the robot does.
126
+ Primary execution method. Sends a message to the LLM with memory/MCP/tools resolution and returns a `RobotResult`.
127
+
128
+ **Parameters:**
129
+
130
+ | Name | Type | Default | Description |
131
+ |------|------|---------|-------------|
132
+ | `message` | `String` | **required** | The user message to send |
133
+ | `network` | `NetworkRun`, `nil` | `nil` | Network context (passed internally) |
134
+ | `network_memory` | `Memory`, `nil` | `nil` | Shared network memory |
135
+ | `memory` | `Memory`, `Hash`, `nil` | `nil` | Runtime memory to merge |
136
+ | `mcp` | `Symbol`, `Array` | `:none` | Runtime MCP override |
137
+ | `tools` | `Symbol`, `Array` | `:none` | Runtime tools override |
138
+ | `**kwargs` | `Hash` | `{}` | Additional keyword arguments passed to `Agent#ask` |
139
+
140
+ **Returns:** `RobotResult`
141
+
142
+ **Examples:**
143
+
144
+ ```ruby
145
+ # Simple message
146
+ result = robot.run("What is 2+2?")
147
+
148
+ # With runtime memory
149
+ result = robot.run("Summarize the data", memory: { data: report })
150
+
151
+ # With streaming block
152
+ result = robot.run("Tell me a story") { |event| print event.text }
153
+
154
+ # With runtime overrides
155
+ result = robot.run("Help me", mcp: :none, tools: :none)
156
+ ```
33
157
 
34
158
  ### model
35
159
 
36
160
  ```ruby
37
- robot.model # => String
161
+ robot.model # => "claude-sonnet-4" or nil
38
162
  ```
39
163
 
40
- LLM model identifier (e.g., "claude-sonnet-4", "gpt-4o").
164
+ Returns the model ID string. Resolves through the underlying chat object.
41
165
 
42
- ### template
166
+ ### update
43
167
 
44
168
  ```ruby
45
- robot.template # => String
169
+ robot.update(
170
+ template: nil,
171
+ context: nil,
172
+ system_prompt: nil,
173
+ model: nil,
174
+ temperature: nil,
175
+ **kwargs
176
+ )
177
+ # => self
46
178
  ```
47
179
 
48
- System prompt that defines the robot's personality.
180
+ Reconfigure the robot after construction. Returns `self` for chaining.
181
+
182
+ ### with_* Methods (Chaining)
183
+
184
+ All `with_*` methods delegate to the persistent `@chat` and return `self` for chaining:
185
+
186
+ | Method | Description |
187
+ |--------|-------------|
188
+ | `with_model(model_id)` | Change the LLM model |
189
+ | `with_temperature(temp)` | Set temperature |
190
+ | `with_top_p(value)` | Set nucleus sampling |
191
+ | `with_top_k(value)` | Set top-k sampling |
192
+ | `with_max_tokens(value)` | Set max response tokens |
193
+ | `with_presence_penalty(value)` | Set presence penalty |
194
+ | `with_frequency_penalty(value)` | Set frequency penalty |
195
+ | `with_stop(sequences)` | Set stop sequences |
196
+ | `with_instructions(prompt)` | Set system instructions |
197
+ | `with_tool(tool)` | Add a single tool |
198
+ | `with_tools(*tools)` | Add multiple tools |
199
+ | `with_params(**params)` | Set additional parameters |
200
+ | `with_headers(**headers)` | Set custom headers |
201
+ | `with_schema(schema)` | Set output schema |
202
+ | `with_context(**ctx)` | Set context |
203
+ | `with_thinking(opts)` | Enable extended thinking |
204
+ | `with_bus(bus)` | Connect to a message bus (creates one if nil) |
205
+
206
+ **Example:**
207
+
208
+ ```ruby
209
+ robot = RobotLab.build(name: "bot")
210
+ robot
211
+ .with_model("claude-sonnet-4")
212
+ .with_temperature(0.7)
213
+ .with_instructions("Be concise.")
214
+ .run("Hello")
215
+ ```
49
216
 
50
- ### local_tools
217
+ ### with_template
51
218
 
52
219
  ```ruby
53
- robot.local_tools # => Array<Tool>
220
+ robot.with_template(:assistant, tone: "friendly")
221
+ # => self
54
222
  ```
55
223
 
56
- Tools defined directly on the robot.
224
+ Apply a prompt_manager template. Separate from the delegated `with_*` methods because it handles template parsing and front matter config.
57
225
 
58
- ### mcp_clients
226
+ ### call
59
227
 
60
228
  ```ruby
61
- robot.mcp_clients # => Array<MCP::Client>
229
+ robot.call(result)
230
+ # => SimpleFlow::Result
62
231
  ```
63
232
 
64
- Connected MCP server clients.
233
+ SimpleFlow step interface. Extracts the message from `result.context[:run_params]`, calls `run`, and wraps the output in a continued `SimpleFlow::Result`.
65
234
 
66
- ### mcp_tools
235
+ Override this method in subclasses for custom routing logic (e.g., classifiers).
236
+
237
+ ### reset_memory
67
238
 
68
239
  ```ruby
69
- robot.mcp_tools # => Array<Tool>
240
+ robot.reset_memory
241
+ # => self
70
242
  ```
71
243
 
72
- Tools discovered from MCP servers.
244
+ Reset the robot's inherent memory to its initial state.
73
245
 
74
- ### mcp_config
246
+ ### send_message
75
247
 
76
248
  ```ruby
77
- robot.mcp_config # => Symbol | Array
249
+ message = robot.send_message(to: :bob, content: "Tell me a joke.")
250
+ # => RobotMessage
78
251
  ```
79
252
 
80
- MCP configuration (`:inherit`, `:none`, or server array).
253
+ Publish a message to another robot's bus channel. Increments the internal message counter, creates a `RobotMessage`, tracks it in the outbox, and publishes to the target channel.
254
+
255
+ **Parameters:**
81
256
 
82
- ### tools_config
257
+ | Name | Type | Description |
258
+ |------|------|-------------|
259
+ | `to` | `String`, `Symbol` | Target robot's channel name |
260
+ | `content` | `String`, `Hash` | Message payload |
261
+
262
+ **Returns:** `RobotMessage`
263
+
264
+ **Raises:** `BusError` if no bus is configured.
265
+
266
+ ### send_reply
83
267
 
84
268
  ```ruby
85
- robot.tools_config # => Symbol | Array
269
+ reply = robot.send_reply(to: :alice, content: "Here's a joke...", in_reply_to: "alice:1")
270
+ # => RobotMessage
86
271
  ```
87
272
 
88
- Tools whitelist configuration (`:inherit`, `:none`, or tool names).
273
+ Publish a correlated reply to a specific message. The `in_reply_to` composite key links this reply to the original message.
89
274
 
90
- ## Methods
275
+ **Parameters:**
276
+
277
+ | Name | Type | Description |
278
+ |------|------|-------------|
279
+ | `to` | `String`, `Symbol` | Target robot's channel name |
280
+ | `content` | `String`, `Hash` | Reply payload |
281
+ | `in_reply_to` | `String` | Composite key of the original message (e.g., `"alice:1"`) |
91
282
 
92
- ### tools
283
+ **Returns:** `RobotMessage`
284
+
285
+ **Raises:** `BusError` if no bus is configured.
286
+
287
+ ### on_message
93
288
 
94
289
  ```ruby
95
- robot.tools # => Array<Tool>
290
+ robot.on_message { |message| puts message.content }
291
+ # => self
96
292
  ```
97
293
 
98
- Returns all available tools (local + MCP, filtered by whitelist).
294
+ Register a custom handler for incoming bus messages. Block arity controls delivery handling:
99
295
 
100
- ### run
296
+ - **1 argument** `|message|` — auto-acknowledges the delivery before calling the block
297
+ - **2 arguments** `|delivery, message|` — manual mode; you call `delivery.ack!` or `delivery.nack!`
298
+
299
+ **Examples:**
101
300
 
102
301
  ```ruby
103
- result = robot.run(
104
- state: state,
105
- network: network,
106
- streaming: nil,
107
- **context
302
+ # Auto-ack mode (1 arg)
303
+ robot.on_message do |message|
304
+ joke = run(message.content.to_s).last_text_content
305
+ send_reply(to: message.from.to_sym, content: joke, in_reply_to: message.key)
306
+ end
307
+
308
+ # Manual mode (2 args)
309
+ robot.on_message do |delivery, message|
310
+ if message.content.to_s.length > 10
311
+ delivery.ack!
312
+ send_reply(to: message.from.to_sym, content: "Got it!", in_reply_to: message.key)
313
+ else
314
+ delivery.nack!
315
+ end
316
+ end
317
+ ```
318
+
319
+ ### spawn
320
+
321
+ ```ruby
322
+ child = robot.spawn(
323
+ name: "specialist",
324
+ system_prompt: "You are a specialist."
108
325
  )
109
- # => RobotResult
326
+ # => RobotLab::Robot (connected to same bus)
110
327
  ```
111
328
 
112
- Execute the robot with the given state.
329
+ Create a new robot on the same message bus. If the parent has no bus, one is created automatically and the parent is connected to it.
113
330
 
114
331
  **Parameters:**
115
332
 
116
- | Name | Type | Description |
117
- |------|------|-------------|
118
- | `state` | `State` | Conversation state |
119
- | `network` | `Network`, `NetworkRun`, `nil` | Network context |
120
- | `streaming` | `Proc`, `nil` | Streaming callback |
121
- | `**context` | `Hash` | Additional context |
333
+ | Name | Type | Default | Description |
334
+ |------|------|---------|-------------|
335
+ | `name` | `String` | `"robot"` | Name for the new robot |
336
+ | `system_prompt` | `String`, `nil` | `nil` | Inline system prompt |
337
+ | `template` | `Symbol`, `nil` | `nil` | Prompt template |
338
+ | `local_tools` | `Array` | `[]` | Tools for the new robot |
339
+ | `**options` | `Hash` | `{}` | Additional options passed to `RobotLab.build` |
122
340
 
123
- **Returns:** `RobotResult`
341
+ **Returns:** `Robot`
124
342
 
125
- ### disconnect
343
+ **Examples:**
126
344
 
127
345
  ```ruby
128
- robot.disconnect
129
- ```
346
+ # Minimal spawn (bus created automatically)
347
+ bot = RobotLab.build
348
+ bot2 = bot.spawn(system_prompt: "You are helpful.")
349
+
350
+ # Spawn with template
351
+ specialist = dispatcher.spawn(
352
+ name: "billing",
353
+ template: :billing,
354
+ local_tools: [InvoiceLookup]
355
+ )
130
356
 
131
- Disconnect from all MCP servers.
357
+ # Fan-out: multiple robots with the same name
358
+ worker1 = bot.spawn(name: "worker", system_prompt: "Worker 1")
359
+ worker2 = bot.spawn(name: "worker", system_prompt: "Worker 2")
360
+ # Messages sent to :worker are delivered to both
361
+ ```
132
362
 
133
- ### to_h
363
+ ### with_bus
134
364
 
135
365
  ```ruby
136
- robot.to_h # => Hash
366
+ robot.with_bus(bus)
367
+ # => self
137
368
  ```
138
369
 
139
- Returns hash representation of the robot.
370
+ Connect the robot to a message bus after creation. If called without an argument and the robot has no bus, a new one is created. Returns `self` for chaining.
371
+
372
+ **Parameters:**
140
373
 
141
- ## Builder DSL
374
+ | Name | Type | Default | Description |
375
+ |------|------|---------|-------------|
376
+ | `bus` | `TypedBus::MessageBus`, `nil` | `nil` | Bus to join (creates one if nil and robot has no bus) |
142
377
 
143
- ### name
378
+ **Returns:** `self`
379
+
380
+ **Examples:**
144
381
 
145
382
  ```ruby
146
- name "my_robot"
147
- ```
383
+ # Join an existing bus
384
+ bot = RobotLab.build(name: "bot")
385
+ bot.with_bus(some_bus)
148
386
 
149
- Set the robot's name.
387
+ # Create a bus on demand
388
+ bot = RobotLab.build(name: "bot").with_bus
150
389
 
151
- ### description
390
+ # Switch buses
391
+ bot.with_bus(bus1) # joins bus1
392
+ bot.with_bus(bus2) # leaves bus1, joins bus2
393
+ ```
394
+
395
+ ### disconnect
152
396
 
153
397
  ```ruby
154
- description "Handles customer inquiries"
398
+ robot.disconnect
399
+ # => self
155
400
  ```
156
401
 
157
- Set the robot's description.
402
+ Disconnect from all MCP servers and bus channels.
158
403
 
159
- ### model
404
+ ### to_h
160
405
 
161
406
  ```ruby
162
- model "claude-sonnet-4"
407
+ robot.to_h
408
+ # => Hash
163
409
  ```
164
410
 
165
- Set the LLM model.
411
+ Returns a hash representation of the robot including name, description, template, system_prompt, local_tools, mcp_tools, mcp_config, tools_config, mcp_servers, model, and bus (true if configured, omitted otherwise).
166
412
 
167
- ### template
413
+ ## Memory Behavior
168
414
 
169
- ```ruby
170
- # Inline template
171
- template "You are a helpful assistant."
415
+ - **Standalone**: Robot uses its own inherent `Memory` instance (`robot.memory`).
416
+ - **In a Network**: Robot uses the network's shared memory (passed via `network_memory:`).
172
417
 
173
- # Template file (loads from template_path)
174
- template "support/system_prompt"
418
+ ```ruby
419
+ # Standalone memory access
420
+ robot.memory[:user_id] = 123
421
+ robot.memory[:user_id] # => 123
175
422
 
176
- # Template file with variables
177
- template "support/system_prompt", company: "Acme"
423
+ # Reset standalone memory
424
+ robot.reset_memory
178
425
  ```
179
426
 
180
- Set the system prompt.
427
+ ## Templates
181
428
 
182
- ### tool
429
+ Templates are `.md` files with optional YAML front matter, loaded via `prompt_manager`. The `template:` parameter maps to a file path relative to the configured template directory:
183
430
 
184
431
  ```ruby
185
- tool :tool_name do
186
- description "What the tool does"
187
- parameter :param, type: :string, required: true
188
- handler { |param:, **_| do_something(param) }
189
- end
432
+ # template: :assistant => prompts/assistant.md
433
+ robot = RobotLab.build(name: "bot", template: :assistant, context: { tone: "friendly" })
190
434
  ```
191
435
 
192
- Define a tool for the robot.
436
+ Front matter supports two categories of keys:
437
+
438
+ **LLM Config:** `model`, `temperature`, `top_p`, `top_k`, `max_tokens`, `presence_penalty`, `frequency_penalty`, `stop` — applied to the underlying chat.
439
+
440
+ **Robot Extras:** `robot_name`, `description`, `tools`, `mcp` — applied to the robot's identity and capabilities. Constructor-provided values always take precedence.
441
+
442
+ | Key | Type | Description |
443
+ |-----|------|-------------|
444
+ | `robot_name` | `String` | Override robot name (when constructor uses the default `"robot"`) |
445
+ | `description` | `String` | Human-readable description |
446
+ | `tools` | `Array<String>` | Tool class names resolved via `Object.const_get` |
447
+ | `mcp` | `Array<Hash>` | MCP server configurations |
193
448
 
194
- ### mcp
449
+ ## RunConfig
450
+
451
+ `RunConfig` provides shared operational defaults that flow through the configuration hierarchy. Pass it via the `config:` parameter on `Robot.new` or `RobotLab.build`.
195
452
 
196
453
  ```ruby
197
- mcp :inherit # Use network's MCP servers
198
- mcp :none # No MCP servers
199
- mcp [ # Specific servers
200
- { name: "fs", transport: { type: "stdio", command: "mcp-fs" } }
201
- ]
454
+ shared = RobotLab::RunConfig.new(model: "claude-sonnet-4", temperature: 0.7)
455
+
456
+ robot = RobotLab.build(
457
+ name: "writer",
458
+ system_prompt: "You write creatively.",
459
+ config: shared,
460
+ temperature: 0.9 # explicit kwargs override config
461
+ )
462
+
463
+ robot.config #=> RunConfig with model: "claude-sonnet-4", temperature: 0.9, ...
202
464
  ```
203
465
 
204
- Configure MCP servers.
466
+ RunConfig fields: `model`, `temperature`, `top_p`, `top_k`, `max_tokens`, `presence_penalty`, `frequency_penalty`, `stop`, `mcp`, `tools`, `on_tool_call`, `on_tool_result`, `bus`, `enable_cache`.
205
467
 
206
- ### tools (whitelist)
468
+ See [Configuration: RunConfig](../../getting-started/configuration.md#runconfig-shared-operational-defaults) for full details.
469
+
470
+ ## Configuration Hierarchy
471
+
472
+ Tools and MCP servers use hierarchical resolution: **runtime > robot > network > global config**.
207
473
 
208
- ```ruby
209
- tools :inherit # Use network's tools
210
- tools :none # No inherited tools
211
- tools %w[read_file write_file] # Only these tools
212
474
  ```
475
+ RobotLab.config (global)
476
+ |
477
+ +-- Network (config:)
478
+ | |
479
+ | +-- Task (config:)
480
+ | | |
481
+ | | +-- Robot (config: + build-time mcp:, tools:)
482
+ | | |
483
+ | | +-- Template front matter
484
+ | | |
485
+ | | +-- run() call (runtime mcp:, tools:)
486
+ ```
487
+
488
+ Values at each level:
213
489
 
214
- Configure tool whitelist.
490
+ - `:none` -- no tools/MCP at this level
491
+ - `:inherit` -- inherit from parent level
492
+ - `Array` -- explicit list of tool names or MCP server configs
215
493
 
216
494
  ## Examples
217
495
 
218
496
  ### Basic Robot
219
497
 
220
498
  ```ruby
221
- robot = RobotLab.build do
222
- name "greeter"
223
- template "You greet users warmly."
224
- end
499
+ robot = RobotLab.build(
500
+ name: "greeter",
501
+ system_prompt: "You greet users warmly."
502
+ )
503
+ result = robot.run("Hello!")
504
+ puts result.last_text_content
225
505
  ```
226
506
 
227
- ### Robot with Tools
507
+ ### Robot with Template
228
508
 
229
509
  ```ruby
230
- robot = RobotLab.build do
231
- name "calculator"
232
- model "claude-sonnet-4"
233
- template "You help with math problems."
234
-
235
- tool :add do
236
- description "Add two numbers"
237
- parameter :a, type: :number, required: true
238
- parameter :b, type: :number, required: true
239
- handler { |a:, b:, **_| a + b }
240
- end
510
+ robot = RobotLab.build(
511
+ name: "support",
512
+ template: :support,
513
+ context: { company: "Acme Corp" }
514
+ )
515
+ result = robot.run("I need help with my order")
516
+ ```
517
+
518
+ ### Robot with Tools
241
519
 
242
- tool :multiply do
243
- description "Multiply two numbers"
244
- parameter :a, type: :number, required: true
245
- parameter :b, type: :number, required: true
246
- handler { |a:, b:, **_| a * b }
520
+ ```ruby
521
+ class Calculator < RubyLLM::Tool
522
+ description "Performs basic arithmetic"
523
+ param :operation, type: "string", desc: "add, subtract, multiply, divide"
524
+ param :a, type: "number", desc: "First operand"
525
+ param :b, type: "number", desc: "Second operand"
526
+
527
+ def execute(operation:, a:, b:)
528
+ case operation
529
+ when "add" then a + b
530
+ when "subtract" then a - b
531
+ when "multiply" then a * b
532
+ when "divide" then a.to_f / b
533
+ end
247
534
  end
248
535
  end
536
+
537
+ robot = RobotLab.build(
538
+ name: "math_bot",
539
+ system_prompt: "You help with math.",
540
+ local_tools: [Calculator]
541
+ )
542
+ result = robot.run("What is 15 * 7?")
249
543
  ```
250
544
 
251
545
  ### Robot with MCP
252
546
 
253
547
  ```ruby
254
- robot = RobotLab.build do
255
- name "developer"
256
- template "You help with coding tasks."
257
-
258
- mcp [
548
+ robot = RobotLab.build(
549
+ name: "developer",
550
+ system_prompt: "You help with coding tasks.",
551
+ mcp: [
259
552
  {
260
553
  name: "github",
261
- transport: { type: "stdio", command: "mcp-server-github" }
554
+ transport: { type: "stdio", command: "github-mcp-server", args: ["stdio"] }
262
555
  }
263
556
  ]
557
+ )
558
+ result = robot.run("Search for popular Ruby repos")
559
+ robot.disconnect
560
+ ```
561
+
562
+ ### Bare Robot with Chaining
563
+
564
+ ```ruby
565
+ robot = RobotLab.build(name: "bot")
566
+ result = robot
567
+ .with_instructions("Be concise.")
568
+ .with_temperature(0.3)
569
+ .run("Explain quantum computing")
570
+ ```
571
+
572
+ ### Robot with Message Bus
573
+
574
+ ```ruby
575
+ bus = TypedBus::MessageBus.new
576
+
577
+ bob = RobotLab.build(name: "bob", system_prompt: "You tell jokes.", bus: bus)
578
+
579
+ alice = RobotLab.build(name: "alice", system_prompt: "You evaluate jokes.", bus: bus)
580
+ alice.on_message do |message|
581
+ verdict = alice.run("Is this funny? #{message.content}").last_text_content
582
+ puts verdict
583
+ end
264
584
 
265
- tools %w[search_repositories create_issue]
585
+ bob.on_message do |message|
586
+ joke = bob.run(message.content.to_s).last_text_content
587
+ bob.send_reply(to: message.from.to_sym, content: joke, in_reply_to: message.key)
266
588
  end
589
+
590
+ alice.send_message(to: :bob, content: "Tell me a robot joke.")
591
+ ```
592
+
593
+ ### Spawning Robots Dynamically
594
+
595
+ ```ruby
596
+ # Parent robot spawns specialists on demand
597
+ dispatcher = RobotLab.build(
598
+ name: "dispatcher",
599
+ system_prompt: "You delegate work."
600
+ )
601
+
602
+ dispatcher.on_message do |message|
603
+ puts "Reply from #{message.from}: #{message.content}"
604
+ end
605
+
606
+ # spawn creates child on same bus (bus created lazily)
607
+ helper = dispatcher.spawn(
608
+ name: "helper",
609
+ system_prompt: "You answer questions concisely."
610
+ )
611
+
612
+ answer = helper.run("What is 2+2?").last_text_content
613
+ helper.send_message(to: :dispatcher, content: answer)
614
+ ```
615
+
616
+ ### Connecting to a Bus After Creation
617
+
618
+ ```ruby
619
+ bot = RobotLab.build(name: "latecomer", system_prompt: "Hi there.")
620
+
621
+ # Join a bus later
622
+ bus = TypedBus::MessageBus.new
623
+ bot.with_bus(bus)
624
+
625
+ # Now bot can send/receive messages
626
+ bot.send_message(to: :someone, content: "Hello!")
267
627
  ```
268
628
 
269
629
  ## See Also