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
data/docs/api/errors.md CHANGED
@@ -15,9 +15,14 @@ StandardError
15
15
  ├── RobotLab::MCPError
16
16
  ├── RobotLab::BusError
17
17
  ├── RobotLab::RactorBoundaryError
18
- └── RobotLab::ToolError
18
+ ├── RobotLab::ToolError
19
+ ├── RobotLab::BudgetExceeded
20
+ └── RobotLab::AwaitTimeout
19
21
  ```
20
22
 
23
+ `AwaitTimeout` is defined in `lib/robot_lab/memory.rb` and raised by
24
+ [`Memory#get`](core/memory.md#get) when a blocking read expires.
25
+
21
26
  Additionally, `DelegationFuture` defines its own scoped error:
22
27
 
23
28
  ```
@@ -48,6 +53,8 @@ Raised when configuration is invalid or missing required values.
48
53
 
49
54
  ```ruby
50
55
  # Example: missing API key, invalid template path
56
+ begin
57
+ robot.run("hello")
51
58
  rescue RobotLab::ConfigurationError => e
52
59
  puts "Bad config: #{e.message}"
53
60
  end
@@ -57,15 +64,27 @@ end
57
64
 
58
65
  ## RobotLab::DependencyError < ConfigurationError
59
66
 
60
- Raised when a required optional gem dependency is not installed.
67
+ Raised when a required optional gem dependency is not installed. There are three
68
+ distinct raise sites in core, each with its own message:
69
+
70
+ | Raised by | Missing dependency | Message |
71
+ |-----------|--------------------|---------|
72
+ | `TextAnalysis.require_classifier!` — reached from `Convergence`, `HistoryCompressor`, `Robot#compress_history`, `Robot#search_history` | `classifier` gem | `The 'classifier' gem is required for text analysis features. Add it to your Gemfile: gem 'classifier', '~> 2.3'` |
73
+ | `Memory`'s document-store methods (`store_document`, `search_documents`, `document_keys`, `delete_document`) | `robot_lab-document_store` gem | `document storage requires the robot_lab-document_store gem. Add \`gem 'robot_lab-document_store'\` to your Gemfile.` |
74
+ | `Network#run` under `parallel_mode: :ractor` | `robot_lab-ractor` gem | `parallel_mode: :ractor requires the robot_lab-ractor gem. Add \`gem 'robot_lab-ractor'\` to your Gemfile.` |
61
75
 
62
76
  ```ruby
63
- # Triggered by: Convergence, HistoryCompressor when 'classifier' gem is absent
77
+ begin
78
+ robot.compress_history
64
79
  rescue RobotLab::DependencyError => e
65
- puts e.message # "Add gem 'classifier', '~> 2.3' to your Gemfile"
80
+ warn e.message
66
81
  end
67
82
  ```
68
83
 
84
+ Two places swallow it rather than propagate: `auto_compact: :context_window`
85
+ logs the message at `:warn` and skips compaction, and
86
+ `MCP::ServerDiscovery` rescues it and falls back.
87
+
69
88
  ---
70
89
 
71
90
  ## RobotLab::InferenceError
@@ -73,6 +92,8 @@ end
73
92
  Raised when LLM inference fails (API errors, timeouts, rate limits).
74
93
 
75
94
  ```ruby
95
+ begin
96
+ robot.run("hello")
76
97
  rescue RobotLab::InferenceError => e
77
98
  puts "LLM call failed: #{e.message}"
78
99
  end
@@ -93,13 +114,22 @@ robot = RobotLab.build(
93
114
  )
94
115
 
95
116
  begin
96
- robot.run("Run all steps.")
117
+ robot.run("Run all steps.", tools: :inherit)
97
118
  rescue RobotLab::ToolLoopError => e
98
- puts e.message # "Tool call limit of 10 exceeded"
119
+ puts e.message
120
+ # "Circuit breaker triggered: 11 tool calls exceeded max_tool_rounds (10)"
99
121
  robot.clear_messages # required before reuse
100
122
  end
101
123
  ```
102
124
 
125
+ The message format is:
126
+
127
+ ```
128
+ Circuit breaker triggered: <N> tool calls exceeded max_tool_rounds (<M>)
129
+ ```
130
+
131
+ `N` is the call count that tripped the breaker (always `M + 1`); `M` is the configured limit.
132
+
103
133
  ---
104
134
 
105
135
  ## RobotLab::ToolNotFoundError
@@ -113,11 +143,15 @@ Raised when a tool name is referenced but cannot be found in the `ToolManifest`.
113
143
  Raised when MCP server communication fails (connection refused, timeout, protocol error).
114
144
 
115
145
  ```ruby
146
+ begin
147
+ robot.run("hello", mcp: :inherit, tools: :inherit)
116
148
  rescue RobotLab::MCPError => e
117
149
  puts "MCP failed: #{e.message}"
118
150
  end
119
151
  ```
120
152
 
153
+ `MCPError.new(message, retryable: true)` marks a specific instance as safe to retry (see [Retryable Errors](#retryable-errors) below). The connection-lost and timeout errors raised internally by `MCP::ConnectionPoller` are always constructed with `retryable: true`.
154
+
121
155
  ---
122
156
 
123
157
  ## RobotLab::BusError
@@ -125,6 +159,8 @@ end
125
159
  Raised when message bus communication fails (no bus configured, channel not found).
126
160
 
127
161
  ```ruby
162
+ begin
163
+ robot.send_message(to: :bob, content: "hi")
128
164
  rescue RobotLab::BusError => e
129
165
  puts "Bus error: #{e.message}"
130
166
  end
@@ -136,30 +172,132 @@ end
136
172
 
137
173
  Raised when a value cannot be made Ractor-shareable before crossing a Ractor boundary (e.g., a live `IO` object, a `Proc`, or an object with mutable state).
138
174
 
175
+ !!! note "The error class is core; the machinery that raises it is not"
176
+ `RobotLab::RactorBoundaryError` is defined in core
177
+ (`lib/robot_lab/error.rb`) so hosts can rescue it without loading anything
178
+ extra. But `RactorBoundary`, `RactorWorkerPool`, and `RactorMemoryProxy` are
179
+ **not defined in core** — they ship in the separate **`robot_lab-ractor`**
180
+ gem. Core only *references* `RactorBoundary.freeze_deep` from
181
+ `Network#build_robot_spec`, which is reachable only under
182
+ `parallel_mode: :ractor`, which itself raises `DependencyError` unless that
183
+ gem is loaded.
184
+
139
185
  ```ruby
186
+ require "robot_lab"
187
+ require "robot_lab/ractor" # provides RactorBoundary and the worker pool
188
+
140
189
  begin
141
190
  RobotLab::RactorBoundary.freeze_deep({ io: StringIO.new })
142
191
  rescue RobotLab::RactorBoundaryError => e
143
- puts e.message # "Cannot make value Ractor-shareable: ..."
192
+ puts e.message
144
193
  end
145
194
  ```
146
195
 
147
- Raised proactively by `RactorWorkerPool#submit` and `RactorMemoryProxy#set` before any Ractor is involved.
148
-
149
196
  ---
150
197
 
151
198
  ## RobotLab::ToolError
152
199
 
153
- Raised when a tool fails during execution inside a Ractor worker (the pool unwraps `RactorJobError` and re-raises as `ToolError`).
200
+ An opt-in error type for tool failures. Core raises it nowhere itself you raise
201
+ it from your own `execute`, and the `robot_lab-ractor` worker pool raises it when
202
+ unwrapping a failure that happened inside a Ractor.
203
+
204
+ `ToolError.new(message, retryable: true)` marks a specific instance as retryable.
205
+
206
+ When a tool raises inside `Tool#call`, the error is caught and returned to the
207
+ LLM as text rather than propagating (unless the class sets
208
+ `self.raise_on_error = true`). RobotLab appends `" (retryable)"` whenever
209
+ `RobotLab::Errors.retryable?(error)` is true, so the model itself can see that
210
+ another attempt is worth trying:
211
+
212
+ ```ruby
213
+ class Fetch < RobotLab::Tool
214
+ description "Fetch a record"
215
+ param :id, type: "string", desc: "record id"
216
+
217
+ def execute(id:)
218
+ raise RobotLab::ToolError.new("upstream unavailable", retryable: true)
219
+ end
220
+ end
221
+
222
+ Fetch.new.call({ "id" => "1" })
223
+ # => "Error (fetch): upstream unavailable (retryable)"
224
+ ```
225
+
226
+ Unlike the generic `StandardError` path, the `ToolError` path writes **nothing**
227
+ to the logger. See [Tool#call](core/tool.md#call).
228
+
229
+ ---
230
+
231
+ ## RobotLab::BudgetExceeded
232
+
233
+ Raised when a Robot's configured `token_budget` or `cost_budget` is already exhausted **before** an LLM call is attempted (see [Budgets](../guides/observability.md#budgets-token-cost) and `RobotLab::Budget::Ledger`). This is distinct from the pre-existing `InferenceError` "Token budget exceeded" message, which covers a call that *completed* but pushed cumulative usage over budget — `BudgetExceeded` means the call was refused outright, before any tokens were spent.
234
+
235
+ ```ruby
236
+ robot = RobotLab.build(name: "capped", system_prompt: "...", cost_budget: 0.50)
237
+
238
+ begin
239
+ robot.run("Do the expensive thing")
240
+ rescue RobotLab::BudgetExceeded => e
241
+ puts e.message # "budget exceeded for cost: 0.62 > 0.5"
242
+ end
243
+ ```
244
+
245
+ Raised by `Budget::Ledger#reserve!`; the message format is
246
+ `"budget exceeded for <dimension>: <committed + amount> > <limit>"` where
247
+ `<dimension>` is `tokens` or `cost`.
248
+
249
+ The post-call `InferenceError` messages are formatted differently:
250
+
251
+ | Dimension | Message |
252
+ |-----------|---------|
253
+ | `token_budget` | `Token budget exceeded: <N> tokens used, budget is <M>` |
254
+ | `cost_budget` | `Cost budget exceeded: $0.523100 used, budget is $0.500000` (both values via `%.6f`) |
255
+
256
+ ---
257
+
258
+ ## RobotLab::AwaitTimeout
259
+
260
+ Raised by [`Memory#get`](core/memory.md#get) when a blocking read
261
+ (`wait: true` or `wait: <seconds>`) expires before the key is written.
154
262
 
155
263
  ```ruby
156
264
  begin
157
- pool.submit("MyTool", { input: "bad" })
158
- rescue RobotLab::ToolError => e
159
- puts e.message # "Tool 'MyTool' failed in Ractor: ..."
265
+ memory.get(:sentiment, wait: 30)
266
+ rescue RobotLab::AwaitTimeout => e
267
+ puts e.message # "Timeout waiting for :sentiment after 30 seconds"
160
268
  end
161
269
  ```
162
270
 
271
+ With multiple keys the timeout is applied **per missing key**, so
272
+ `get(:a, :b, wait: 30)` can block for up to 60 seconds before raising.
273
+
274
+ Not retryable per `RobotLab::Errors.retryable?` — it falls into the
275
+ "everything else" bucket.
276
+
277
+ ---
278
+
279
+ ## Retryable Errors
280
+
281
+ `RobotLab::Errors.retryable?(error)` classifies whether a host (an ActiveJob `retry_on` list, `robot_lab-to`'s takeover loop, a custom retry wrapper) should retry the operation that raised `error`:
282
+
283
+ | Error | Retryable? |
284
+ |-------|------------|
285
+ | `InferenceError` (and subclasses, except `ToolLoopError`) | Always |
286
+ | `ToolLoopError` | Never — it's a circuit breaker; retrying immediately re-triggers the same loop |
287
+ | `MCPError` / `ToolError` | Only when raised with `retryable: true` at the raise site |
288
+ | Everything else (`ConfigurationError`, `ToolNotFoundError`, `DependencyError`, `RactorBoundaryError`, `BusError`, `BudgetExceeded`, `AwaitTimeout`, `DelegationTimeout`, non-RobotLab errors) | Never |
289
+
290
+ ```ruby
291
+ begin
292
+ robot.run(task)
293
+ rescue RobotLab::Error => e
294
+ retry if RobotLab::Errors.retryable?(e)
295
+ raise
296
+ end
297
+ ```
298
+
299
+ `RobotLab::Errors.retryable_classes` returns `[RobotLab::InferenceError]` — an always-retryable allow-list for explicit ActiveJob-style `retry_on` declarations. It excludes `MCPError`/`ToolError` because their retryability is per-raise (via `retryable:`), not per-class.
300
+
163
301
  ---
164
302
 
165
303
  ## RobotLab::DelegationFuture::DelegationTimeout
@@ -181,5 +319,5 @@ end
181
319
  ## Related
182
320
 
183
321
  - [Building Robots](../guides/building-robots.md) — Tool loop circuit breaker, delegation
184
- - [Ractor Parallelism](../guides/ractor-parallelism.md) — Ractor boundary and tool errors
322
+ - [Observability & Safety](../guides/observability.md) — circuit breaker and tool loop errors
185
323
  - [MCP Integration](../guides/mcp-integration.md) — MCP connection errors