robot_lab 0.2.1 → 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.
Files changed (236) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +1 -0
  3. data/.loki +60 -0
  4. data/.quality/reek_baseline.txt +43 -0
  5. data/.rubocop.yml +5 -167
  6. data/CHANGELOG.md +54 -0
  7. data/README.md +185 -64
  8. data/Rakefile +28 -12
  9. data/docs/api/core/index.md +41 -14
  10. data/docs/api/core/memory.md +247 -29
  11. data/docs/api/core/network.md +285 -33
  12. data/docs/api/core/result.md +120 -32
  13. data/docs/api/core/robot.md +636 -60
  14. data/docs/api/core/state.md +87 -197
  15. data/docs/api/core/tool.md +165 -20
  16. data/docs/api/errors.md +152 -14
  17. data/docs/api/hooks.md +469 -0
  18. data/docs/api/index.md +83 -7
  19. data/docs/api/mcp/client.md +129 -35
  20. data/docs/api/mcp/index.md +164 -23
  21. data/docs/api/mcp/server.md +27 -3
  22. data/docs/api/mcp/transports.md +94 -22
  23. data/docs/api/messages/index.md +26 -3
  24. data/docs/api/messages/text-message.md +33 -11
  25. data/docs/api/messages/tool-call-message.md +27 -4
  26. data/docs/api/messages/tool-result-message.md +23 -4
  27. data/docs/api/messages/user-message.md +45 -8
  28. data/docs/api/skills.md +519 -0
  29. data/docs/api/streaming/context.md +28 -5
  30. data/docs/api/streaming/index.md +57 -11
  31. data/docs/api/support.md +846 -0
  32. data/docs/architecture/core-concepts.md +118 -30
  33. data/docs/architecture/index.md +86 -11
  34. data/docs/architecture/message-flow.md +66 -29
  35. data/docs/architecture/network-orchestration.md +153 -38
  36. data/docs/architecture/robot-execution.md +173 -91
  37. data/docs/architecture/state-management.md +31 -12
  38. data/docs/concepts.md +176 -21
  39. data/docs/examples/basic-chat.md +72 -19
  40. data/docs/examples/index.md +117 -31
  41. data/docs/examples/mcp-server.md +154 -45
  42. data/docs/examples/multi-robot-network.md +91 -21
  43. data/docs/examples/tool-usage.md +104 -37
  44. data/docs/getting-started/configuration.md +299 -93
  45. data/docs/getting-started/installation.md +53 -41
  46. data/docs/getting-started/quick-start.md +51 -6
  47. data/docs/guides/building-robots.md +296 -50
  48. data/docs/guides/creating-networks.md +220 -31
  49. data/docs/guides/hooks.md +1084 -0
  50. data/docs/guides/index.md +5 -0
  51. data/docs/guides/knowledge.md +37 -6
  52. data/docs/guides/mcp-integration.md +211 -44
  53. data/docs/guides/memory.md +103 -12
  54. data/docs/guides/observability.md +166 -51
  55. data/docs/guides/streaming.md +184 -125
  56. data/docs/guides/using-tools.md +300 -11
  57. data/docs/index.md +37 -5
  58. data/examples/01_simple_robot.rb +2 -2
  59. data/examples/02_tools.rb +14 -4
  60. data/examples/03_network.rb +23 -9
  61. data/examples/04_mcp.rb +11 -4
  62. data/examples/05_streaming.rb +8 -5
  63. data/examples/06_prompt_templates.rb +13 -9
  64. data/examples/07_network_memory.rb +5 -5
  65. data/examples/08_llm_config.rb +20 -15
  66. data/examples/09_chaining.rb +4 -4
  67. data/examples/11_network_introspection.rb +4 -4
  68. data/examples/12_message_bus.rb +2 -2
  69. data/examples/13_spawn.rb +2 -2
  70. data/examples/14_rusty_circuit/README.md +1 -0
  71. data/examples/14_rusty_circuit/comic.rb +7 -3
  72. data/examples/14_rusty_circuit/display.rb +14 -0
  73. data/examples/14_rusty_circuit/heckler.rb +8 -6
  74. data/examples/14_rusty_circuit/open_mic.rb +17 -6
  75. data/examples/14_rusty_circuit/scout.rb +17 -10
  76. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +14 -10
  77. data/examples/15_memory_network_and_bus/linux_writer.rb +2 -2
  78. data/examples/15_memory_network_and_bus/os_editor.rb +3 -1
  79. data/examples/15_memory_network_and_bus/os_writer.rb +4 -1
  80. data/examples/16_writers_room/writer.rb +22 -22
  81. data/examples/16_writers_room/writers_room.rb +2 -0
  82. data/examples/17_skills.rb +14 -13
  83. data/examples/18_rails/README.md +20 -1
  84. data/examples/18_rails/app/controllers/chat_controller.rb +5 -1
  85. data/examples/18_rails/app/jobs/robot_run_job.rb +11 -5
  86. data/examples/18_rails/app/robots/chat_robot.rb +11 -0
  87. data/examples/18_rails/config/initializers/robot_lab.rb +8 -0
  88. data/examples/19_token_tracking.rb +25 -9
  89. data/examples/20_circuit_breaker.rb +10 -7
  90. data/examples/21_learning_loop.rb +42 -16
  91. data/examples/22_context_compression.rb +23 -23
  92. data/examples/23_convergence.rb +24 -17
  93. data/examples/24_structured_delegation.rb +13 -8
  94. data/examples/25_history_search.rb +12 -8
  95. data/examples/27_incident_response/incident_response.rb +31 -13
  96. data/examples/28_mcp_discovery.rb +17 -13
  97. data/examples/29_ractor_tools.rb +4 -2
  98. data/examples/30_ractor_network.rb +22 -17
  99. data/examples/31_launch_assessment.rb +20 -9
  100. data/examples/32_newsletter_reader.rb +7 -2
  101. data/examples/33_stock_predictor.rb +34 -13
  102. data/examples/34_agentskills.rb +7 -3
  103. data/examples/35_hooks.rb +266 -0
  104. data/examples/README.md +203 -42
  105. data/examples/common.rb +79 -7
  106. data/examples/xyzzy.rb +97 -0
  107. data/lib/robot_lab/agent_skill.rb +5 -4
  108. data/lib/robot_lab/budget/ledger.rb +98 -0
  109. data/lib/robot_lab/capabilities.rb +84 -0
  110. data/lib/robot_lab/config/defaults.yml +10 -0
  111. data/lib/robot_lab/config.rb +14 -9
  112. data/lib/robot_lab/error.rb +34 -4
  113. data/lib/robot_lab/errors.rb +45 -0
  114. data/lib/robot_lab/hook.rb +79 -0
  115. data/lib/robot_lab/hook_context.rb +194 -0
  116. data/lib/robot_lab/hook_registry.rb +55 -0
  117. data/lib/robot_lab/hooks.rb +87 -0
  118. data/lib/robot_lab/mcp/connection_poller.rb +2 -2
  119. data/lib/robot_lab/names.rb +402 -0
  120. data/lib/robot_lab/narrator.rb +87 -0
  121. data/lib/robot_lab/network.rb +77 -20
  122. data/lib/robot_lab/robot/agent_skill_matching.rb +1 -3
  123. data/lib/robot_lab/robot/budget.rb +89 -0
  124. data/lib/robot_lab/robot/bus_messaging.rb +90 -18
  125. data/lib/robot_lab/robot/hooking.rb +56 -0
  126. data/lib/robot_lab/robot/mcp_management.rb +8 -2
  127. data/lib/robot_lab/robot/template_rendering.rb +18 -5
  128. data/lib/robot_lab/robot.rb +233 -89
  129. data/lib/robot_lab/run_config.rb +24 -5
  130. data/lib/robot_lab/runnable.rb +51 -0
  131. data/lib/robot_lab/sandbox/null.rb +13 -0
  132. data/lib/robot_lab/sandbox/seatbelt.rb +104 -0
  133. data/lib/robot_lab/sandbox.rb +52 -0
  134. data/lib/robot_lab/script_tool.rb +60 -2
  135. data/lib/robot_lab/task.rb +26 -20
  136. data/lib/robot_lab/tool.rb +52 -11
  137. data/lib/robot_lab/version.rb +1 -1
  138. data/lib/robot_lab.rb +70 -15
  139. data/mkdocs.yml +7 -1
  140. metadata +41 -99
  141. data/examples/temp.md +0 -51
  142. data/site/404.html +0 -2300
  143. data/site/api/core/index.html +0 -2706
  144. data/site/api/core/memory/index.html +0 -3793
  145. data/site/api/core/network/index.html +0 -3500
  146. data/site/api/core/robot/index.html +0 -4566
  147. data/site/api/core/state/index.html +0 -3390
  148. data/site/api/core/tool/index.html +0 -3843
  149. data/site/api/index.html +0 -2635
  150. data/site/api/mcp/client/index.html +0 -3435
  151. data/site/api/mcp/index.html +0 -2783
  152. data/site/api/mcp/server/index.html +0 -3252
  153. data/site/api/mcp/transports/index.html +0 -3352
  154. data/site/api/messages/index.html +0 -2641
  155. data/site/api/messages/text-message/index.html +0 -3087
  156. data/site/api/messages/tool-call-message/index.html +0 -3159
  157. data/site/api/messages/tool-result-message/index.html +0 -3252
  158. data/site/api/messages/user-message/index.html +0 -3212
  159. data/site/api/streaming/context/index.html +0 -3282
  160. data/site/api/streaming/events/index.html +0 -3347
  161. data/site/api/streaming/index.html +0 -2738
  162. data/site/architecture/core-concepts/index.html +0 -3757
  163. data/site/architecture/index.html +0 -2797
  164. data/site/architecture/message-flow/index.html +0 -3238
  165. data/site/architecture/network-orchestration/index.html +0 -3433
  166. data/site/architecture/robot-execution/index.html +0 -3140
  167. data/site/architecture/state-management/index.html +0 -3498
  168. data/site/assets/css/custom.css +0 -56
  169. data/site/assets/images/favicon.png +0 -0
  170. data/site/assets/images/robot_lab.jpg +0 -0
  171. data/site/assets/javascripts/bundle.79ae519e.min.js +0 -16
  172. data/site/assets/javascripts/bundle.79ae519e.min.js.map +0 -7
  173. data/site/assets/javascripts/lunr/min/lunr.ar.min.js +0 -1
  174. data/site/assets/javascripts/lunr/min/lunr.da.min.js +0 -18
  175. data/site/assets/javascripts/lunr/min/lunr.de.min.js +0 -18
  176. data/site/assets/javascripts/lunr/min/lunr.du.min.js +0 -18
  177. data/site/assets/javascripts/lunr/min/lunr.el.min.js +0 -1
  178. data/site/assets/javascripts/lunr/min/lunr.es.min.js +0 -18
  179. data/site/assets/javascripts/lunr/min/lunr.fi.min.js +0 -18
  180. data/site/assets/javascripts/lunr/min/lunr.fr.min.js +0 -18
  181. data/site/assets/javascripts/lunr/min/lunr.he.min.js +0 -1
  182. data/site/assets/javascripts/lunr/min/lunr.hi.min.js +0 -1
  183. data/site/assets/javascripts/lunr/min/lunr.hu.min.js +0 -18
  184. data/site/assets/javascripts/lunr/min/lunr.hy.min.js +0 -1
  185. data/site/assets/javascripts/lunr/min/lunr.it.min.js +0 -18
  186. data/site/assets/javascripts/lunr/min/lunr.ja.min.js +0 -1
  187. data/site/assets/javascripts/lunr/min/lunr.jp.min.js +0 -1
  188. data/site/assets/javascripts/lunr/min/lunr.kn.min.js +0 -1
  189. data/site/assets/javascripts/lunr/min/lunr.ko.min.js +0 -1
  190. data/site/assets/javascripts/lunr/min/lunr.multi.min.js +0 -1
  191. data/site/assets/javascripts/lunr/min/lunr.nl.min.js +0 -18
  192. data/site/assets/javascripts/lunr/min/lunr.no.min.js +0 -18
  193. data/site/assets/javascripts/lunr/min/lunr.pt.min.js +0 -18
  194. data/site/assets/javascripts/lunr/min/lunr.ro.min.js +0 -18
  195. data/site/assets/javascripts/lunr/min/lunr.ru.min.js +0 -18
  196. data/site/assets/javascripts/lunr/min/lunr.sa.min.js +0 -1
  197. data/site/assets/javascripts/lunr/min/lunr.stemmer.support.min.js +0 -1
  198. data/site/assets/javascripts/lunr/min/lunr.sv.min.js +0 -18
  199. data/site/assets/javascripts/lunr/min/lunr.ta.min.js +0 -1
  200. data/site/assets/javascripts/lunr/min/lunr.te.min.js +0 -1
  201. data/site/assets/javascripts/lunr/min/lunr.th.min.js +0 -1
  202. data/site/assets/javascripts/lunr/min/lunr.tr.min.js +0 -18
  203. data/site/assets/javascripts/lunr/min/lunr.vi.min.js +0 -1
  204. data/site/assets/javascripts/lunr/min/lunr.zh.min.js +0 -1
  205. data/site/assets/javascripts/lunr/tinyseg.js +0 -206
  206. data/site/assets/javascripts/lunr/wordcut.js +0 -6708
  207. data/site/assets/javascripts/workers/search.2c215733.min.js +0 -42
  208. data/site/assets/javascripts/workers/search.2c215733.min.js.map +0 -7
  209. data/site/assets/stylesheets/main.484c7ddc.min.css +0 -1
  210. data/site/assets/stylesheets/main.484c7ddc.min.css.map +0 -1
  211. data/site/assets/stylesheets/palette.ab4e12ef.min.css +0 -1
  212. data/site/assets/stylesheets/palette.ab4e12ef.min.css.map +0 -1
  213. data/site/concepts/index.html +0 -3455
  214. data/site/examples/basic-chat/index.html +0 -2880
  215. data/site/examples/index.html +0 -2907
  216. data/site/examples/mcp-server/index.html +0 -3018
  217. data/site/examples/multi-robot-network/index.html +0 -3131
  218. data/site/examples/rails-application/index.html +0 -3329
  219. data/site/examples/tool-usage/index.html +0 -3085
  220. data/site/getting-started/configuration/index.html +0 -3745
  221. data/site/getting-started/index.html +0 -2572
  222. data/site/getting-started/installation/index.html +0 -2981
  223. data/site/getting-started/quick-start/index.html +0 -2942
  224. data/site/guides/building-robots/index.html +0 -4290
  225. data/site/guides/creating-networks/index.html +0 -3858
  226. data/site/guides/index.html +0 -2586
  227. data/site/guides/mcp-integration/index.html +0 -3581
  228. data/site/guides/memory/index.html +0 -3586
  229. data/site/guides/rails-integration/index.html +0 -4019
  230. data/site/guides/streaming/index.html +0 -3157
  231. data/site/guides/using-tools/index.html +0 -3802
  232. data/site/index.html +0 -2671
  233. data/site/search/search_index.json +0 -1
  234. data/site/sitemap.xml +0 -183
  235. data/site/sitemap.xml.gz +0 -0
  236. data/site/tags.json +0 -1
@@ -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/config.yml`
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.max_iterations #=> 10
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 ROBOT_LAB_MAX_ITERATIONS=20
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
- ROBOT_LAB_MAX_ITERATIONS --> max_iterations
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
- defaults:
77
- ruby_llm:
78
- anthropic_api_key: <%= ENV['ANTHROPIC_API_KEY'] %>
79
- model: claude-sonnet-4
80
- request_timeout: 120
81
-
82
- max_iterations: 15
83
- template_path: prompts
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
- !!! tip "ERB support"
106
- Config files support ERB templating, so you can reference environment variables with `<%= ENV['...'] %>`. This is useful for keeping secrets out of config files while still using YAML structure.
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/config.yml` for personal defaults that apply across all your projects:
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/config.yml"
113
- defaults:
114
- ruby_llm:
115
- anthropic_api_key: <%= ENV['ANTHROPIC_API_KEY'] %>
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 whitelist |
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.provider` | `:anthropic` | Default LLM provider |
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,39 @@ 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
260
+
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.
265
+
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
+ ```
278
+
279
+ ### Skill-Script Sandboxing (`sandbox:` section)
189
280
 
190
- Default chat parameters applied to all robots unless overridden:
281
+ Opt-in confinement for the scripts a [skill bundle](../guides/using-tools.md#skill-scripts-and-sandboxing) exposes as tools. Disabled by default — scripts run exactly as before until you turn it on:
191
282
 
192
283
  | Key | Default | Description |
193
284
  |-----|---------|-------------|
194
- | `chat.with_temperature` | `0.7` | Controls randomness (0.0-2.0) |
195
- | `chat.with_params.top_p` | `null` | Nucleus sampling threshold |
196
- | `chat.with_params.top_k` | `null` | Top-k sampling |
197
- | `chat.with_params.max_tokens` | `null` | Maximum tokens in response |
198
- | `chat.with_params.presence_penalty` | `null` | Presence penalty (-2.0 to 2.0) |
199
- | `chat.with_params.frequency_penalty` | `null` | Frequency penalty (-2.0 to 2.0) |
200
- | `chat.with_params.stop` | `null` | Stop sequences |
285
+ | `sandbox.enabled` | `false` | Turn on OS-level confinement for skill scripts |
286
+ | `sandbox.fs_read` | `["."]` | Ceiling: paths any skill script may read (relative to cwd) |
287
+ | `sandbox.fs_write` | `[]` | Ceiling: paths any skill script may write |
288
+ | `sandbox.network` | `false` | Ceiling: whether any skill script may use the network |
289
+ | `sandbox.timeout` | `60` | Ceiling: max seconds a skill script may run |
290
+
291
+ These are a **ceiling**, not a grant — the actual permissions a script runs with are the intersection of this ceiling and what the individual SKILL.md declares for itself. See [Skill Scripts and Sandboxing](../guides/using-tools.md#skill-scripts-and-sandboxing) for how the two combine and which platforms actually enforce confinement.
201
292
 
202
293
  ## Runtime-Only Attributes
203
294
 
@@ -228,7 +319,8 @@ This clears the cached config and reloads from all sources on next access.
228
319
 
229
320
  ## Environment-Specific Configuration
230
321
 
231
- 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:
232
324
 
233
325
  === "Development"
234
326
 
@@ -242,10 +334,10 @@ The `defaults.yml` shipped with RobotLab includes environment-specific overrides
242
334
 
243
335
  ```yaml
244
336
  test:
245
- max_iterations: 3
246
- streaming_enabled: false
337
+ max_iterations: 3 # reserved, no effect
338
+ streaming_enabled: false # reserved, no effect
247
339
  ruby_llm:
248
- model: claude-haiku-3-5
340
+ model: claude-3-haiku-20240307
249
341
  request_timeout: 30
250
342
  max_retries: 1
251
343
  log_level: :warn
@@ -255,8 +347,8 @@ The `defaults.yml` shipped with RobotLab includes environment-specific overrides
255
347
 
256
348
  ```yaml
257
349
  production:
258
- streaming_enabled: false
259
- max_iterations: 20
350
+ streaming_enabled: false # reserved, no effect
351
+ max_iterations: 20 # reserved, no effect
260
352
  ruby_llm:
261
353
  request_timeout: 180
262
354
  max_retries: 5
@@ -265,26 +357,56 @@ The `defaults.yml` shipped with RobotLab includes environment-specific overrides
265
357
 
266
358
  The current environment is determined automatically (via `RAILS_ENV`, `RACK_ENV`, or defaults to `development`).
267
359
 
268
- ## Rails Integration
269
-
270
- In Rails, RobotLab is configured automatically via its Railtie. The logger defaults to `Rails.logger`, and templates default to `app/prompts/`.
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.
271
364
 
272
- Create a project config file for Rails-specific settings:
365
+ ## Rails Integration
273
366
 
274
- ```yaml title="config/robot_lab.yml"
275
- defaults:
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:
276
385
  ruby_llm:
277
386
  anthropic_api_key: <%= Rails.application.credentials.anthropic_api_key %>
278
387
  model: claude-sonnet-4
279
-
388
+ request_timeout: 180
389
+ max_retries: 5
280
390
  template_path: null # auto-detects app/prompts in Rails
281
391
 
282
392
  production:
283
393
  ruby_llm:
394
+ anthropic_api_key: <%= Rails.application.credentials.anthropic_api_key %>
395
+ model: claude-sonnet-4
284
396
  request_timeout: 180
285
397
  max_retries: 5
286
398
  ```
287
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
+
288
410
  You can also use Rails credentials:
289
411
 
290
412
  ```bash
@@ -297,13 +419,10 @@ anthropic_api_key: sk-ant-...
297
419
  openai_api_key: sk-...
298
420
  ```
299
421
 
300
- Then reference them in your config file with ERB:
301
-
302
- ```yaml title="config/robot_lab.yml"
303
- defaults:
304
- ruby_llm:
305
- anthropic_api_key: <%= Rails.application.credentials.anthropic_api_key %>
306
- ```
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.
307
426
 
308
427
  ## RunConfig: Shared Operational Defaults
309
428
 
@@ -348,13 +467,32 @@ robot = RobotLab.build(
348
467
  temperature: 0.9 # overrides shared config's 0.5
349
468
  )
350
469
 
351
- # Network applies config to all member robots
470
+ # Network-level config
352
471
  network = RobotLab.create_network(name: "pipeline", config: shared) do
353
472
  task :analyzer, analyzer_robot, depends_on: :none
354
473
  task :writer, writer_robot, depends_on: [:analyzer]
355
474
  end
356
475
  ```
357
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
+
358
496
  ### Merging Configs
359
497
 
360
498
  RunConfig supports merge semantics where the more-specific config's values win:
@@ -373,8 +511,10 @@ effective.temperature #=> 0.9 (overridden)
373
511
  |----------|--------|
374
512
  | **LLM** | `model`, `temperature`, `top_p`, `top_k`, `max_tokens`, `presence_penalty`, `frequency_penalty`, `stop` |
375
513
  | **Tools** | `mcp`, `tools` |
376
- | **Callbacks** | `on_tool_call`, `on_tool_result` |
377
- | **Infrastructure** | `bus`, `enable_cache`, `max_tool_rounds`, `token_budget`, `ractor_pool_size`, `max_concurrent_robots`, `doom_loop_threshold`, `auto_compact`, `compact_threshold` |
514
+ | **Callbacks** | `on_tool_call`, `on_tool_result`, `on_content` |
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` |
516
+
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).
378
518
 
379
519
  ### RunConfig vs RobotLab.config
380
520
 
@@ -394,41 +534,107 @@ Individual robots can override the global model and other settings:
394
534
  robot = RobotLab.build(
395
535
  name: "fast_bot",
396
536
  system_prompt: "You are a quick responder.",
397
- model: "claude-haiku-3-5",
537
+ model: "claude-3-haiku-20240307",
398
538
  temperature: 0.3,
399
539
  max_tokens: 500
400
540
  )
401
541
 
402
542
  # Or use chaining at runtime
403
- robot.with_temperature(0.9).with_max_tokens(2000).run("Tell me a story.")
543
+ robot.with_temperature(0.9).with_params(max_tokens: 2000).run("Tell me a story.")
404
544
  ```
405
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
+
406
555
  ## Hierarchical MCP and Tools
407
556
 
408
557
  MCP servers and tools use a hierarchical configuration: `runtime > robot > network > global`. Each level can specify:
409
558
 
410
559
  - `:inherit` -- Use the parent level's configuration
411
560
  - `:none` -- No MCP servers or tools at this level
412
- - An explicit array -- Specific servers or tools
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:`.
413
615
 
414
616
  ```ruby
415
- # Robot inheriting network MCP config
416
- robot = RobotLab.build(
417
- name: "agent",
418
- system_prompt: "You are helpful.",
419
- mcp: :inherit,
420
- tools: :inherit
421
- )
422
-
423
- # Robot with no MCP, specific tools
617
+ # Correct: attach tools at build time, request them at run time
424
618
  robot = RobotLab.build(
425
619
  name: "calculator",
426
620
  system_prompt: "You solve math problems.",
427
- mcp: :none,
428
621
  local_tools: [Calculator]
429
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)
430
634
  ```
431
635
 
636
+ See [Runtime Tool Filtering](../guides/using-tools.md#runtime-tool-filtering) for the full `:inherit`/`:none`/array semantics.
637
+
432
638
  ## Next Steps
433
639
 
434
640
  - [Building Robots](../guides/building-robots.md) - Create custom robots