robot_lab 0.0.1 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/deploy-github-pages.yml +9 -9
- data/.irbrc +6 -0
- data/CHANGELOG.md +90 -0
- data/README.md +203 -46
- data/Rakefile +70 -1
- data/docs/api/core/index.md +12 -0
- data/docs/api/core/robot.md +478 -130
- data/docs/api/core/tool.md +205 -209
- data/docs/api/history/active-record-adapter.md +174 -94
- data/docs/api/history/config.md +186 -93
- data/docs/api/history/index.md +57 -61
- data/docs/api/history/thread-manager.md +123 -73
- data/docs/api/mcp/client.md +119 -48
- data/docs/api/mcp/index.md +75 -60
- data/docs/api/mcp/server.md +120 -136
- data/docs/api/mcp/transports.md +172 -184
- data/docs/api/streaming/context.md +157 -74
- data/docs/api/streaming/events.md +114 -166
- data/docs/api/streaming/index.md +74 -72
- data/docs/architecture/core-concepts.md +361 -112
- data/docs/architecture/index.md +97 -59
- data/docs/architecture/message-flow.md +138 -129
- data/docs/architecture/network-orchestration.md +197 -50
- data/docs/architecture/robot-execution.md +199 -146
- data/docs/architecture/state-management.md +255 -187
- data/docs/concepts.md +312 -48
- data/docs/examples/basic-chat.md +89 -77
- data/docs/examples/index.md +222 -47
- data/docs/examples/mcp-server.md +207 -203
- data/docs/examples/multi-robot-network.md +129 -35
- data/docs/examples/rails-application.md +159 -160
- data/docs/examples/tool-usage.md +295 -204
- data/docs/getting-started/configuration.md +275 -162
- data/docs/getting-started/index.md +1 -1
- data/docs/getting-started/installation.md +22 -13
- data/docs/getting-started/quick-start.md +166 -121
- data/docs/guides/building-robots.md +417 -212
- data/docs/guides/creating-networks.md +94 -24
- data/docs/guides/mcp-integration.md +152 -113
- data/docs/guides/memory.md +220 -164
- data/docs/guides/streaming.md +80 -110
- data/docs/guides/using-tools.md +259 -212
- data/docs/index.md +50 -37
- data/examples/01_simple_robot.rb +6 -9
- data/examples/02_tools.rb +6 -9
- data/examples/03_network.rb +13 -14
- data/examples/04_mcp.rb +5 -8
- data/examples/05_streaming.rb +5 -8
- data/examples/06_prompt_templates.rb +42 -37
- data/examples/07_network_memory.rb +13 -14
- data/examples/08_llm_config.rb +140 -0
- data/examples/09_chaining.rb +223 -0
- data/examples/10_memory.rb +331 -0
- data/examples/11_network_introspection.rb +230 -0
- data/examples/12_message_bus.rb +74 -0
- data/examples/13_spawn.rb +90 -0
- data/examples/14_rusty_circuit/comic.rb +143 -0
- data/examples/14_rusty_circuit/display.rb +203 -0
- data/examples/14_rusty_circuit/heckler.rb +57 -0
- data/examples/14_rusty_circuit/open_mic.rb +121 -0
- data/examples/14_rusty_circuit/prompts/open_mic_comic.md +20 -0
- data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +23 -0
- data/examples/14_rusty_circuit/prompts/open_mic_scout.md +20 -0
- data/examples/14_rusty_circuit/scout.rb +173 -0
- data/examples/14_rusty_circuit/scout_notes.md +89 -0
- data/examples/14_rusty_circuit/show.log +234 -0
- data/examples/15_memory_network_and_bus/editor_in_chief.rb +24 -0
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +206 -0
- data/examples/15_memory_network_and_bus/linux_writer.rb +80 -0
- data/examples/15_memory_network_and_bus/os_editor.rb +46 -0
- data/examples/15_memory_network_and_bus/os_writer.rb +46 -0
- data/examples/15_memory_network_and_bus/output/combined_article.md +13 -0
- data/examples/15_memory_network_and_bus/output/final_article.md +15 -0
- data/examples/15_memory_network_and_bus/output/linux_draft.md +5 -0
- data/examples/15_memory_network_and_bus/output/mac_draft.md +7 -0
- data/examples/15_memory_network_and_bus/output/memory.json +13 -0
- data/examples/15_memory_network_and_bus/output/revision_1.md +19 -0
- data/examples/15_memory_network_and_bus/output/revision_2.md +15 -0
- data/examples/15_memory_network_and_bus/output/windows_draft.md +7 -0
- data/examples/15_memory_network_and_bus/prompts/os_advocate.md +13 -0
- data/examples/15_memory_network_and_bus/prompts/os_chief.md +13 -0
- data/examples/15_memory_network_and_bus/prompts/os_editor.md +13 -0
- data/examples/README.md +197 -0
- data/examples/prompts/{assistant/system.txt.erb → assistant.md} +3 -0
- data/examples/prompts/{billing/system.txt.erb → billing.md} +3 -0
- data/examples/prompts/{classifier/system.txt.erb → classifier.md} +3 -0
- data/examples/prompts/comedian.md +6 -0
- data/examples/prompts/comedy_critic.md +10 -0
- data/examples/prompts/configurable.md +9 -0
- data/examples/prompts/dispatcher.md +12 -0
- data/examples/prompts/{entity_extractor/system.txt.erb → entity_extractor.md} +3 -0
- data/examples/prompts/{escalation/system.txt.erb → escalation.md} +7 -0
- data/examples/prompts/frontmatter_mcp_test.md +9 -0
- data/examples/prompts/frontmatter_named_test.md +5 -0
- data/examples/prompts/frontmatter_tools_test.md +6 -0
- data/examples/prompts/{general/system.txt.erb → general.md} +3 -0
- data/examples/prompts/{github_assistant/system.txt.erb → github_assistant.md} +8 -0
- data/examples/prompts/{helper/system.txt.erb → helper.md} +3 -0
- data/examples/prompts/{keyword_extractor/system.txt.erb → keyword_extractor.md} +3 -0
- data/examples/prompts/llm_config_demo.md +20 -0
- data/examples/prompts/{order_support/system.txt.erb → order_support.md} +8 -0
- data/examples/prompts/os_advocate.md +13 -0
- data/examples/prompts/os_chief.md +13 -0
- data/examples/prompts/os_editor.md +13 -0
- data/examples/prompts/{product_support/system.txt.erb → product_support.md} +7 -0
- data/examples/prompts/{sentiment_analyzer/system.txt.erb → sentiment_analyzer.md} +3 -0
- data/examples/prompts/{synthesizer/system.txt.erb → synthesizer.md} +3 -0
- data/examples/prompts/{technical/system.txt.erb → technical.md} +3 -0
- data/examples/prompts/{triage/system.txt.erb → triage.md} +6 -0
- data/lib/generators/robot_lab/templates/initializer.rb.tt +1 -1
- data/lib/robot_lab/adapters/openai.rb +2 -1
- data/lib/robot_lab/ask_user.rb +75 -0
- data/lib/robot_lab/config/defaults.yml +121 -0
- data/lib/robot_lab/config.rb +183 -0
- data/lib/robot_lab/error.rb +6 -0
- data/lib/robot_lab/mcp/client.rb +1 -1
- data/lib/robot_lab/memory.rb +2 -2
- data/lib/robot_lab/robot.rb +523 -249
- data/lib/robot_lab/robot_message.rb +44 -0
- data/lib/robot_lab/robot_result.rb +1 -0
- data/lib/robot_lab/robotic_model.rb +1 -1
- data/lib/robot_lab/streaming/context.rb +1 -1
- data/lib/robot_lab/tool.rb +108 -172
- data/lib/robot_lab/tool_config.rb +1 -1
- data/lib/robot_lab/tool_manifest.rb +2 -18
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab.rb +66 -55
- metadata +107 -116
- data/examples/prompts/assistant/user.txt.erb +0 -1
- data/examples/prompts/billing/user.txt.erb +0 -1
- data/examples/prompts/classifier/user.txt.erb +0 -1
- data/examples/prompts/entity_extractor/user.txt.erb +0 -3
- data/examples/prompts/escalation/user.txt.erb +0 -34
- data/examples/prompts/general/user.txt.erb +0 -1
- data/examples/prompts/github_assistant/user.txt.erb +0 -1
- data/examples/prompts/helper/user.txt.erb +0 -1
- data/examples/prompts/keyword_extractor/user.txt.erb +0 -3
- data/examples/prompts/order_support/user.txt.erb +0 -22
- data/examples/prompts/product_support/user.txt.erb +0 -32
- data/examples/prompts/sentiment_analyzer/user.txt.erb +0 -3
- data/examples/prompts/synthesizer/user.txt.erb +0 -15
- data/examples/prompts/technical/user.txt.erb +0 -1
- data/examples/prompts/triage/user.txt.erb +0 -17
- data/lib/robot_lab/configuration.rb +0 -143
|
@@ -4,24 +4,26 @@ This guide covers everything you need to know about creating robots in RobotLab.
|
|
|
4
4
|
|
|
5
5
|
## Basic Robot
|
|
6
6
|
|
|
7
|
-
Create a
|
|
7
|
+
Create a robot using the `RobotLab.build` factory method with keyword arguments:
|
|
8
8
|
|
|
9
9
|
```ruby
|
|
10
|
-
robot = RobotLab.build
|
|
11
|
-
name "assistant"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
robot = RobotLab.build(
|
|
11
|
+
name: "assistant",
|
|
12
|
+
system_prompt: "You are a helpful assistant."
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
result = robot.run("Hello!")
|
|
16
|
+
puts result.last_text_content
|
|
15
17
|
```
|
|
16
18
|
|
|
17
19
|
## Robot Properties
|
|
18
20
|
|
|
19
21
|
### Name
|
|
20
22
|
|
|
21
|
-
A unique identifier used for routing and logging:
|
|
23
|
+
A unique identifier used for routing and logging. If omitted, an auto-generated name is used:
|
|
22
24
|
|
|
23
25
|
```ruby
|
|
24
|
-
name "support_agent"
|
|
26
|
+
robot = RobotLab.build(name: "support_agent", system_prompt: "...")
|
|
25
27
|
```
|
|
26
28
|
|
|
27
29
|
### Description
|
|
@@ -29,348 +31,551 @@ name "support_agent"
|
|
|
29
31
|
Describes what the robot does (useful for routing decisions):
|
|
30
32
|
|
|
31
33
|
```ruby
|
|
32
|
-
|
|
34
|
+
robot = RobotLab.build(
|
|
35
|
+
name: "support_agent",
|
|
36
|
+
description: "Handles customer support inquiries about orders and refunds",
|
|
37
|
+
system_prompt: "..."
|
|
38
|
+
)
|
|
33
39
|
```
|
|
34
40
|
|
|
35
41
|
### Model
|
|
36
42
|
|
|
37
|
-
The LLM model to use
|
|
43
|
+
The LLM model to use. Defaults to the value in `RobotLab.config.ruby_llm.model`:
|
|
38
44
|
|
|
39
45
|
```ruby
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
model "
|
|
46
|
+
robot = RobotLab.build(
|
|
47
|
+
name: "writer",
|
|
48
|
+
model: "claude-sonnet-4",
|
|
49
|
+
system_prompt: "You are a creative writer."
|
|
50
|
+
)
|
|
43
51
|
```
|
|
44
52
|
|
|
45
|
-
###
|
|
53
|
+
### System Prompt
|
|
46
54
|
|
|
47
|
-
|
|
55
|
+
An inline string that defines the robot's personality and behavior:
|
|
48
56
|
|
|
49
57
|
```ruby
|
|
50
|
-
|
|
51
|
-
|
|
58
|
+
robot = RobotLab.build(
|
|
59
|
+
name: "support",
|
|
60
|
+
system_prompt: <<~PROMPT
|
|
61
|
+
You are a customer support specialist for TechCo.
|
|
52
62
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
63
|
+
Your responsibilities:
|
|
64
|
+
- Answer questions about products and services
|
|
65
|
+
- Help resolve order issues
|
|
66
|
+
- Provide friendly, professional assistance
|
|
57
67
|
|
|
58
|
-
|
|
59
|
-
PROMPT
|
|
68
|
+
Always be polite and acknowledge the customer's concerns.
|
|
69
|
+
PROMPT
|
|
70
|
+
)
|
|
60
71
|
```
|
|
61
72
|
|
|
62
|
-
##
|
|
73
|
+
## Template Files
|
|
63
74
|
|
|
64
|
-
|
|
75
|
+
Templates are `.md` files managed by [prompt_manager](https://github.com/MadBomber/prompt_manager). Reference a template by symbol; RobotLab resolves it through the configured template path.
|
|
65
76
|
|
|
66
77
|
```ruby
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
Order.find_by(id: order_id)&.to_h || { error: "Order not found" }
|
|
75
|
-
end
|
|
76
|
-
end
|
|
78
|
+
# Reference template by symbol (loads prompts/support.md)
|
|
79
|
+
robot = RobotLab.build(
|
|
80
|
+
name: "support",
|
|
81
|
+
template: :support,
|
|
82
|
+
context: { company: "TechCo", tone: "friendly" }
|
|
83
|
+
)
|
|
84
|
+
```
|
|
77
85
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
### Template Format
|
|
87
|
+
|
|
88
|
+
Templates use `.md` files with YAML front matter:
|
|
89
|
+
|
|
90
|
+
```markdown title="prompts/support.md"
|
|
91
|
+
---
|
|
92
|
+
description: Customer support assistant
|
|
93
|
+
parameters:
|
|
94
|
+
company: "Acme"
|
|
95
|
+
tone: "professional"
|
|
96
|
+
model: claude-sonnet-4
|
|
97
|
+
temperature: 0.7
|
|
98
|
+
---
|
|
99
|
+
You are a support agent for <%= company %>.
|
|
100
|
+
Your tone should be <%= tone %>.
|
|
87
101
|
```
|
|
88
102
|
|
|
89
|
-
###
|
|
103
|
+
### Front Matter Configuration
|
|
104
|
+
|
|
105
|
+
The following YAML front matter keys are applied to the robot's chat automatically:
|
|
106
|
+
|
|
107
|
+
**LLM Configuration:**
|
|
108
|
+
|
|
109
|
+
| Key | Description |
|
|
110
|
+
|-----|-------------|
|
|
111
|
+
| `model` | Override the LLM model |
|
|
112
|
+
| `temperature` | Controls randomness (0.0 - 1.0) |
|
|
113
|
+
| `top_p` | Nucleus sampling threshold |
|
|
114
|
+
| `top_k` | Top-k sampling |
|
|
115
|
+
| `max_tokens` | Maximum tokens in response |
|
|
116
|
+
| `presence_penalty` | Penalize based on presence |
|
|
117
|
+
| `frequency_penalty` | Penalize based on frequency |
|
|
118
|
+
| `stop` | Stop sequences |
|
|
119
|
+
|
|
120
|
+
**Robot Identity and Capabilities:**
|
|
121
|
+
|
|
122
|
+
| Key | Description |
|
|
123
|
+
|-----|-------------|
|
|
124
|
+
| `robot_name` | Override the robot's name (when constructor uses the default) |
|
|
125
|
+
| `description` | Human-readable description of the robot |
|
|
126
|
+
| `tools` | Array of tool class names (resolved via `Object.const_get`) |
|
|
127
|
+
| `mcp` | Array of MCP server configurations |
|
|
128
|
+
|
|
129
|
+
Constructor-provided values always take precedence over frontmatter values.
|
|
130
|
+
|
|
131
|
+
### Self-Contained Templates
|
|
132
|
+
|
|
133
|
+
Templates can declare everything a robot needs — identity, tools, MCP servers, and LLM config — making the `.md` file a complete robot definition:
|
|
134
|
+
|
|
135
|
+
```markdown title="prompts/github_assistant.md"
|
|
136
|
+
---
|
|
137
|
+
description: GitHub assistant with MCP tool access
|
|
138
|
+
robot_name: github_bot
|
|
139
|
+
mcp:
|
|
140
|
+
- name: github
|
|
141
|
+
transport: stdio
|
|
142
|
+
command: npx
|
|
143
|
+
args: ["-y", "@modelcontextprotocol/server-github"]
|
|
144
|
+
model: claude-sonnet-4
|
|
145
|
+
temperature: 0.3
|
|
146
|
+
---
|
|
147
|
+
You are a helpful GitHub assistant with access to GitHub tools via MCP.
|
|
148
|
+
Use the available tools to help answer questions about GitHub repositories.
|
|
149
|
+
```
|
|
90
150
|
|
|
91
|
-
|
|
151
|
+
Build the robot with minimal constructor arguments:
|
|
92
152
|
|
|
93
153
|
```ruby
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
parameter :limit, type: :integer, default: 10, description: "Max results"
|
|
97
|
-
parameter :category, type: :string, enum: %w[books movies music]
|
|
98
|
-
end
|
|
154
|
+
# Template provides name, description, MCP config, model, and temperature
|
|
155
|
+
robot = RobotLab.build(template: :github_assistant)
|
|
99
156
|
```
|
|
100
157
|
|
|
101
|
-
|
|
102
|
-
|--------|-------------|
|
|
103
|
-
| `type` | Parameter type (`:string`, `:integer`, `:boolean`, `:number`, `:array`, `:object`) |
|
|
104
|
-
| `required` | Whether the parameter is required |
|
|
105
|
-
| `default` | Default value if not provided |
|
|
106
|
-
| `description` | Description for the LLM |
|
|
107
|
-
| `enum` | List of allowed values |
|
|
158
|
+
### Tools in Front Matter
|
|
108
159
|
|
|
109
|
-
|
|
160
|
+
Declare tool classes by name in the `tools:` key. RobotLab resolves each string to a Ruby constant and instantiates it:
|
|
110
161
|
|
|
111
|
-
|
|
162
|
+
```markdown title="prompts/order_support.md"
|
|
163
|
+
---
|
|
164
|
+
description: Order support specialist
|
|
165
|
+
tools:
|
|
166
|
+
- OrderLookup
|
|
167
|
+
- RefundProcessor
|
|
168
|
+
---
|
|
169
|
+
You help customers with order inquiries and refunds.
|
|
170
|
+
```
|
|
112
171
|
|
|
113
172
|
```ruby
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
# state - Current State
|
|
173
|
+
# Tools are loaded from frontmatter — no local_tools: needed
|
|
174
|
+
robot = RobotLab.build(template: :order_support)
|
|
175
|
+
```
|
|
118
176
|
|
|
119
|
-
|
|
120
|
-
user_id = state.data[:user_id]
|
|
177
|
+
Tool classes must be defined and loaded before the robot is built. If a tool name cannot be resolved, it is skipped with a warning.
|
|
121
178
|
|
|
122
|
-
|
|
123
|
-
state.memory.remember("last_search", param1)
|
|
179
|
+
Constructor `local_tools:` overrides frontmatter `tools:` when provided:
|
|
124
180
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
181
|
+
```ruby
|
|
182
|
+
# Constructor tools take precedence over frontmatter tools
|
|
183
|
+
robot = RobotLab.build(
|
|
184
|
+
template: :order_support,
|
|
185
|
+
local_tools: [OrderLookup] # Only OrderLookup, not RefundProcessor
|
|
186
|
+
)
|
|
128
187
|
```
|
|
129
188
|
|
|
130
|
-
|
|
131
|
-
Use `**_context` to accept but ignore context:
|
|
132
|
-
```ruby
|
|
133
|
-
handler { |query:, **_context| search(query) }
|
|
134
|
-
```
|
|
189
|
+
### MCP in Front Matter
|
|
135
190
|
|
|
136
|
-
|
|
191
|
+
Declare MCP server configurations directly in the template:
|
|
137
192
|
|
|
138
|
-
|
|
193
|
+
```markdown title="prompts/developer.md"
|
|
194
|
+
---
|
|
195
|
+
description: Developer assistant with filesystem access
|
|
196
|
+
mcp:
|
|
197
|
+
- name: filesystem
|
|
198
|
+
transport: stdio
|
|
199
|
+
command: mcp-server-filesystem
|
|
200
|
+
args: ["--root", "/home/user/projects"]
|
|
201
|
+
---
|
|
202
|
+
You are a developer assistant with filesystem access.
|
|
203
|
+
```
|
|
139
204
|
|
|
140
205
|
```ruby
|
|
141
|
-
|
|
142
|
-
RobotLab.configure do |config|
|
|
143
|
-
config.template_path = "prompts" # or "app/prompts" in Rails
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
# Reference template by name
|
|
147
|
-
robot = RobotLab.build do
|
|
148
|
-
name "support"
|
|
149
|
-
template "support_agent" # Loads prompts/support_agent.erb
|
|
150
|
-
end
|
|
206
|
+
robot = RobotLab.build(template: :developer)
|
|
151
207
|
```
|
|
152
208
|
|
|
153
|
-
|
|
209
|
+
Constructor `mcp:` overrides frontmatter `mcp:` when provided.
|
|
154
210
|
|
|
155
|
-
|
|
211
|
+
### Template with System Prompt
|
|
212
|
+
|
|
213
|
+
You can combine a template and an inline system prompt. Both are applied to the chat -- the template first, then the system prompt is appended as additional instructions:
|
|
156
214
|
|
|
157
215
|
```ruby
|
|
158
|
-
robot = RobotLab.build
|
|
159
|
-
name "support"
|
|
160
|
-
template
|
|
161
|
-
|
|
216
|
+
robot = RobotLab.build(
|
|
217
|
+
name: "support",
|
|
218
|
+
template: :support,
|
|
219
|
+
context: { company: "TechCo" },
|
|
220
|
+
system_prompt: "Always respond in Spanish."
|
|
221
|
+
)
|
|
162
222
|
```
|
|
163
223
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
224
|
+
## Adding Tools
|
|
225
|
+
|
|
226
|
+
Give robots capabilities via the `local_tools:` parameter. Tools can be `RubyLLM::Tool` subclasses or `RobotLab::Tool` instances:
|
|
227
|
+
|
|
228
|
+
```ruby
|
|
229
|
+
robot = RobotLab.build(
|
|
230
|
+
name: "order_assistant",
|
|
231
|
+
system_prompt: "You help customers with orders.",
|
|
232
|
+
local_tools: [OrderLookup, InventoryCheck]
|
|
233
|
+
)
|
|
167
234
|
```
|
|
168
235
|
|
|
236
|
+
See the [Using Tools](using-tools.md) guide for details on defining tools.
|
|
237
|
+
|
|
169
238
|
## MCP Configuration
|
|
170
239
|
|
|
171
|
-
Connect to MCP servers:
|
|
240
|
+
Connect to MCP (Model Context Protocol) servers via the `mcp:` parameter:
|
|
172
241
|
|
|
173
242
|
```ruby
|
|
174
|
-
robot = RobotLab.build
|
|
175
|
-
name "coder"
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
mcp [
|
|
243
|
+
robot = RobotLab.build(
|
|
244
|
+
name: "coder",
|
|
245
|
+
template: :developer,
|
|
246
|
+
mcp: [
|
|
179
247
|
{
|
|
180
248
|
name: "filesystem",
|
|
181
249
|
transport: { type: "stdio", command: "mcp-server-fs", args: ["--root", "/data"] }
|
|
182
250
|
}
|
|
183
251
|
]
|
|
252
|
+
)
|
|
253
|
+
```
|
|
184
254
|
|
|
185
|
-
|
|
186
|
-
mcp :inherit
|
|
255
|
+
MCP configuration supports hierarchical resolution:
|
|
187
256
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
257
|
+
| Value | Behavior |
|
|
258
|
+
|-------|----------|
|
|
259
|
+
| `:none` | No MCP servers (default) |
|
|
260
|
+
| `:inherit` | Use parent network/config MCP servers |
|
|
261
|
+
| `[...]` | Explicit array of server configurations |
|
|
262
|
+
|
|
263
|
+
See the [MCP Integration](mcp-integration.md) guide for transport types and advanced patterns.
|
|
192
264
|
|
|
193
|
-
##
|
|
265
|
+
## Chaining Configuration
|
|
194
266
|
|
|
195
|
-
|
|
267
|
+
Robots support `with_*` method chaining for runtime reconfiguration. Each method returns `self` for fluent usage:
|
|
196
268
|
|
|
197
269
|
```ruby
|
|
198
|
-
robot = RobotLab.build
|
|
199
|
-
name "reader"
|
|
270
|
+
robot = RobotLab.build(name: "bot")
|
|
200
271
|
|
|
201
|
-
|
|
202
|
-
|
|
272
|
+
result = robot
|
|
273
|
+
.with_instructions("Be concise and direct.")
|
|
274
|
+
.with_temperature(0.9)
|
|
275
|
+
.with_model("claude-sonnet-4")
|
|
276
|
+
.run("Summarize quantum computing in one sentence.")
|
|
277
|
+
```
|
|
203
278
|
|
|
204
|
-
|
|
205
|
-
tools :inherit
|
|
279
|
+
### Available Chain Methods
|
|
206
280
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
281
|
+
| Method | Description |
|
|
282
|
+
|--------|-------------|
|
|
283
|
+
| `with_model(id)` | Change the LLM model |
|
|
284
|
+
| `with_instructions(text)` | Set system instructions |
|
|
285
|
+
| `with_temperature(val)` | Set temperature |
|
|
286
|
+
| `with_top_p(val)` | Set nucleus sampling |
|
|
287
|
+
| `with_top_k(val)` | Set top-k sampling |
|
|
288
|
+
| `with_max_tokens(val)` | Set max output tokens |
|
|
289
|
+
| `with_presence_penalty(val)` | Set presence penalty |
|
|
290
|
+
| `with_frequency_penalty(val)` | Set frequency penalty |
|
|
291
|
+
| `with_stop(sequences)` | Set stop sequences |
|
|
292
|
+
| `with_tool(tool)` | Add a single tool |
|
|
293
|
+
| `with_tools(*tools)` | Add multiple tools |
|
|
294
|
+
| `with_template(id, **ctx)` | Apply a prompt template |
|
|
295
|
+
| `with_schema(schema)` | Set structured output schema |
|
|
296
|
+
| `with_thinking(config)` | Enable extended thinking |
|
|
297
|
+
| `with_bus(bus)` | Connect to a message bus (creates one if nil) |
|
|
211
298
|
|
|
212
299
|
## Running Robots
|
|
213
300
|
|
|
214
301
|
### Standalone
|
|
215
302
|
|
|
216
|
-
Run a robot directly:
|
|
303
|
+
Run a robot directly with a string message:
|
|
304
|
+
|
|
305
|
+
```ruby
|
|
306
|
+
result = robot.run("Hello!")
|
|
307
|
+
puts result.last_text_content
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
The `run` method returns a `RobotResult` with:
|
|
217
311
|
|
|
218
312
|
```ruby
|
|
219
|
-
|
|
220
|
-
result
|
|
313
|
+
result.last_text_content # => "Hi there! How can I help?"
|
|
314
|
+
result.output # => Array of output messages
|
|
315
|
+
result.tool_calls # => Array of tool call results
|
|
316
|
+
result.robot_name # => "assistant"
|
|
317
|
+
result.stop_reason # => stop reason from the LLM
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### With Runtime Memory
|
|
221
321
|
|
|
222
|
-
|
|
322
|
+
Inject memory values for a single run:
|
|
323
|
+
|
|
324
|
+
```ruby
|
|
325
|
+
result = robot.run("What's my account status?", memory: { user_id: 123 })
|
|
223
326
|
```
|
|
224
327
|
|
|
225
328
|
### In a Network
|
|
226
329
|
|
|
227
|
-
Run through a network for
|
|
330
|
+
Run through a network for orchestration:
|
|
228
331
|
|
|
229
332
|
```ruby
|
|
230
|
-
network = RobotLab.create_network do
|
|
231
|
-
|
|
333
|
+
network = RobotLab.create_network(name: "pipeline") do
|
|
334
|
+
task :assistant, robot, depends_on: :none
|
|
232
335
|
end
|
|
233
336
|
|
|
234
|
-
|
|
235
|
-
result
|
|
337
|
+
result = network.run(message: "Hello!")
|
|
338
|
+
puts result.value.last_text_content
|
|
236
339
|
```
|
|
237
340
|
|
|
238
341
|
### With Streaming
|
|
239
342
|
|
|
240
|
-
Stream responses in real-time:
|
|
343
|
+
Stream responses in real-time by passing a block:
|
|
241
344
|
|
|
242
345
|
```ruby
|
|
243
|
-
robot.run(
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
puts "\nCalling tool: #{event[:data][:name]}"
|
|
252
|
-
end
|
|
253
|
-
}
|
|
254
|
-
)
|
|
346
|
+
robot.run("Tell me a story") do |event|
|
|
347
|
+
case event[:event]
|
|
348
|
+
when "text.delta"
|
|
349
|
+
print event[:data][:content]
|
|
350
|
+
when "tool_call"
|
|
351
|
+
puts "\nCalling tool: #{event[:data][:name]}"
|
|
352
|
+
end
|
|
353
|
+
end
|
|
255
354
|
```
|
|
256
355
|
|
|
257
356
|
## Robot Patterns
|
|
258
357
|
|
|
259
358
|
### Classifier Robot
|
|
260
359
|
|
|
261
|
-
Route requests to specialized handlers:
|
|
360
|
+
Route requests to specialized handlers. Subclass `RobotLab::Robot` and override `call` for custom pipeline behavior:
|
|
262
361
|
|
|
263
362
|
```ruby
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
363
|
+
class ClassifierRobot < RobotLab::Robot
|
|
364
|
+
def call(result)
|
|
365
|
+
context = extract_run_context(result)
|
|
366
|
+
message = context.delete(:message)
|
|
367
|
+
robot_result = run(message, **context)
|
|
368
|
+
|
|
369
|
+
new_result = result
|
|
370
|
+
.with_context(@name.to_sym, robot_result)
|
|
371
|
+
.continue(robot_result)
|
|
372
|
+
|
|
373
|
+
category = robot_result.last_text_content.to_s.strip.downcase
|
|
374
|
+
|
|
375
|
+
case category
|
|
376
|
+
when /billing/ then new_result.activate(:billing)
|
|
377
|
+
when /technical/ then new_result.activate(:technical)
|
|
378
|
+
else new_result.activate(:general)
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
end
|
|
273
382
|
|
|
383
|
+
classifier = ClassifierRobot.new(
|
|
384
|
+
name: "classifier",
|
|
385
|
+
system_prompt: <<~PROMPT
|
|
386
|
+
Classify the user's message into exactly one category:
|
|
387
|
+
- billing
|
|
388
|
+
- technical
|
|
389
|
+
- general
|
|
274
390
|
Respond with only the category name, nothing else.
|
|
275
391
|
PROMPT
|
|
276
|
-
|
|
392
|
+
)
|
|
277
393
|
```
|
|
278
394
|
|
|
279
395
|
### Specialist Robot
|
|
280
396
|
|
|
281
|
-
Handle specific domains:
|
|
397
|
+
Handle specific domains with template and tools:
|
|
282
398
|
|
|
283
399
|
```ruby
|
|
284
|
-
billing_specialist = RobotLab.build
|
|
285
|
-
name "billing_specialist"
|
|
286
|
-
description "Handles billing and payment inquiries"
|
|
400
|
+
billing_specialist = RobotLab.build(
|
|
401
|
+
name: "billing_specialist",
|
|
402
|
+
description: "Handles billing and payment inquiries",
|
|
403
|
+
template: :billing,
|
|
404
|
+
context: { department: "billing" },
|
|
405
|
+
local_tools: [InvoiceLookup, RefundProcessor]
|
|
406
|
+
)
|
|
407
|
+
```
|
|
287
408
|
|
|
288
|
-
|
|
289
|
-
You are a billing specialist. You help customers with:
|
|
290
|
-
- Invoice questions
|
|
291
|
-
- Payment issues
|
|
292
|
-
- Subscription management
|
|
409
|
+
### Summarizer Robot
|
|
293
410
|
|
|
294
|
-
|
|
411
|
+
Condense information:
|
|
412
|
+
|
|
413
|
+
```ruby
|
|
414
|
+
summarizer = RobotLab.build(
|
|
415
|
+
name: "summarizer",
|
|
416
|
+
description: "Summarizes conversations and documents",
|
|
417
|
+
system_prompt: <<~PROMPT
|
|
418
|
+
Create concise summaries of the provided content.
|
|
419
|
+
Focus on key points and actionable items.
|
|
420
|
+
Use bullet points for clarity.
|
|
295
421
|
PROMPT
|
|
422
|
+
)
|
|
423
|
+
```
|
|
296
424
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
425
|
+
### Bus-Connected Robot
|
|
426
|
+
|
|
427
|
+
Enable bidirectional communication between robots using a message bus. This pattern supports negotiation loops and convergence:
|
|
428
|
+
|
|
429
|
+
```ruby
|
|
430
|
+
bus = TypedBus::MessageBus.new
|
|
431
|
+
|
|
432
|
+
class Comedian < RobotLab::Robot
|
|
433
|
+
def initialize(bus:)
|
|
434
|
+
super(name: "bob", template: :comedian, bus: bus)
|
|
435
|
+
on_message do |message|
|
|
436
|
+
joke = run(message.content.to_s).last_text_content.strip
|
|
437
|
+
reply(message, joke)
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
class ComedyCritic < RobotLab::Robot
|
|
443
|
+
def initialize(bus:)
|
|
444
|
+
super(name: "alice", template: :comedy_critic, bus: bus)
|
|
445
|
+
@accepted = false
|
|
446
|
+
on_message do |message|
|
|
447
|
+
verdict = run("Evaluate: #{message.content}").last_text_content.strip
|
|
448
|
+
@accepted = verdict.start_with?("FUNNY")
|
|
449
|
+
send_message(to: :bob, content: "Try again.") unless @accepted
|
|
450
|
+
end
|
|
301
451
|
end
|
|
452
|
+
attr_reader :accepted
|
|
302
453
|
end
|
|
454
|
+
|
|
455
|
+
bob = Comedian.new(bus: bus)
|
|
456
|
+
alice = ComedyCritic.new(bus: bus)
|
|
457
|
+
alice.send_message(to: :bob, content: "Tell me a funny robot joke.")
|
|
303
458
|
```
|
|
304
459
|
|
|
305
|
-
|
|
460
|
+
The `on_message` block arity controls delivery handling:
|
|
461
|
+
- **1 argument** `|message|` — auto-acknowledges before calling
|
|
462
|
+
- **2 arguments** `|delivery, message|` — manual `delivery.ack!` / `delivery.nack!`
|
|
306
463
|
|
|
307
|
-
|
|
464
|
+
See [Message Bus](../architecture/core-concepts.md#message-bus) for details.
|
|
465
|
+
|
|
466
|
+
### Spawning Robots Dynamically
|
|
467
|
+
|
|
468
|
+
Create new robots at runtime using `spawn`. The bus is created lazily — no upfront wiring required:
|
|
308
469
|
|
|
309
470
|
```ruby
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
description "Summarizes conversations and documents"
|
|
471
|
+
class Dispatcher < RobotLab::Robot
|
|
472
|
+
attr_reader :spawned
|
|
313
473
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
474
|
+
def initialize(bus: nil)
|
|
475
|
+
super(name: "dispatcher", template: :dispatcher, bus: bus)
|
|
476
|
+
@spawned = {}
|
|
477
|
+
|
|
478
|
+
on_message do |message|
|
|
479
|
+
puts "#{message.from} replied: #{message.content.to_s.lines.first&.strip}"
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
def dispatch(question)
|
|
484
|
+
# Ask LLM what specialist to create
|
|
485
|
+
plan = run(question).last_text_content.strip
|
|
486
|
+
role, instruction = plan.split("\n", 2)
|
|
487
|
+
role = role.strip.downcase.gsub(/\s+/, "_")
|
|
488
|
+
|
|
489
|
+
# Spawn (or reuse) a specialist
|
|
490
|
+
specialist = @spawned[role] ||= spawn(
|
|
491
|
+
name: role,
|
|
492
|
+
system_prompt: instruction&.strip || "You are a helpful #{role}."
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
# Have the specialist answer and reply
|
|
496
|
+
answer = specialist.run(question).last_text_content.strip
|
|
497
|
+
specialist.send_message(to: :dispatcher, content: answer)
|
|
498
|
+
end
|
|
319
499
|
end
|
|
320
500
|
```
|
|
321
501
|
|
|
322
|
-
|
|
502
|
+
Key features of `spawn`:
|
|
503
|
+
|
|
504
|
+
- Creates a child robot on the same bus as the parent
|
|
505
|
+
- Creates a bus lazily if the parent doesn't have one
|
|
506
|
+
- Spawned robots can immediately send and receive messages
|
|
507
|
+
- Multiple robots with the same name enable fan-out messaging
|
|
323
508
|
|
|
324
|
-
|
|
509
|
+
Robots can also join a bus after creation:
|
|
325
510
|
|
|
326
511
|
```ruby
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
- Security vulnerabilities
|
|
331
|
-
- Performance issues
|
|
332
|
-
- Best practice violations
|
|
512
|
+
bot = RobotLab.build(name: "latecomer", system_prompt: "Hello.")
|
|
513
|
+
bot.with_bus(existing_bus) # now connected and can send/receive messages
|
|
514
|
+
```
|
|
333
515
|
|
|
334
|
-
|
|
335
|
-
PROMPT
|
|
516
|
+
## Configuration
|
|
336
517
|
|
|
337
|
-
|
|
338
|
-
|
|
518
|
+
RobotLab uses `MywayConfig` for configuration. Access the config object directly -- there is no `RobotLab.configure` block:
|
|
519
|
+
|
|
520
|
+
```ruby
|
|
521
|
+
RobotLab.config.ruby_llm.model # => "claude-sonnet-4"
|
|
522
|
+
RobotLab.config.ruby_llm.request_timeout # => 120
|
|
339
523
|
```
|
|
340
524
|
|
|
341
|
-
|
|
525
|
+
Configuration is loaded from:
|
|
526
|
+
|
|
527
|
+
- Bundled defaults (`lib/robot_lab/config/defaults.yml`)
|
|
528
|
+
- Environment-specific overrides (development, test, production)
|
|
529
|
+
- XDG config files (`~/.config/robot_lab/config.yml`)
|
|
530
|
+
- Project config (`./config/robot_lab.yml`)
|
|
531
|
+
- Environment variables (`ROBOT_LAB_*` prefix)
|
|
532
|
+
|
|
533
|
+
## Best Practices
|
|
534
|
+
|
|
535
|
+
### 1. Clear, Focused Prompts
|
|
342
536
|
|
|
343
537
|
```ruby
|
|
344
|
-
# Good:
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
538
|
+
# Good: Specific and focused
|
|
539
|
+
robot = RobotLab.build(
|
|
540
|
+
name: "reviewer",
|
|
541
|
+
system_prompt: <<~PROMPT
|
|
542
|
+
You are a code reviewer. Review code for:
|
|
543
|
+
- Security vulnerabilities
|
|
544
|
+
- Performance issues
|
|
545
|
+
- Best practice violations
|
|
546
|
+
|
|
547
|
+
Provide specific line numbers and suggestions.
|
|
548
|
+
PROMPT
|
|
549
|
+
)
|
|
350
550
|
|
|
351
|
-
# Bad:
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
551
|
+
# Bad: Vague and unfocused
|
|
552
|
+
robot = RobotLab.build(
|
|
553
|
+
name: "reviewer",
|
|
554
|
+
system_prompt: "You help with code stuff."
|
|
555
|
+
)
|
|
355
556
|
```
|
|
356
557
|
|
|
357
|
-
###
|
|
558
|
+
### 2. Use Templates for Reusable Prompts
|
|
559
|
+
|
|
560
|
+
Templates keep prompts in version-controlled files and allow parameterization:
|
|
358
561
|
|
|
359
562
|
```ruby
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
{ success: false, error: "User not found", user_id: user_id }
|
|
366
|
-
end
|
|
367
|
-
rescue ActiveRecord::ConnectionError => e
|
|
368
|
-
{ success: false, error: "Database unavailable", retry: true }
|
|
369
|
-
end
|
|
563
|
+
robot = RobotLab.build(
|
|
564
|
+
name: "support",
|
|
565
|
+
template: :support,
|
|
566
|
+
context: { company: "TechCo", language: "English" }
|
|
567
|
+
)
|
|
370
568
|
```
|
|
371
569
|
|
|
570
|
+
### 3. Handle Tool Errors Gracefully
|
|
571
|
+
|
|
572
|
+
See [Using Tools: Error Handling](using-tools.md#error-handling) for patterns.
|
|
573
|
+
|
|
372
574
|
## Next Steps
|
|
373
575
|
|
|
374
576
|
- [Creating Networks](creating-networks.md) - Orchestrate multiple robots
|
|
577
|
+
- [Message Bus](../architecture/core-concepts.md#message-bus) - Bidirectional robot communication
|
|
578
|
+
- [Dynamic Spawning](../architecture/core-concepts.md#dynamic-spawning) - Robots creating robots at runtime
|
|
375
579
|
- [Using Tools](using-tools.md) - Advanced tool patterns
|
|
580
|
+
- [Memory Guide](memory.md) - Share data between runs and robots
|
|
376
581
|
- [API Reference: Robot](../api/core/robot.md) - Complete API documentation
|