robot_lab 0.2.6 → 0.2.8
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/.envrc +4 -0
- data/.loki +5 -0
- data/Archspec.rb +44 -0
- data/CHANGELOG.md +17 -1
- data/README.md +124 -64
- data/Rakefile +6 -111
- data/_typos.toml +21 -0
- 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 +423 -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 +87 -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 +288 -98
- 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 +263 -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 +247 -18
- 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/agent_skill_catalog.rb +1 -0
- data/lib/robot_lab/ask_user.rb +2 -0
- data/lib/robot_lab/bus_poller.rb +2 -0
- data/lib/robot_lab/capabilities.rb +4 -0
- data/lib/robot_lab/config.rb +18 -5
- data/lib/robot_lab/doom_loop_detector.rb +6 -3
- data/lib/robot_lab/history_compressor.rb +5 -0
- data/lib/robot_lab/hook.rb +1 -0
- data/lib/robot_lab/hook_context.rb +4 -0
- data/lib/robot_lab/hook_registry.rb +1 -0
- data/lib/robot_lab/hooks.rb +6 -3
- data/lib/robot_lab/mcp/client.rb +2 -2
- data/lib/robot_lab/mcp/connection_poller.rb +16 -8
- data/lib/robot_lab/mcp/server_discovery.rb +1 -0
- data/lib/robot_lab/mcp/transports/sse.rb +3 -0
- data/lib/robot_lab/mcp/transports/stdio.rb +5 -0
- data/lib/robot_lab/mcp/transports/streamable_http.rb +4 -0
- data/lib/robot_lab/mcp/transports/websocket.rb +3 -0
- data/lib/robot_lab/memory.rb +23 -6
- data/lib/robot_lab/memory_change.rb +1 -0
- data/lib/robot_lab/message.rb +3 -0
- data/lib/robot_lab/names.rb +400 -0
- data/lib/robot_lab/network.rb +13 -6
- data/lib/robot_lab/robot/agent_skill_matching.rb +3 -3
- data/lib/robot_lab/robot/bus_messaging.rb +19 -8
- data/lib/robot_lab/robot/history_search.rb +2 -0
- data/lib/robot_lab/robot/hooking.rb +3 -0
- data/lib/robot_lab/robot/mcp_management.rb +11 -3
- data/lib/robot_lab/robot/template_rendering.rb +29 -7
- data/lib/robot_lab/robot.rb +81 -19
- data/lib/robot_lab/robot_result.rb +3 -0
- data/lib/robot_lab/run_config.rb +5 -0
- data/lib/robot_lab/script_tool.rb +20 -39
- data/lib/robot_lab/state_proxy.rb +1 -0
- data/lib/robot_lab/streaming/context.rb +1 -0
- data/lib/robot_lab/streaming/events.rb +1 -0
- data/lib/robot_lab/task.rb +2 -0
- data/lib/robot_lab/tool.rb +4 -0
- data/lib/robot_lab/user_message.rb +1 -0
- data/lib/robot_lab/utils.rb +2 -0
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab/waiter.rb +3 -0
- data/lib/robot_lab.rb +27 -18
- data/mkdocs.yml +6 -1
- metadata +12 -7
- data/lib/robot_lab/sandbox/null.rb +0 -13
- data/lib/robot_lab/sandbox/seatbelt.rb +0 -104
- data/lib/robot_lab/sandbox.rb +0 -52
|
@@ -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,23 +256,32 @@ 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
|
|
|
204
|
-
|
|
281
|
+
Core declares this config schema but has no sandboxing behavior of its own — enforcement
|
|
282
|
+
lives in the optional [`robot_lab-sandbox`](https://github.com/MadBomber/robot_lab-sandbox)
|
|
283
|
+
gem. Without it, skill scripts always run unconfined regardless of these values; requiring
|
|
284
|
+
it installs a `RobotLab::ScriptTool.executor` that honors them:
|
|
205
285
|
|
|
206
286
|
| Key | Default | Description |
|
|
207
287
|
|-----|---------|-------------|
|
|
@@ -242,7 +322,8 @@ This clears the cached config and reloads from all sources on next access.
|
|
|
242
322
|
|
|
243
323
|
## Environment-Specific Configuration
|
|
244
324
|
|
|
245
|
-
The `defaults.yml` shipped with RobotLab includes environment-specific overrides
|
|
325
|
+
The `defaults.yml` shipped with RobotLab includes environment-specific overrides.
|
|
326
|
+
This is what the gem actually ships:
|
|
246
327
|
|
|
247
328
|
=== "Development"
|
|
248
329
|
|
|
@@ -256,10 +337,10 @@ The `defaults.yml` shipped with RobotLab includes environment-specific overrides
|
|
|
256
337
|
|
|
257
338
|
```yaml
|
|
258
339
|
test:
|
|
259
|
-
max_iterations: 3
|
|
260
|
-
streaming_enabled: false
|
|
340
|
+
max_iterations: 3 # reserved, no effect
|
|
341
|
+
streaming_enabled: false # reserved, no effect
|
|
261
342
|
ruby_llm:
|
|
262
|
-
model: claude-haiku-
|
|
343
|
+
model: claude-3-haiku-20240307
|
|
263
344
|
request_timeout: 30
|
|
264
345
|
max_retries: 1
|
|
265
346
|
log_level: :warn
|
|
@@ -269,8 +350,8 @@ The `defaults.yml` shipped with RobotLab includes environment-specific overrides
|
|
|
269
350
|
|
|
270
351
|
```yaml
|
|
271
352
|
production:
|
|
272
|
-
streaming_enabled: false
|
|
273
|
-
max_iterations: 20
|
|
353
|
+
streaming_enabled: false # reserved, no effect
|
|
354
|
+
max_iterations: 20 # reserved, no effect
|
|
274
355
|
ruby_llm:
|
|
275
356
|
request_timeout: 180
|
|
276
357
|
max_retries: 5
|
|
@@ -279,26 +360,56 @@ The `defaults.yml` shipped with RobotLab includes environment-specific overrides
|
|
|
279
360
|
|
|
280
361
|
The current environment is determined automatically (via `RAILS_ENV`, `RACK_ENV`, or defaults to `development`).
|
|
281
362
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
363
|
+
> [!NOTE]
|
|
364
|
+
> Under `test`, the effective default model is `claude-3-haiku-20240307` — a full
|
|
365
|
+
> dated model id. Short aliases like `claude-haiku-3-5` are **not** valid RubyLLM
|
|
366
|
+
> model ids and raise `RubyLLM::ModelNotFoundError` at robot construction.
|
|
285
367
|
|
|
286
|
-
|
|
368
|
+
## Rails Integration
|
|
287
369
|
|
|
288
|
-
|
|
289
|
-
|
|
370
|
+
> [!NOTE]
|
|
371
|
+
> Core RobotLab ships **no Railtie and no Engine**. It performs two bare
|
|
372
|
+
> `defined?(::Rails)` checks: the default logger becomes `Rails.logger`, and
|
|
373
|
+
> `template_path` resolves to `Rails.root/app/prompts` when left unset. Generators,
|
|
374
|
+
> `RobotLab::Job`, and Turbo broadcasting live in the separate
|
|
375
|
+
> [robot_lab-rails](https://github.com/MadBomber/robot_lab-rails) gem.
|
|
376
|
+
|
|
377
|
+
> [!WARNING]
|
|
378
|
+
> **Under Rails, `./config/robot_lab.yml` must be environment-sectioned — a flat
|
|
379
|
+
> file is ignored.** Rails' anyway_config integration sets
|
|
380
|
+
> `Anyway::Settings.current_environment` to `Rails.env`, which switches the project
|
|
381
|
+
> config loader into environmental mode. Keys then have to live under
|
|
382
|
+
> `development:` / `test:` / `production:`; anything written at the root of the file
|
|
383
|
+
> is dropped. Outside Rails the rule is exactly inverted — flat keys are read and an
|
|
384
|
+
> environment section is ignored.
|
|
385
|
+
|
|
386
|
+
```yaml title="config/robot_lab.yml (under Rails)"
|
|
387
|
+
development:
|
|
290
388
|
ruby_llm:
|
|
291
389
|
anthropic_api_key: <%= Rails.application.credentials.anthropic_api_key %>
|
|
292
390
|
model: claude-sonnet-4
|
|
293
|
-
|
|
391
|
+
request_timeout: 180
|
|
392
|
+
max_retries: 5
|
|
294
393
|
template_path: null # auto-detects app/prompts in Rails
|
|
295
394
|
|
|
296
395
|
production:
|
|
297
396
|
ruby_llm:
|
|
397
|
+
anthropic_api_key: <%= Rails.application.credentials.anthropic_api_key %>
|
|
398
|
+
model: claude-sonnet-4
|
|
298
399
|
request_timeout: 180
|
|
299
400
|
max_retries: 5
|
|
300
401
|
```
|
|
301
402
|
|
|
403
|
+
The same file outside Rails would instead be written flat:
|
|
404
|
+
|
|
405
|
+
```yaml title="config/robot_lab.yml (no Rails)"
|
|
406
|
+
ruby_llm:
|
|
407
|
+
model: claude-sonnet-4
|
|
408
|
+
request_timeout: 180
|
|
409
|
+
|
|
410
|
+
template_path: prompts
|
|
411
|
+
```
|
|
412
|
+
|
|
302
413
|
You can also use Rails credentials:
|
|
303
414
|
|
|
304
415
|
```bash
|
|
@@ -311,13 +422,10 @@ anthropic_api_key: sk-ant-...
|
|
|
311
422
|
openai_api_key: sk-...
|
|
312
423
|
```
|
|
313
424
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
ruby_llm:
|
|
319
|
-
anthropic_api_key: <%= Rails.application.credentials.anthropic_api_key %>
|
|
320
|
-
```
|
|
425
|
+
`./config/robot_lab.yml` is evaluated through ERB, so credentials can be
|
|
426
|
+
referenced inline as shown above. Note that this only works in the **project**
|
|
427
|
+
config file — the `~/.config/robot_lab/robot_lab.yml` user file is not run
|
|
428
|
+
through ERB.
|
|
321
429
|
|
|
322
430
|
## RunConfig: Shared Operational Defaults
|
|
323
431
|
|
|
@@ -362,13 +470,32 @@ robot = RobotLab.build(
|
|
|
362
470
|
temperature: 0.9 # overrides shared config's 0.5
|
|
363
471
|
)
|
|
364
472
|
|
|
365
|
-
# Network
|
|
473
|
+
# Network-level config
|
|
366
474
|
network = RobotLab.create_network(name: "pipeline", config: shared) do
|
|
367
475
|
task :analyzer, analyzer_robot, depends_on: :none
|
|
368
476
|
task :writer, writer_robot, depends_on: [:analyzer]
|
|
369
477
|
end
|
|
370
478
|
```
|
|
371
479
|
|
|
480
|
+
> [!WARNING]
|
|
481
|
+
> **A network-level `config:` propagates only `mcp` and `tools`** — and only when
|
|
482
|
+
> the member robot opts in by passing `mcp: :inherit` / `tools: :inherit` on its
|
|
483
|
+
> `run`. LLM fields (`model`, `temperature`, `max_tokens`, …) and callbacks
|
|
484
|
+
> (`on_content`, `on_tool_call`, `on_tool_result`) are read from each robot's
|
|
485
|
+
> **own** config at construction time and are never inherited from the network.
|
|
486
|
+
> The only field the network itself consumes is `max_concurrent_robots`.
|
|
487
|
+
>
|
|
488
|
+
> If you want a whole team on one model, pass the same `config:` to each robot:
|
|
489
|
+
>
|
|
490
|
+
> ```ruby
|
|
491
|
+
> shared = RobotLab::RunConfig.new(model: "claude-sonnet-4", temperature: 0.5)
|
|
492
|
+
> analyst = RobotLab.build(name: "analyst", system_prompt: "...", config: shared)
|
|
493
|
+
> writer = RobotLab.build(name: "writer", system_prompt: "...", config: shared)
|
|
494
|
+
> ```
|
|
495
|
+
>
|
|
496
|
+
> A per-task `config:` is merged into the network config and is subject to the
|
|
497
|
+
> same `mcp`/`tools`-only limitation.
|
|
498
|
+
|
|
372
499
|
### Merging Configs
|
|
373
500
|
|
|
374
501
|
RunConfig supports merge semantics where the more-specific config's values win:
|
|
@@ -387,10 +514,10 @@ effective.temperature #=> 0.9 (overridden)
|
|
|
387
514
|
|----------|--------|
|
|
388
515
|
| **LLM** | `model`, `temperature`, `top_p`, `top_k`, `max_tokens`, `presence_penalty`, `frequency_penalty`, `stop` |
|
|
389
516
|
| **Tools** | `mcp`, `tools` |
|
|
390
|
-
| **Callbacks** | `on_tool_call`, `on_tool_result` |
|
|
517
|
+
| **Callbacks** | `on_tool_call`, `on_tool_result`, `on_content` |
|
|
391
518
|
| **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
519
|
|
|
393
|
-
`cost_budget` mirrors `token_budget` for cumulative dollar spend — see [Budgets](../guides/observability.md#budgets-token
|
|
520
|
+
`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
521
|
|
|
395
522
|
### RunConfig vs RobotLab.config
|
|
396
523
|
|
|
@@ -410,43 +537,106 @@ Individual robots can override the global model and other settings:
|
|
|
410
537
|
robot = RobotLab.build(
|
|
411
538
|
name: "fast_bot",
|
|
412
539
|
system_prompt: "You are a quick responder.",
|
|
413
|
-
model: "claude-haiku-
|
|
540
|
+
model: "claude-3-haiku-20240307",
|
|
414
541
|
temperature: 0.3,
|
|
415
542
|
max_tokens: 500
|
|
416
543
|
)
|
|
417
544
|
|
|
418
545
|
# Or use chaining at runtime
|
|
419
|
-
robot.with_temperature(0.9).
|
|
546
|
+
robot.with_temperature(0.9).with_params(max_tokens: 2000).run("Tell me a story.")
|
|
420
547
|
```
|
|
421
548
|
|
|
549
|
+
> [!WARNING]
|
|
550
|
+
> There is **no `with_max_tokens`, `with_top_p`, `with_top_k`, `with_stop`,
|
|
551
|
+
> `with_presence_penalty`, or `with_frequency_penalty`** — calling them raises
|
|
552
|
+
> `NoMethodError`. The complete chainable set is `with_context`, `with_headers`,
|
|
553
|
+
> `with_instructions`, `with_model`, `with_params`, `with_schema`,
|
|
554
|
+
> `with_temperature`, `with_thinking`, `with_tool`, `with_tools`, plus RobotLab's
|
|
555
|
+
> own `with_template` and `with_bus`. For everything else use a constructor
|
|
556
|
+
> keyword argument or `with_params(...)`.
|
|
557
|
+
|
|
422
558
|
## Hierarchical MCP and Tools
|
|
423
559
|
|
|
424
560
|
MCP servers and tools use a hierarchical configuration: `runtime > robot > network > global`. Each level can specify:
|
|
425
561
|
|
|
426
562
|
- `:inherit` -- Use the parent level's configuration
|
|
427
563
|
- `:none` -- No MCP servers or tools at this level
|
|
428
|
-
- An explicit array --
|
|
564
|
+
- An explicit array -- A name **allowlist** (not a local-vs-MCP switch). Entries are
|
|
565
|
+
compared as strings against each attached tool's `name`, so they must match the
|
|
566
|
+
form the tool was attached in: a tool attached as a class matches `[RefundTool]`
|
|
567
|
+
or `%w[RefundTool]`, while one attached as an instance matches `%w[refund]`
|
|
568
|
+
|
|
569
|
+
> [!WARNING]
|
|
570
|
+
> **`tools:` and `mcp:` both default to `:none` — including on `run()` itself.**
|
|
571
|
+
> `Robot#run` is declared `run(message = nil, ..., mcp: :none, tools: :none, ...)`,
|
|
572
|
+
> and an explicit `:none` means "send zero tools this turn". So a plain
|
|
573
|
+
> `robot.run("...")` sends the LLM **no tools and connects no MCP servers**, even
|
|
574
|
+
> when you passed `local_tools:` or `mcp:` at build time.
|
|
575
|
+
>
|
|
576
|
+
> The fix goes on the **run**, not the build:
|
|
577
|
+
>
|
|
578
|
+
> ```ruby
|
|
579
|
+
> robot.run("...", tools: :inherit) # send the attached local tools
|
|
580
|
+
> robot.run("...", mcp: :inherit, tools: :inherit) # connect MCP and send its tools
|
|
581
|
+
> ```
|
|
582
|
+
>
|
|
583
|
+
> `mcp: :inherit` triggers the connection; `tools: :inherit` is additionally
|
|
584
|
+
> required for the MCP tools to actually be handed to the model.
|
|
585
|
+
|
|
586
|
+
> [!IMPORTANT]
|
|
587
|
+
> **For a standalone robot, do not pass `tools: :inherit` at build time.** Build-time
|
|
588
|
+
> `:inherit` resolves against the parent level, and for a standalone robot the parent
|
|
589
|
+
> is the global `:none` — producing an allowlist of `["none"]` that matches nothing
|
|
590
|
+
> and suppresses the tools even when the run asks for `:inherit`. Verified
|
|
591
|
+
> resolution for a standalone robot:
|
|
592
|
+
>
|
|
593
|
+
> | build `tools:` | run `tools:` | tools sent |
|
|
594
|
+
> |---|---|---|
|
|
595
|
+
> | unset | `:none` (default) | none |
|
|
596
|
+
> | unset | `:inherit` | the attached tools ✅ |
|
|
597
|
+
> | `:inherit` | `:inherit` | **none** ❌ |
|
|
598
|
+
> | `:inherit` | `:none` | none |
|
|
599
|
+
> | `:none` | `:inherit` | the attached tools ✅ |
|
|
600
|
+
>
|
|
601
|
+
> Leave `tools:` unset at build time — unless the robot is a member of a network.
|
|
602
|
+
|
|
603
|
+
> [!NOTE]
|
|
604
|
+
> **Inside a network, build-time `:inherit` is the opt-in, not a bug.** The parent
|
|
605
|
+
> is resolved at run time as the network's `config:`, so `:inherit` is how a robot
|
|
606
|
+
> asks for the network-level allowlist. With
|
|
607
|
+
> `RobotLab::RunConfig.new(tools: %w[RefundTool])` on the network and a robot
|
|
608
|
+
> holding `local_tools: [RefundTool, InvoiceTool]`:
|
|
609
|
+
>
|
|
610
|
+
> | build `tools:` | task `tools:` | tools sent |
|
|
611
|
+
> |---|---|---|
|
|
612
|
+
> | unset | omitted (`:none`) | none |
|
|
613
|
+
> | unset | `:inherit` | `refund`, `invoice` — the network allowlist is **not** applied |
|
|
614
|
+
> | `:inherit` | `:inherit` | `refund` — the network allowlist **is** applied ✅ |
|
|
615
|
+
> | `:inherit` | omitted (`:none`) | none |
|
|
616
|
+
>
|
|
617
|
+
> The same reasoning applies to `mcp:`.
|
|
429
618
|
|
|
430
619
|
```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
|
|
620
|
+
# Correct: attach tools at build time, request them at run time
|
|
440
621
|
robot = RobotLab.build(
|
|
441
622
|
name: "calculator",
|
|
442
623
|
system_prompt: "You solve math problems.",
|
|
443
|
-
mcp: :none,
|
|
444
|
-
tools: :inherit, # required: without this, local_tools below is never sent
|
|
445
624
|
local_tools: [Calculator]
|
|
446
625
|
)
|
|
626
|
+
|
|
627
|
+
robot.run("What is 17 * 23?", tools: :inherit)
|
|
628
|
+
|
|
629
|
+
# Robot with MCP servers attached at build time
|
|
630
|
+
robot = RobotLab.build(
|
|
631
|
+
name: "agent",
|
|
632
|
+
system_prompt: "You are helpful.",
|
|
633
|
+
mcp: [{ name: "filesystem", transport: { type: "stdio", command: "mcp-server-filesystem" } }]
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
robot.run("List the files in ./lib", mcp: :inherit, tools: :inherit)
|
|
447
637
|
```
|
|
448
638
|
|
|
449
|
-
|
|
639
|
+
See [Runtime Tool Filtering](../guides/using-tools.md#runtime-tool-filtering) for the full `:inherit`/`:none`/array semantics.
|
|
450
640
|
|
|
451
641
|
## Next Steps
|
|
452
642
|
|