robot_lab 0.2.6 → 0.2.7
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/README.md +124 -64
- data/docs/api/core/index.md +41 -15
- data/docs/api/core/memory.md +247 -29
- data/docs/api/core/network.md +255 -33
- data/docs/api/core/result.md +120 -32
- data/docs/api/core/robot.md +551 -61
- data/docs/api/core/state.md +87 -197
- data/docs/api/core/tool.md +165 -20
- data/docs/api/errors.md +110 -17
- data/docs/api/hooks.md +469 -0
- data/docs/api/index.md +80 -7
- data/docs/api/mcp/client.md +129 -35
- data/docs/api/mcp/index.md +164 -23
- data/docs/api/mcp/server.md +27 -3
- data/docs/api/mcp/transports.md +94 -22
- data/docs/api/messages/index.md +26 -3
- data/docs/api/messages/text-message.md +33 -11
- data/docs/api/messages/tool-call-message.md +27 -4
- data/docs/api/messages/tool-result-message.md +23 -4
- data/docs/api/messages/user-message.md +45 -8
- data/docs/api/skills.md +519 -0
- data/docs/api/streaming/context.md +28 -5
- data/docs/api/streaming/index.md +57 -11
- data/docs/api/support.md +846 -0
- data/docs/architecture/core-concepts.md +79 -31
- data/docs/architecture/index.md +86 -11
- data/docs/architecture/message-flow.md +66 -29
- data/docs/architecture/network-orchestration.md +145 -38
- data/docs/architecture/robot-execution.md +172 -90
- data/docs/architecture/state-management.md +31 -12
- data/docs/concepts.md +176 -21
- data/docs/examples/basic-chat.md +72 -19
- data/docs/examples/index.md +117 -31
- data/docs/examples/mcp-server.md +154 -45
- data/docs/examples/multi-robot-network.md +91 -21
- data/docs/examples/tool-usage.md +104 -37
- data/docs/getting-started/configuration.md +284 -97
- data/docs/getting-started/installation.md +53 -41
- data/docs/getting-started/quick-start.md +51 -6
- data/docs/guides/building-robots.md +258 -50
- data/docs/guides/creating-networks.md +214 -30
- data/docs/guides/hooks.md +141 -54
- data/docs/guides/knowledge.md +35 -4
- data/docs/guides/mcp-integration.md +211 -44
- data/docs/guides/memory.md +103 -12
- data/docs/guides/observability.md +95 -47
- data/docs/guides/streaming.md +184 -125
- data/docs/guides/using-tools.md +237 -17
- data/docs/index.md +36 -4
- data/examples/01_simple_robot.rb +2 -2
- data/examples/02_tools.rb +14 -4
- data/examples/03_network.rb +12 -7
- data/examples/04_mcp.rb +11 -4
- data/examples/05_streaming.rb +8 -5
- data/examples/06_prompt_templates.rb +13 -9
- data/examples/07_network_memory.rb +5 -5
- data/examples/08_llm_config.rb +20 -15
- data/examples/09_chaining.rb +4 -4
- data/examples/11_network_introspection.rb +4 -4
- data/examples/12_message_bus.rb +2 -2
- data/examples/13_spawn.rb +2 -2
- data/examples/14_rusty_circuit/README.md +1 -0
- data/examples/14_rusty_circuit/comic.rb +7 -3
- data/examples/14_rusty_circuit/display.rb +14 -0
- data/examples/14_rusty_circuit/heckler.rb +8 -6
- data/examples/14_rusty_circuit/open_mic.rb +17 -6
- data/examples/14_rusty_circuit/scout.rb +17 -10
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +14 -10
- data/examples/15_memory_network_and_bus/linux_writer.rb +2 -2
- data/examples/15_memory_network_and_bus/os_editor.rb +3 -1
- data/examples/15_memory_network_and_bus/os_writer.rb +4 -1
- data/examples/16_writers_room/writer.rb +22 -22
- data/examples/16_writers_room/writers_room.rb +2 -0
- data/examples/17_skills.rb +14 -13
- data/examples/18_rails/README.md +20 -1
- data/examples/18_rails/app/controllers/chat_controller.rb +5 -1
- data/examples/18_rails/app/jobs/robot_run_job.rb +11 -5
- data/examples/18_rails/app/robots/chat_robot.rb +11 -0
- data/examples/18_rails/config/initializers/robot_lab.rb +8 -0
- data/examples/19_token_tracking.rb +25 -9
- data/examples/20_circuit_breaker.rb +10 -7
- data/examples/21_learning_loop.rb +42 -16
- data/examples/22_context_compression.rb +23 -23
- data/examples/23_convergence.rb +24 -17
- data/examples/24_structured_delegation.rb +13 -8
- data/examples/25_history_search.rb +12 -8
- data/examples/27_incident_response/incident_response.rb +31 -13
- data/examples/28_mcp_discovery.rb +17 -13
- data/examples/29_ractor_tools.rb +4 -2
- data/examples/30_ractor_network.rb +22 -17
- data/examples/31_launch_assessment.rb +20 -9
- data/examples/32_newsletter_reader.rb +7 -2
- data/examples/33_stock_predictor.rb +34 -13
- data/examples/34_agentskills.rb +7 -3
- data/examples/35_hooks.rb +18 -8
- data/examples/README.md +199 -45
- data/examples/common.rb +79 -11
- data/examples/xyzzy.rb +8 -1
- data/lib/robot_lab/config.rb +10 -5
- data/lib/robot_lab/names.rb +402 -0
- data/lib/robot_lab/robot/agent_skill_matching.rb +1 -3
- data/lib/robot_lab/robot/bus_messaging.rb +16 -8
- data/lib/robot_lab/robot/template_rendering.rb +16 -3
- data/lib/robot_lab/robot.rb +23 -2
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab.rb +21 -15
- data/mkdocs.yml +6 -1
- metadata +7 -2
|
@@ -8,13 +8,18 @@ Configuration values are loaded in priority order (lowest to highest):
|
|
|
8
8
|
|
|
9
9
|
1. **Bundled defaults** -- `lib/robot_lab/config/defaults.yml` (shipped with the gem)
|
|
10
10
|
2. **Environment-specific overrides** -- `development`, `test`, or `production` sections in defaults.yml
|
|
11
|
-
3. **User config file** -- `~/.config/robot_lab/
|
|
11
|
+
3. **User config file** -- `~/.config/robot_lab/robot_lab.yml`
|
|
12
12
|
4. **Project config file** -- `./config/robot_lab.yml`
|
|
13
13
|
5. **Environment variables** -- `ROBOT_LAB_*` prefix
|
|
14
14
|
6. **Runtime attributes** -- e.g., `RobotLab.config.logger = ...`
|
|
15
15
|
|
|
16
16
|
Higher-priority sources override lower-priority ones. You only need to set the values you want to change.
|
|
17
17
|
|
|
18
|
+
> [!IMPORTANT]
|
|
19
|
+
> The user config file is `~/.config/robot_lab/**robot_lab**.yml` — the filename
|
|
20
|
+
> repeats the application name. `~/.config/robot_lab/config.yml` is **never
|
|
21
|
+
> read**, and RobotLab gives no warning when it is present but ignored.
|
|
22
|
+
|
|
18
23
|
## Accessing Configuration
|
|
19
24
|
|
|
20
25
|
Use `RobotLab.config` to access the configuration object:
|
|
@@ -24,8 +29,7 @@ Use `RobotLab.config` to access the configuration object:
|
|
|
24
29
|
RobotLab.config.ruby_llm.model #=> "claude-sonnet-4"
|
|
25
30
|
RobotLab.config.ruby_llm.anthropic_api_key #=> "sk-ant-..."
|
|
26
31
|
RobotLab.config.ruby_llm.request_timeout #=> 120
|
|
27
|
-
RobotLab.config.
|
|
28
|
-
RobotLab.config.streaming_enabled #=> true
|
|
32
|
+
RobotLab.config.template_path #=> nil (auto-detected)
|
|
29
33
|
|
|
30
34
|
# Check the environment
|
|
31
35
|
RobotLab.config.development? #=> true/false
|
|
@@ -46,8 +50,7 @@ Environment variables use the `ROBOT_LAB_` prefix. Use double underscores (`__`)
|
|
|
46
50
|
|
|
47
51
|
```bash
|
|
48
52
|
# Top-level settings
|
|
49
|
-
export
|
|
50
|
-
export ROBOT_LAB_STREAMING_ENABLED=false
|
|
53
|
+
export ROBOT_LAB_TEMPLATE_PATH=prompts
|
|
51
54
|
|
|
52
55
|
# Nested ruby_llm settings (note the double underscore)
|
|
53
56
|
export ROBOT_LAB_RUBY_LLM__MODEL=claude-sonnet-4
|
|
@@ -63,71 +66,132 @@ The double underscore convention maps to nested YAML structure:
|
|
|
63
66
|
```
|
|
64
67
|
ROBOT_LAB_RUBY_LLM__ANTHROPIC_API_KEY --> ruby_llm.anthropic_api_key
|
|
65
68
|
ROBOT_LAB_RUBY_LLM__MODEL --> ruby_llm.model
|
|
66
|
-
|
|
69
|
+
ROBOT_LAB_TEMPLATE_PATH --> template_path
|
|
67
70
|
```
|
|
68
71
|
|
|
72
|
+
> [!WARNING]
|
|
73
|
+
> **Nested values arrive as Strings.** Only top-level keys are type-coerced.
|
|
74
|
+
> With `ROBOT_LAB_RUBY_LLM__REQUEST_TIMEOUT=180` set,
|
|
75
|
+
> `RobotLab.config.ruby_llm.request_timeout` returns the String `"180"`, not the
|
|
76
|
+
> Integer `180`. If a numeric nested setting matters to your code, set it in a
|
|
77
|
+
> config file instead, or coerce it yourself with `.to_i` / `.to_f`.
|
|
78
|
+
|
|
69
79
|
## Config Files
|
|
70
80
|
|
|
81
|
+
> [!WARNING]
|
|
82
|
+
> **A `defaults:` wrapper is always ignored in your own files.** The `defaults:`
|
|
83
|
+
> key you see inside the gem's bundled `lib/robot_lab/config/defaults.yml` applies
|
|
84
|
+
> **only to that bundled file**. Wrap your own settings in it and every value
|
|
85
|
+
> silently falls back to the default — no error, no warning.
|
|
86
|
+
>
|
|
87
|
+
> ```yaml
|
|
88
|
+
> # WRONG — silently ignored in a user or project config file
|
|
89
|
+
> defaults:
|
|
90
|
+
> template_path: prompts
|
|
91
|
+
>
|
|
92
|
+
> # RIGHT
|
|
93
|
+
> template_path: prompts
|
|
94
|
+
> ```
|
|
95
|
+
>
|
|
96
|
+
> Sections named for the **current environment** (`development:`, `test:`,
|
|
97
|
+
> `production:`) are a different matter, and the two files disagree:
|
|
98
|
+
>
|
|
99
|
+
> | File | Flat keys | `development:` section |
|
|
100
|
+
> |------|-----------|------------------------|
|
|
101
|
+
> | `~/.config/robot_lab/robot_lab.yml` | honoured | honoured |
|
|
102
|
+
> | `./config/robot_lab.yml` (no Rails) | honoured | ignored |
|
|
103
|
+
> | `./config/robot_lab.yml` (under Rails) | **ignored** | **honoured** |
|
|
104
|
+
>
|
|
105
|
+
> The user file checks for a section matching the current environment and falls
|
|
106
|
+
> back to the file root, so both forms work. The project file is read by
|
|
107
|
+
> anyway_config, which only treats it as environmental once
|
|
108
|
+
> `Anyway::Settings.current_environment` is set — which is precisely what Rails
|
|
109
|
+
> does (it sets it to `Rails.env`). See [Rails Integration](#rails-integration).
|
|
110
|
+
|
|
71
111
|
### Project Config
|
|
72
112
|
|
|
73
|
-
Create `./config/robot_lab.yml` in your project root
|
|
113
|
+
Create `./config/robot_lab.yml` in your project root. Outside Rails, write the
|
|
114
|
+
keys at the top level (under Rails they must be nested under the environment name
|
|
115
|
+
instead — see [Rails Integration](#rails-integration)):
|
|
74
116
|
|
|
75
117
|
```yaml title="config/robot_lab.yml"
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
development:
|
|
86
|
-
ruby_llm:
|
|
87
|
-
log_level: :debug
|
|
88
|
-
|
|
89
|
-
test:
|
|
90
|
-
max_iterations: 3
|
|
91
|
-
streaming_enabled: false
|
|
92
|
-
ruby_llm:
|
|
93
|
-
model: claude-haiku-3-5
|
|
94
|
-
request_timeout: 30
|
|
95
|
-
max_retries: 1
|
|
96
|
-
|
|
97
|
-
production:
|
|
98
|
-
max_iterations: 20
|
|
99
|
-
ruby_llm:
|
|
100
|
-
request_timeout: 180
|
|
101
|
-
max_retries: 5
|
|
102
|
-
log_level: :warn
|
|
118
|
+
ruby_llm:
|
|
119
|
+
anthropic_api_key: <%= ENV['ANTHROPIC_API_KEY'] %>
|
|
120
|
+
model: claude-sonnet-4
|
|
121
|
+
request_timeout: 120
|
|
122
|
+
max_retries: 3
|
|
123
|
+
log_level: info
|
|
124
|
+
|
|
125
|
+
template_path: prompts
|
|
103
126
|
```
|
|
104
127
|
|
|
105
|
-
|
|
106
|
-
|
|
128
|
+
> [!NOTE]
|
|
129
|
+
> **ERB works here, and only here.** The project config file is read through ERB,
|
|
130
|
+
> so `<%= ENV['ANTHROPIC_API_KEY'] %>` is expanded before the YAML is parsed. The
|
|
131
|
+
> user config file described below is **not** — see the warning there.
|
|
132
|
+
|
|
133
|
+
> [!WARNING]
|
|
134
|
+
> **No YAML symbols in the project config file.** It is parsed with an empty
|
|
135
|
+
> permitted-classes list, so `log_level: :info` raises
|
|
136
|
+
> `Psych::DisallowedClass: Tried to load unspecified class: Symbol` and your
|
|
137
|
+
> application fails to boot. Write the plain string `log_level: info`. (Symbols
|
|
138
|
+
> *are* permitted in the bundled `defaults.yml` and in the user config file,
|
|
139
|
+
> which is why you will see `:debug` there.)
|
|
107
140
|
|
|
108
141
|
### User Config
|
|
109
142
|
|
|
110
|
-
Create `~/.config/robot_lab/
|
|
143
|
+
Create `~/.config/robot_lab/robot_lab.yml` for personal defaults that apply
|
|
144
|
+
across all your projects. Keys go at the top level here too:
|
|
111
145
|
|
|
112
|
-
```yaml title="~/.config/robot_lab/
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
model: claude-sonnet-4
|
|
146
|
+
```yaml title="~/.config/robot_lab/robot_lab.yml"
|
|
147
|
+
ruby_llm:
|
|
148
|
+
model: claude-sonnet-4
|
|
149
|
+
request_timeout: 120
|
|
117
150
|
```
|
|
118
151
|
|
|
152
|
+
> [!WARNING]
|
|
153
|
+
> **Do not put ERB in the user config file.** It is parsed with
|
|
154
|
+
> `YAML.safe_load` and never passed through ERB, so
|
|
155
|
+
> `anthropic_api_key: <%= ENV['ANTHROPIC_API_KEY'] %>` is stored as the literal
|
|
156
|
+
> nine-character-plus string `"<%= ENV['ANTHROPIC_API_KEY'] %>"` and sent to the
|
|
157
|
+
> provider as your API key. Put secrets in environment variables
|
|
158
|
+
> (`ANTHROPIC_API_KEY` or `ROBOT_LAB_RUBY_LLM__ANTHROPIC_API_KEY`), or in the
|
|
159
|
+
> project config file where ERB is evaluated.
|
|
160
|
+
|
|
161
|
+
> [!NOTE]
|
|
162
|
+
> The user config file honours **both** forms: flat keys at the root, or a
|
|
163
|
+
> top-level section named for the current environment (`development:`, `test:`,
|
|
164
|
+
> `production:`). The loader looks for the environment section first and falls back
|
|
165
|
+
> to the root. Only `defaults:` is ignored here.
|
|
166
|
+
|
|
119
167
|
## Configuration Reference
|
|
120
168
|
|
|
121
169
|
### Core Settings
|
|
122
170
|
|
|
123
171
|
| Key | Default | Description |
|
|
124
172
|
|-----|---------|-------------|
|
|
125
|
-
| `max_iterations` | `10` | Maximum robots per network run |
|
|
126
|
-
| `max_tool_iterations` | `10` | Maximum tool calls per robot run |
|
|
127
|
-
| `streaming_enabled` | `true` | Enable streaming by default |
|
|
128
173
|
| `template_path` | `null` (auto-detected) | Directory for prompt templates |
|
|
129
174
|
| `mcp` | `:none` | Global MCP server configuration |
|
|
130
|
-
| `tools` | `:none` | Global tool
|
|
175
|
+
| `tools` | `:none` | Global tool allowlist |
|
|
176
|
+
| `sandbox.*` | see below | Skill-script confinement ceiling |
|
|
177
|
+
|
|
178
|
+
> [!WARNING]
|
|
179
|
+
> **Reserved / not implemented.** `defaults.yml` also ships `max_iterations`,
|
|
180
|
+
> `max_tool_iterations`, `streaming_enabled`, and an entire `chat:` tree
|
|
181
|
+
> (`chat.with_model`, `chat.with_temperature`, `chat.with_tools`,
|
|
182
|
+
> `chat.with_params.*`). These keys resolve — `RobotLab.config.max_iterations`
|
|
183
|
+
> returns `10` — but **nothing in the library reads them**. Setting them has zero
|
|
184
|
+
> effect. They are placeholders; do not build on them.
|
|
185
|
+
>
|
|
186
|
+
> The real equivalents are:
|
|
187
|
+
>
|
|
188
|
+
> | Dead key | Use instead |
|
|
189
|
+
> |----------|-------------|
|
|
190
|
+
> | `max_tool_iterations` | `max_tool_rounds:` on a robot or `RunConfig` |
|
|
191
|
+
> | `streaming_enabled` | `on_content:` callback, or a block passed to `run` |
|
|
192
|
+
> | `chat.with_temperature` | `temperature:` on a robot or `RunConfig` |
|
|
193
|
+
> | `chat.with_params.*` | `top_p:` / `max_tokens:` / etc. on a robot or `RunConfig` |
|
|
194
|
+
> | `max_iterations` | no equivalent — networks are bounded by their task graph |
|
|
131
195
|
|
|
132
196
|
### RubyLLM Settings (`ruby_llm:` section)
|
|
133
197
|
|
|
@@ -152,12 +216,19 @@ All settings under the `ruby_llm:` key are applied to `RubyLLM.configure` automa
|
|
|
152
216
|
|
|
153
217
|
| Key | Default | Description |
|
|
154
218
|
|-----|---------|-------------|
|
|
155
|
-
| `ruby_llm.
|
|
156
|
-
| `ruby_llm.model` | `claude-sonnet-4` | Default model for robots |
|
|
219
|
+
| `ruby_llm.model` | `claude-sonnet-4` | Default model for robots that do not set `model:` |
|
|
157
220
|
| `ruby_llm.default_model` | `null` | RubyLLM default model override |
|
|
158
221
|
| `ruby_llm.default_embedding_model` | `null` | Default embedding model |
|
|
159
222
|
| `ruby_llm.default_image_model` | `null` | Default image model |
|
|
160
223
|
|
|
224
|
+
> [!NOTE]
|
|
225
|
+
> `defaults.yml` also carries `ruby_llm.provider: :anthropic` and
|
|
226
|
+
> `ruby_llm.assume_model_exists: false`, but neither is read. A robot's provider
|
|
227
|
+
> comes from its own `provider:` keyword argument, and `assume_model_exists` is
|
|
228
|
+
> derived from whether that argument was given. Model ids must be full RubyLLM
|
|
229
|
+
> ids — an unknown id raises `RubyLLM::ModelNotFoundError` when the robot is
|
|
230
|
+
> constructed.
|
|
231
|
+
|
|
161
232
|
#### Connection Settings
|
|
162
233
|
|
|
163
234
|
| Key | Default | Description |
|
|
@@ -185,19 +256,25 @@ All settings under the `ruby_llm:` key are applied to `RubyLLM.configure` automa
|
|
|
185
256
|
| `ruby_llm.log_level` | `:info` | Log level (`:debug`, `:info`, `:warn`, `:error`) |
|
|
186
257
|
| `ruby_llm.log_stream_debug` | `false` | Log streaming debug output |
|
|
187
258
|
|
|
188
|
-
### Chat Configuration (`chat:` section)
|
|
259
|
+
### Chat Configuration (`chat:` section) — not implemented
|
|
189
260
|
|
|
190
|
-
|
|
261
|
+
The `chat:` tree in `defaults.yml` (`chat.with_temperature`,
|
|
262
|
+
`chat.with_params.top_p`, `chat.with_params.max_tokens`, `chat.with_tools`, …)
|
|
263
|
+
is **reserved and has no consumers**. Values set there are parsed and then
|
|
264
|
+
ignored.
|
|
191
265
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
266
|
+
To set LLM parameters globally, pass a `RunConfig` to each robot, or set them per
|
|
267
|
+
robot with constructor keyword arguments:
|
|
268
|
+
|
|
269
|
+
```ruby
|
|
270
|
+
robot = RobotLab.build(
|
|
271
|
+
name: "bot",
|
|
272
|
+
system_prompt: "You are helpful.",
|
|
273
|
+
temperature: 0.7,
|
|
274
|
+
max_tokens: 2000,
|
|
275
|
+
top_p: 0.9
|
|
276
|
+
)
|
|
277
|
+
```
|
|
201
278
|
|
|
202
279
|
### Skill-Script Sandboxing (`sandbox:` section)
|
|
203
280
|
|
|
@@ -242,7 +319,8 @@ This clears the cached config and reloads from all sources on next access.
|
|
|
242
319
|
|
|
243
320
|
## Environment-Specific Configuration
|
|
244
321
|
|
|
245
|
-
The `defaults.yml` shipped with RobotLab includes environment-specific overrides
|
|
322
|
+
The `defaults.yml` shipped with RobotLab includes environment-specific overrides.
|
|
323
|
+
This is what the gem actually ships:
|
|
246
324
|
|
|
247
325
|
=== "Development"
|
|
248
326
|
|
|
@@ -256,10 +334,10 @@ The `defaults.yml` shipped with RobotLab includes environment-specific overrides
|
|
|
256
334
|
|
|
257
335
|
```yaml
|
|
258
336
|
test:
|
|
259
|
-
max_iterations: 3
|
|
260
|
-
streaming_enabled: false
|
|
337
|
+
max_iterations: 3 # reserved, no effect
|
|
338
|
+
streaming_enabled: false # reserved, no effect
|
|
261
339
|
ruby_llm:
|
|
262
|
-
model: claude-haiku-
|
|
340
|
+
model: claude-3-haiku-20240307
|
|
263
341
|
request_timeout: 30
|
|
264
342
|
max_retries: 1
|
|
265
343
|
log_level: :warn
|
|
@@ -269,8 +347,8 @@ The `defaults.yml` shipped with RobotLab includes environment-specific overrides
|
|
|
269
347
|
|
|
270
348
|
```yaml
|
|
271
349
|
production:
|
|
272
|
-
streaming_enabled: false
|
|
273
|
-
max_iterations: 20
|
|
350
|
+
streaming_enabled: false # reserved, no effect
|
|
351
|
+
max_iterations: 20 # reserved, no effect
|
|
274
352
|
ruby_llm:
|
|
275
353
|
request_timeout: 180
|
|
276
354
|
max_retries: 5
|
|
@@ -279,26 +357,56 @@ The `defaults.yml` shipped with RobotLab includes environment-specific overrides
|
|
|
279
357
|
|
|
280
358
|
The current environment is determined automatically (via `RAILS_ENV`, `RACK_ENV`, or defaults to `development`).
|
|
281
359
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
360
|
+
> [!NOTE]
|
|
361
|
+
> Under `test`, the effective default model is `claude-3-haiku-20240307` — a full
|
|
362
|
+
> dated model id. Short aliases like `claude-haiku-3-5` are **not** valid RubyLLM
|
|
363
|
+
> model ids and raise `RubyLLM::ModelNotFoundError` at robot construction.
|
|
285
364
|
|
|
286
|
-
|
|
365
|
+
## Rails Integration
|
|
287
366
|
|
|
288
|
-
|
|
289
|
-
|
|
367
|
+
> [!NOTE]
|
|
368
|
+
> Core RobotLab ships **no Railtie and no Engine**. It performs two bare
|
|
369
|
+
> `defined?(::Rails)` checks: the default logger becomes `Rails.logger`, and
|
|
370
|
+
> `template_path` resolves to `Rails.root/app/prompts` when left unset. Generators,
|
|
371
|
+
> `RobotLab::Job`, and Turbo broadcasting live in the separate
|
|
372
|
+
> [robot_lab-rails](https://github.com/MadBomber/robot_lab-rails) gem.
|
|
373
|
+
|
|
374
|
+
> [!WARNING]
|
|
375
|
+
> **Under Rails, `./config/robot_lab.yml` must be environment-sectioned — a flat
|
|
376
|
+
> file is ignored.** Rails' anyway_config integration sets
|
|
377
|
+
> `Anyway::Settings.current_environment` to `Rails.env`, which switches the project
|
|
378
|
+
> config loader into environmental mode. Keys then have to live under
|
|
379
|
+
> `development:` / `test:` / `production:`; anything written at the root of the file
|
|
380
|
+
> is dropped. Outside Rails the rule is exactly inverted — flat keys are read and an
|
|
381
|
+
> environment section is ignored.
|
|
382
|
+
|
|
383
|
+
```yaml title="config/robot_lab.yml (under Rails)"
|
|
384
|
+
development:
|
|
290
385
|
ruby_llm:
|
|
291
386
|
anthropic_api_key: <%= Rails.application.credentials.anthropic_api_key %>
|
|
292
387
|
model: claude-sonnet-4
|
|
293
|
-
|
|
388
|
+
request_timeout: 180
|
|
389
|
+
max_retries: 5
|
|
294
390
|
template_path: null # auto-detects app/prompts in Rails
|
|
295
391
|
|
|
296
392
|
production:
|
|
297
393
|
ruby_llm:
|
|
394
|
+
anthropic_api_key: <%= Rails.application.credentials.anthropic_api_key %>
|
|
395
|
+
model: claude-sonnet-4
|
|
298
396
|
request_timeout: 180
|
|
299
397
|
max_retries: 5
|
|
300
398
|
```
|
|
301
399
|
|
|
400
|
+
The same file outside Rails would instead be written flat:
|
|
401
|
+
|
|
402
|
+
```yaml title="config/robot_lab.yml (no Rails)"
|
|
403
|
+
ruby_llm:
|
|
404
|
+
model: claude-sonnet-4
|
|
405
|
+
request_timeout: 180
|
|
406
|
+
|
|
407
|
+
template_path: prompts
|
|
408
|
+
```
|
|
409
|
+
|
|
302
410
|
You can also use Rails credentials:
|
|
303
411
|
|
|
304
412
|
```bash
|
|
@@ -311,13 +419,10 @@ anthropic_api_key: sk-ant-...
|
|
|
311
419
|
openai_api_key: sk-...
|
|
312
420
|
```
|
|
313
421
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
ruby_llm:
|
|
319
|
-
anthropic_api_key: <%= Rails.application.credentials.anthropic_api_key %>
|
|
320
|
-
```
|
|
422
|
+
`./config/robot_lab.yml` is evaluated through ERB, so credentials can be
|
|
423
|
+
referenced inline as shown above. Note that this only works in the **project**
|
|
424
|
+
config file — the `~/.config/robot_lab/robot_lab.yml` user file is not run
|
|
425
|
+
through ERB.
|
|
321
426
|
|
|
322
427
|
## RunConfig: Shared Operational Defaults
|
|
323
428
|
|
|
@@ -362,13 +467,32 @@ robot = RobotLab.build(
|
|
|
362
467
|
temperature: 0.9 # overrides shared config's 0.5
|
|
363
468
|
)
|
|
364
469
|
|
|
365
|
-
# Network
|
|
470
|
+
# Network-level config
|
|
366
471
|
network = RobotLab.create_network(name: "pipeline", config: shared) do
|
|
367
472
|
task :analyzer, analyzer_robot, depends_on: :none
|
|
368
473
|
task :writer, writer_robot, depends_on: [:analyzer]
|
|
369
474
|
end
|
|
370
475
|
```
|
|
371
476
|
|
|
477
|
+
> [!WARNING]
|
|
478
|
+
> **A network-level `config:` propagates only `mcp` and `tools`** — and only when
|
|
479
|
+
> the member robot opts in by passing `mcp: :inherit` / `tools: :inherit` on its
|
|
480
|
+
> `run`. LLM fields (`model`, `temperature`, `max_tokens`, …) and callbacks
|
|
481
|
+
> (`on_content`, `on_tool_call`, `on_tool_result`) are read from each robot's
|
|
482
|
+
> **own** config at construction time and are never inherited from the network.
|
|
483
|
+
> The only field the network itself consumes is `max_concurrent_robots`.
|
|
484
|
+
>
|
|
485
|
+
> If you want a whole team on one model, pass the same `config:` to each robot:
|
|
486
|
+
>
|
|
487
|
+
> ```ruby
|
|
488
|
+
> shared = RobotLab::RunConfig.new(model: "claude-sonnet-4", temperature: 0.5)
|
|
489
|
+
> analyst = RobotLab.build(name: "analyst", system_prompt: "...", config: shared)
|
|
490
|
+
> writer = RobotLab.build(name: "writer", system_prompt: "...", config: shared)
|
|
491
|
+
> ```
|
|
492
|
+
>
|
|
493
|
+
> A per-task `config:` is merged into the network config and is subject to the
|
|
494
|
+
> same `mcp`/`tools`-only limitation.
|
|
495
|
+
|
|
372
496
|
### Merging Configs
|
|
373
497
|
|
|
374
498
|
RunConfig supports merge semantics where the more-specific config's values win:
|
|
@@ -387,10 +511,10 @@ effective.temperature #=> 0.9 (overridden)
|
|
|
387
511
|
|----------|--------|
|
|
388
512
|
| **LLM** | `model`, `temperature`, `top_p`, `top_k`, `max_tokens`, `presence_penalty`, `frequency_penalty`, `stop` |
|
|
389
513
|
| **Tools** | `mcp`, `tools` |
|
|
390
|
-
| **Callbacks** | `on_tool_call`, `on_tool_result` |
|
|
514
|
+
| **Callbacks** | `on_tool_call`, `on_tool_result`, `on_content` |
|
|
391
515
|
| **Infrastructure** | `bus`, `enable_cache`, `max_tool_rounds`, `token_budget`, `cost_budget`, `max_tools`, `ractor_pool_size`, `max_concurrent_robots`, `doom_loop_threshold`, `auto_compact`, `compact_threshold` |
|
|
392
516
|
|
|
393
|
-
`cost_budget` mirrors `token_budget` for cumulative dollar spend — see [Budgets](../guides/observability.md#budgets-token
|
|
517
|
+
`cost_budget` mirrors `token_budget` for cumulative dollar spend — see [Budgets](../guides/observability.md#budgets-token-cost). `max_tools` overrides the default 128-tool ceiling enforced on every turn before tools are handed to the provider — see [Tool Capping](../guides/using-tools.md#tool-capping-and-per-turn-filtering).
|
|
394
518
|
|
|
395
519
|
### RunConfig vs RobotLab.config
|
|
396
520
|
|
|
@@ -410,43 +534,106 @@ Individual robots can override the global model and other settings:
|
|
|
410
534
|
robot = RobotLab.build(
|
|
411
535
|
name: "fast_bot",
|
|
412
536
|
system_prompt: "You are a quick responder.",
|
|
413
|
-
model: "claude-haiku-
|
|
537
|
+
model: "claude-3-haiku-20240307",
|
|
414
538
|
temperature: 0.3,
|
|
415
539
|
max_tokens: 500
|
|
416
540
|
)
|
|
417
541
|
|
|
418
542
|
# Or use chaining at runtime
|
|
419
|
-
robot.with_temperature(0.9).
|
|
543
|
+
robot.with_temperature(0.9).with_params(max_tokens: 2000).run("Tell me a story.")
|
|
420
544
|
```
|
|
421
545
|
|
|
546
|
+
> [!WARNING]
|
|
547
|
+
> There is **no `with_max_tokens`, `with_top_p`, `with_top_k`, `with_stop`,
|
|
548
|
+
> `with_presence_penalty`, or `with_frequency_penalty`** — calling them raises
|
|
549
|
+
> `NoMethodError`. The complete chainable set is `with_context`, `with_headers`,
|
|
550
|
+
> `with_instructions`, `with_model`, `with_params`, `with_schema`,
|
|
551
|
+
> `with_temperature`, `with_thinking`, `with_tool`, `with_tools`, plus RobotLab's
|
|
552
|
+
> own `with_template` and `with_bus`. For everything else use a constructor
|
|
553
|
+
> keyword argument or `with_params(...)`.
|
|
554
|
+
|
|
422
555
|
## Hierarchical MCP and Tools
|
|
423
556
|
|
|
424
557
|
MCP servers and tools use a hierarchical configuration: `runtime > robot > network > global`. Each level can specify:
|
|
425
558
|
|
|
426
559
|
- `:inherit` -- Use the parent level's configuration
|
|
427
560
|
- `:none` -- No MCP servers or tools at this level
|
|
428
|
-
- An explicit array --
|
|
561
|
+
- An explicit array -- A name **allowlist** (not a local-vs-MCP switch). Entries are
|
|
562
|
+
compared as strings against each attached tool's `name`, so they must match the
|
|
563
|
+
form the tool was attached in: a tool attached as a class matches `[RefundTool]`
|
|
564
|
+
or `%w[RefundTool]`, while one attached as an instance matches `%w[refund]`
|
|
565
|
+
|
|
566
|
+
> [!WARNING]
|
|
567
|
+
> **`tools:` and `mcp:` both default to `:none` — including on `run()` itself.**
|
|
568
|
+
> `Robot#run` is declared `run(message = nil, ..., mcp: :none, tools: :none, ...)`,
|
|
569
|
+
> and an explicit `:none` means "send zero tools this turn". So a plain
|
|
570
|
+
> `robot.run("...")` sends the LLM **no tools and connects no MCP servers**, even
|
|
571
|
+
> when you passed `local_tools:` or `mcp:` at build time.
|
|
572
|
+
>
|
|
573
|
+
> The fix goes on the **run**, not the build:
|
|
574
|
+
>
|
|
575
|
+
> ```ruby
|
|
576
|
+
> robot.run("...", tools: :inherit) # send the attached local tools
|
|
577
|
+
> robot.run("...", mcp: :inherit, tools: :inherit) # connect MCP and send its tools
|
|
578
|
+
> ```
|
|
579
|
+
>
|
|
580
|
+
> `mcp: :inherit` triggers the connection; `tools: :inherit` is additionally
|
|
581
|
+
> required for the MCP tools to actually be handed to the model.
|
|
582
|
+
|
|
583
|
+
> [!IMPORTANT]
|
|
584
|
+
> **For a standalone robot, do not pass `tools: :inherit` at build time.** Build-time
|
|
585
|
+
> `:inherit` resolves against the parent level, and for a standalone robot the parent
|
|
586
|
+
> is the global `:none` — producing an allowlist of `["none"]` that matches nothing
|
|
587
|
+
> and suppresses the tools even when the run asks for `:inherit`. Verified
|
|
588
|
+
> resolution for a standalone robot:
|
|
589
|
+
>
|
|
590
|
+
> | build `tools:` | run `tools:` | tools sent |
|
|
591
|
+
> |---|---|---|
|
|
592
|
+
> | unset | `:none` (default) | none |
|
|
593
|
+
> | unset | `:inherit` | the attached tools ✅ |
|
|
594
|
+
> | `:inherit` | `:inherit` | **none** ❌ |
|
|
595
|
+
> | `:inherit` | `:none` | none |
|
|
596
|
+
> | `:none` | `:inherit` | the attached tools ✅ |
|
|
597
|
+
>
|
|
598
|
+
> Leave `tools:` unset at build time — unless the robot is a member of a network.
|
|
599
|
+
|
|
600
|
+
> [!NOTE]
|
|
601
|
+
> **Inside a network, build-time `:inherit` is the opt-in, not a bug.** The parent
|
|
602
|
+
> is resolved at run time as the network's `config:`, so `:inherit` is how a robot
|
|
603
|
+
> asks for the network-level allowlist. With
|
|
604
|
+
> `RobotLab::RunConfig.new(tools: %w[RefundTool])` on the network and a robot
|
|
605
|
+
> holding `local_tools: [RefundTool, InvoiceTool]`:
|
|
606
|
+
>
|
|
607
|
+
> | build `tools:` | task `tools:` | tools sent |
|
|
608
|
+
> |---|---|---|
|
|
609
|
+
> | unset | omitted (`:none`) | none |
|
|
610
|
+
> | unset | `:inherit` | `refund`, `invoice` — the network allowlist is **not** applied |
|
|
611
|
+
> | `:inherit` | `:inherit` | `refund` — the network allowlist **is** applied ✅ |
|
|
612
|
+
> | `:inherit` | omitted (`:none`) | none |
|
|
613
|
+
>
|
|
614
|
+
> The same reasoning applies to `mcp:`.
|
|
429
615
|
|
|
430
616
|
```ruby
|
|
431
|
-
#
|
|
432
|
-
robot = RobotLab.build(
|
|
433
|
-
name: "agent",
|
|
434
|
-
system_prompt: "You are helpful.",
|
|
435
|
-
mcp: :inherit,
|
|
436
|
-
tools: :inherit
|
|
437
|
-
)
|
|
438
|
-
|
|
439
|
-
# Robot with no MCP, specific tools
|
|
617
|
+
# Correct: attach tools at build time, request them at run time
|
|
440
618
|
robot = RobotLab.build(
|
|
441
619
|
name: "calculator",
|
|
442
620
|
system_prompt: "You solve math problems.",
|
|
443
|
-
mcp: :none,
|
|
444
|
-
tools: :inherit, # required: without this, local_tools below is never sent
|
|
445
621
|
local_tools: [Calculator]
|
|
446
622
|
)
|
|
623
|
+
|
|
624
|
+
robot.run("What is 17 * 23?", tools: :inherit)
|
|
625
|
+
|
|
626
|
+
# Robot with MCP servers attached at build time
|
|
627
|
+
robot = RobotLab.build(
|
|
628
|
+
name: "agent",
|
|
629
|
+
system_prompt: "You are helpful.",
|
|
630
|
+
mcp: [{ name: "filesystem", transport: { type: "stdio", command: "mcp-server-filesystem" } }]
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
robot.run("List the files in ./lib", mcp: :inherit, tools: :inherit)
|
|
447
634
|
```
|
|
448
635
|
|
|
449
|
-
|
|
636
|
+
See [Runtime Tool Filtering](../guides/using-tools.md#runtime-tool-filtering) for the full `:inherit`/`:none`/array semantics.
|
|
450
637
|
|
|
451
638
|
## Next Steps
|
|
452
639
|
|