robot_lab 0.2.8 → 0.3.1

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 (262) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -0
  3. data/README.md +16 -18
  4. data/lib/robot_lab/ask_user.rb +3 -3
  5. data/lib/robot_lab/config/defaults.yml +5 -5
  6. data/lib/robot_lab/config.rb +2 -2
  7. data/lib/robot_lab/mcp/transports/streamable_http.rb +2 -4
  8. data/lib/robot_lab/memory.rb +16 -7
  9. data/lib/robot_lab/robot/hooking.rb +26 -0
  10. data/lib/robot_lab/robot/result_building.rb +119 -0
  11. data/lib/robot_lab/robot.rb +37 -120
  12. data/lib/robot_lab/run_config.rb +52 -20
  13. data/lib/robot_lab/tool.rb +7 -12
  14. data/lib/robot_lab/version.rb +1 -1
  15. data/lib/robot_lab.rb +3 -3
  16. metadata +15 -274
  17. data/.architecture/AGENTS.md +0 -32
  18. data/.architecture/config.yml +0 -8
  19. data/.architecture/members.yml +0 -60
  20. data/.architecture/reviews/feature-free-will.md +0 -490
  21. data/.architecture/reviews/overall-codebase.md +0 -427
  22. data/.claude/settings.local.json +0 -57
  23. data/.codex/config.toml +0 -2
  24. data/.envrc +0 -6
  25. data/.github/workflows/deploy-github-pages.yml +0 -52
  26. data/.irbrc +0 -6
  27. data/.loki +0 -65
  28. data/.quality/reek_baseline.txt +0 -43
  29. data/.rubocop.yml +0 -10
  30. data/Archspec.rb +0 -44
  31. data/CLAUDE.md +0 -139
  32. data/COMMITS.md +0 -196
  33. data/Rakefile +0 -193
  34. data/_typos.toml +0 -21
  35. data/agent2agent_review.md +0 -192
  36. data/agentf_improvements.md +0 -253
  37. data/agents.md +0 -14
  38. data/docs/api/core/index.md +0 -147
  39. data/docs/api/core/memory.md +0 -578
  40. data/docs/api/core/network.md +0 -553
  41. data/docs/api/core/result.md +0 -211
  42. data/docs/api/core/robot.md +0 -1667
  43. data/docs/api/core/state.md +0 -145
  44. data/docs/api/core/tool.md +0 -520
  45. data/docs/api/errors.md +0 -323
  46. data/docs/api/hooks.md +0 -469
  47. data/docs/api/index.md +0 -137
  48. data/docs/api/mcp/client.md +0 -387
  49. data/docs/api/mcp/index.md +0 -267
  50. data/docs/api/mcp/server.md +0 -252
  51. data/docs/api/mcp/transports.md +0 -339
  52. data/docs/api/messages/index.md +0 -126
  53. data/docs/api/messages/text-message.md +0 -170
  54. data/docs/api/messages/tool-call-message.md +0 -206
  55. data/docs/api/messages/tool-result-message.md +0 -242
  56. data/docs/api/messages/user-message.md +0 -232
  57. data/docs/api/skills.md +0 -423
  58. data/docs/api/streaming/context.md +0 -280
  59. data/docs/api/streaming/events.md +0 -185
  60. data/docs/api/streaming/index.md +0 -156
  61. data/docs/api/support.md +0 -846
  62. data/docs/architecture/core-concepts.md +0 -575
  63. data/docs/architecture/index.md +0 -252
  64. data/docs/architecture/message-flow.md +0 -366
  65. data/docs/architecture/network-orchestration.md +0 -478
  66. data/docs/architecture/robot-execution.md +0 -396
  67. data/docs/architecture/state-management.md +0 -410
  68. data/docs/assets/css/custom.css +0 -56
  69. data/docs/assets/images/robot_lab.jpg +0 -0
  70. data/docs/concepts.md +0 -641
  71. data/docs/examples/basic-chat.md +0 -258
  72. data/docs/examples/index.md +0 -425
  73. data/docs/examples/mcp-server.md +0 -403
  74. data/docs/examples/multi-robot-network.md +0 -476
  75. data/docs/examples/tool-usage.md +0 -468
  76. data/docs/getting-started/configuration.md +0 -645
  77. data/docs/getting-started/index.md +0 -56
  78. data/docs/getting-started/installation.md +0 -200
  79. data/docs/getting-started/quick-start.md +0 -293
  80. data/docs/guides/building-robots.md +0 -1187
  81. data/docs/guides/creating-networks.md +0 -736
  82. data/docs/guides/hooks.md +0 -1206
  83. data/docs/guides/index.md +0 -78
  84. data/docs/guides/knowledge.md +0 -219
  85. data/docs/guides/mcp-integration.md +0 -740
  86. data/docs/guides/memory.md +0 -458
  87. data/docs/guides/observability.md +0 -733
  88. data/docs/guides/streaming.md +0 -323
  89. data/docs/guides/using-tools.md +0 -780
  90. data/docs/index.md +0 -280
  91. data/docs/superpowers/plans/2026-05-06-agentskills.md +0 -1303
  92. data/docs/superpowers/specs/2026-05-06-agentskills-design.md +0 -247
  93. data/examples/.envrc +0 -1
  94. data/examples/01_simple_robot.rb +0 -31
  95. data/examples/02_tools.rb +0 -109
  96. data/examples/03_network.rb +0 -118
  97. data/examples/04_mcp.rb +0 -215
  98. data/examples/05_streaming.rb +0 -137
  99. data/examples/06_prompt_templates.rb +0 -325
  100. data/examples/07_network_memory.rb +0 -313
  101. data/examples/08_llm_config.rb +0 -162
  102. data/examples/09_chaining.rb +0 -251
  103. data/examples/10_memory.rb +0 -315
  104. data/examples/11_network_introspection.rb +0 -239
  105. data/examples/12_message_bus.rb +0 -67
  106. data/examples/13_spawn.rb +0 -85
  107. data/examples/14_rusty_circuit/.envrc +0 -1
  108. data/examples/14_rusty_circuit/.gitignore +0 -1
  109. data/examples/14_rusty_circuit/README.md +0 -1
  110. data/examples/14_rusty_circuit/comic.rb +0 -149
  111. data/examples/14_rusty_circuit/display.rb +0 -217
  112. data/examples/14_rusty_circuit/heckler.rb +0 -65
  113. data/examples/14_rusty_circuit/open_mic.rb +0 -132
  114. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +0 -20
  115. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +0 -23
  116. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +0 -20
  117. data/examples/14_rusty_circuit/scout.rb +0 -165
  118. data/examples/14_rusty_circuit/show.log +0 -234
  119. data/examples/15_memory_network_and_bus/.envrc +0 -1
  120. data/examples/15_memory_network_and_bus/editor_in_chief.rb +0 -24
  121. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +0 -213
  122. data/examples/15_memory_network_and_bus/linux_writer.rb +0 -80
  123. data/examples/15_memory_network_and_bus/os_editor.rb +0 -48
  124. data/examples/15_memory_network_and_bus/os_writer.rb +0 -49
  125. data/examples/15_memory_network_and_bus/output/combined_article.md +0 -13
  126. data/examples/15_memory_network_and_bus/output/final_article.md +0 -13
  127. data/examples/15_memory_network_and_bus/output/linux_draft.md +0 -7
  128. data/examples/15_memory_network_and_bus/output/mac_draft.md +0 -7
  129. data/examples/15_memory_network_and_bus/output/memory.json +0 -13
  130. data/examples/15_memory_network_and_bus/output/revision_1.md +0 -18
  131. data/examples/15_memory_network_and_bus/output/revision_2.md +0 -13
  132. data/examples/15_memory_network_and_bus/output/windows_draft.md +0 -7
  133. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +0 -13
  134. data/examples/15_memory_network_and_bus/prompts/os_chief.md +0 -13
  135. data/examples/15_memory_network_and_bus/prompts/os_editor.md +0 -13
  136. data/examples/16_writers_room/.envrc +0 -1
  137. data/examples/16_writers_room/display.rb +0 -158
  138. data/examples/16_writers_room/output/.gitignore +0 -4
  139. data/examples/16_writers_room/output/README.md +0 -69
  140. data/examples/16_writers_room/output/opus_001.md +0 -263
  141. data/examples/16_writers_room/output/opus_001_notes.log +0 -470
  142. data/examples/16_writers_room/output/opus_002.md +0 -245
  143. data/examples/16_writers_room/output/opus_002_notes.log +0 -546
  144. data/examples/16_writers_room/output/opus_002_screenplay.md +0 -7989
  145. data/examples/16_writers_room/output/opus_002_screenplay_notes.md +0 -993
  146. data/examples/16_writers_room/prompts/screenplay_writer.md +0 -66
  147. data/examples/16_writers_room/prompts/writer.md +0 -37
  148. data/examples/16_writers_room/room.rb +0 -186
  149. data/examples/16_writers_room/tools.rb +0 -173
  150. data/examples/16_writers_room/writer.rb +0 -121
  151. data/examples/16_writers_room/writers_room.rb +0 -256
  152. data/examples/17_skills.rb +0 -234
  153. data/examples/18_rails/.envrc +0 -3
  154. data/examples/18_rails/.gitignore +0 -5
  155. data/examples/18_rails/Gemfile +0 -11
  156. data/examples/18_rails/README.md +0 -67
  157. data/examples/18_rails/Rakefile +0 -4
  158. data/examples/18_rails/app/controllers/application_controller.rb +0 -4
  159. data/examples/18_rails/app/controllers/chat_controller.rb +0 -50
  160. data/examples/18_rails/app/jobs/application_job.rb +0 -4
  161. data/examples/18_rails/app/jobs/robot_run_job.rb +0 -25
  162. data/examples/18_rails/app/models/application_record.rb +0 -5
  163. data/examples/18_rails/app/models/robot_lab_result.rb +0 -36
  164. data/examples/18_rails/app/models/robot_lab_thread.rb +0 -23
  165. data/examples/18_rails/app/robots/chat_robot.rb +0 -25
  166. data/examples/18_rails/app/tools/time_tool.rb +0 -9
  167. data/examples/18_rails/app/views/chat/_user_message.html.erb +0 -1
  168. data/examples/18_rails/app/views/chat/index.html.erb +0 -67
  169. data/examples/18_rails/app/views/layouts/application.html.erb +0 -49
  170. data/examples/18_rails/bin/dev +0 -7
  171. data/examples/18_rails/bin/rails +0 -6
  172. data/examples/18_rails/bin/setup +0 -15
  173. data/examples/18_rails/config/application.rb +0 -33
  174. data/examples/18_rails/config/cable.yml +0 -2
  175. data/examples/18_rails/config/database.yml +0 -5
  176. data/examples/18_rails/config/environment.rb +0 -4
  177. data/examples/18_rails/config/initializers/robot_lab.rb +0 -11
  178. data/examples/18_rails/config/routes.rb +0 -6
  179. data/examples/18_rails/config.ru +0 -4
  180. data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +0 -32
  181. data/examples/19_token_tracking.rb +0 -138
  182. data/examples/20_circuit_breaker.rb +0 -147
  183. data/examples/21_learning_loop.rb +0 -181
  184. data/examples/22_context_compression.rb +0 -172
  185. data/examples/23_convergence.rb +0 -133
  186. data/examples/24_structured_delegation.rb +0 -151
  187. data/examples/25_history_search/conversation.jsonl +0 -30
  188. data/examples/25_history_search.rb +0 -133
  189. data/examples/26_document_store/api_versioning_adr.md +0 -52
  190. data/examples/26_document_store/incident_postmortem.md +0 -46
  191. data/examples/26_document_store/postgres_runbook.md +0 -49
  192. data/examples/26_document_store/redis_caching_guide.md +0 -48
  193. data/examples/26_document_store/sidekiq_guide.md +0 -51
  194. data/examples/26_document_store.rb +0 -140
  195. data/examples/27_incident_response/incident_response.rb +0 -261
  196. data/examples/28_mcp_discovery.rb +0 -113
  197. data/examples/29_ractor_tools.rb +0 -240
  198. data/examples/30_ractor_network.rb +0 -252
  199. data/examples/31_launch_assessment.rb +0 -246
  200. data/examples/32_newsletter_reader.rb +0 -193
  201. data/examples/33_stock_generator.rb +0 -80
  202. data/examples/33_stock_predictor.rb +0 -327
  203. data/examples/34_agentskills.rb +0 -76
  204. data/examples/35_hooks.rb +0 -266
  205. data/examples/README.md +0 -533
  206. data/examples/common.rb +0 -148
  207. data/examples/prompts/assistant.md +0 -5
  208. data/examples/prompts/audit_trail.md +0 -8
  209. data/examples/prompts/billing.md +0 -10
  210. data/examples/prompts/classifier.md +0 -7
  211. data/examples/prompts/comedian.md +0 -6
  212. data/examples/prompts/comedy_critic.md +0 -10
  213. data/examples/prompts/configurable.md +0 -9
  214. data/examples/prompts/dispatcher.md +0 -12
  215. data/examples/prompts/entity_extractor.md +0 -14
  216. data/examples/prompts/escalation.md +0 -42
  217. data/examples/prompts/frontmatter_mcp_test.md +0 -9
  218. data/examples/prompts/frontmatter_named_test.md +0 -5
  219. data/examples/prompts/frontmatter_tools_test.md +0 -6
  220. data/examples/prompts/general.md +0 -7
  221. data/examples/prompts/github_assistant.md +0 -14
  222. data/examples/prompts/helper.md +0 -4
  223. data/examples/prompts/incident_responder.md +0 -18
  224. data/examples/prompts/keyword_extractor.md +0 -11
  225. data/examples/prompts/llm_config_demo.md +0 -20
  226. data/examples/prompts/order_support.md +0 -35
  227. data/examples/prompts/os_advocate.md +0 -13
  228. data/examples/prompts/os_chief.md +0 -13
  229. data/examples/prompts/os_editor.md +0 -13
  230. data/examples/prompts/parameterized_main_test.md +0 -6
  231. data/examples/prompts/pii_redactor.md +0 -7
  232. data/examples/prompts/product_support.md +0 -37
  233. data/examples/prompts/runbook_protocol.md +0 -12
  234. data/examples/prompts/sentiment_analyzer.md +0 -12
  235. data/examples/prompts/skill_a_test.md +0 -4
  236. data/examples/prompts/skill_b_test.md +0 -4
  237. data/examples/prompts/skill_config_test.md +0 -5
  238. data/examples/prompts/skill_cycle_a_test.md +0 -6
  239. data/examples/prompts/skill_cycle_b_test.md +0 -6
  240. data/examples/prompts/skill_description_test.md +0 -4
  241. data/examples/prompts/skill_leaf_test.md +0 -4
  242. data/examples/prompts/skill_nested_test.md +0 -6
  243. data/examples/prompts/skill_refs_main_test.md +0 -6
  244. data/examples/prompts/skill_self_ref_test.md +0 -6
  245. data/examples/prompts/skill_with_mcp_test.md +0 -9
  246. data/examples/prompts/skill_with_params_test.md +0 -6
  247. data/examples/prompts/skill_with_robot_name_test.md +0 -5
  248. data/examples/prompts/skill_with_tools_test.md +0 -6
  249. data/examples/prompts/sre_compliance.md +0 -10
  250. data/examples/prompts/structured_output.md +0 -7
  251. data/examples/prompts/synthesizer.md +0 -17
  252. data/examples/prompts/technical.md +0 -10
  253. data/examples/prompts/template_with_skills_test.md +0 -6
  254. data/examples/prompts/triage.md +0 -22
  255. data/examples/ruboruby.md +0 -423
  256. data/examples/xyzzy.rb +0 -97
  257. data/logfile +0 -8
  258. data/mkdocs.yml +0 -209
  259. data/robot_concurrency.md +0 -38
  260. data/simple_acp_review.md +0 -298
  261. data/temp.md +0 -6
  262. data/tool_manifest_plan.md +0 -155
@@ -1,323 +0,0 @@
1
- # Streaming Responses
2
-
3
- Stream LLM responses in real-time for better user experience.
4
-
5
- RobotLab has exactly two ways to receive streaming content, and both hand you a
6
- `RubyLLM::Chunk`:
7
-
8
- 1. **`on_content:`** — a callback wired at build time (or through `RunConfig`) that fires on *every* `run`
9
- 2. **A block passed to `run`** — `robot.run(msg) { |chunk| ... }`, for one-off streaming
10
-
11
- > [!IMPORTANT]
12
- > Read the content off the chunk with **`chunk.content`**. `RubyLLM::Chunk`
13
- > has no `#text` method — `chunk.text` raises `NoMethodError`.
14
-
15
- ## Streaming via `on_content:`
16
-
17
- Wire the callback once at build time; it fires on every subsequent `run`:
18
-
19
- ```ruby
20
- robot = RobotLab.build(
21
- name: "storyteller",
22
- system_prompt: "You are a creative storyteller.",
23
- on_content: ->(chunk) { print chunk.content }
24
- )
25
-
26
- result = robot.run("Tell me a story about a brave robot")
27
- puts
28
- puts result.last_text_content
29
- ```
30
-
31
- ## Streaming via a Block on `run`
32
-
33
- For one-off streaming, pass a block straight to `run`:
34
-
35
- ```ruby
36
- robot = RobotLab.build(
37
- name: "factbot",
38
- system_prompt: "You are concise. Answer in one sentence."
39
- )
40
-
41
- robot.run("What year was Ruby created?") { |chunk| print chunk.content }
42
- ```
43
-
44
- The block goes to the same place `on_content:` does — no need to reach for
45
- `robot.chat.ask` to get streaming, and reaching for it skips memory resolution,
46
- tool resolution, hooks, and budget accounting.
47
-
48
- ## Using Both Together
49
-
50
- When a robot has a stored `on_content:` *and* a block is passed to `run`, both
51
- fire for every chunk. The **stored callback fires first**, then the block:
52
-
53
- ```ruby
54
- stored_log = []
55
- block_log = []
56
-
57
- robot = RobotLab.build(
58
- name: "combo",
59
- system_prompt: "You are concise.",
60
- on_content: ->(chunk) { stored_log << chunk.content }
61
- )
62
-
63
- robot.run("What is Matz's full name?") { |chunk| block_log << chunk.content }
64
-
65
- stored_log == block_log # => true — both saw the same chunks
66
- ```
67
-
68
- ## Streaming via RunConfig
69
-
70
- `on_content` is a `RunConfig` field, so it participates in the config cascade:
71
-
72
- ```ruby
73
- config = RobotLab::RunConfig.new(
74
- model: "claude-sonnet-4",
75
- on_content: ->(chunk) { print chunk.content }
76
- )
77
-
78
- robot = RobotLab.build(
79
- name: "config_bot",
80
- system_prompt: "You are concise.",
81
- config: config
82
- )
83
-
84
- robot.run("Who designed the Ruby programming language?")
85
- ```
86
-
87
- > [!WARNING]
88
- > `on_content` is read from the **robot's own** config when the robot is
89
- > constructed. A network-level `config:` propagates only `mcp` and `tools` to
90
- > member robots — it will **not** supply `on_content` to them. Put the callback
91
- > on each robot (or on the `config:` you pass to that robot).
92
-
93
- ## Web Integration
94
-
95
- ### Rails Action Cable
96
-
97
- ```ruby
98
- class ChatChannel < ApplicationCable::Channel
99
- def receive(data)
100
- robot = RobotLab.build(
101
- name: "chat_bot",
102
- system_prompt: "You are a helpful chat assistant.",
103
- on_content: ->(chunk) {
104
- transmit({ event: "text.delta", content: chunk.content })
105
- }
106
- )
107
-
108
- robot.run(data["message"])
109
- transmit({ event: "run.completed" })
110
- end
111
- end
112
- ```
113
-
114
- ### Server-Sent Events
115
-
116
- ```ruby
117
- class StreamController < ApplicationController
118
- include ActionController::Live
119
-
120
- def create
121
- response.headers["Content-Type"] = "text/event-stream"
122
-
123
- robot = RobotLab.build(
124
- name: "stream_bot",
125
- system_prompt: "You are helpful."
126
- )
127
-
128
- robot.run(params[:message]) do |chunk|
129
- response.stream.write("data: #{chunk.content}\n\n")
130
- end
131
-
132
- response.stream.write("data: [DONE]\n\n")
133
- ensure
134
- response.stream.close
135
- end
136
- end
137
- ```
138
-
139
- ### WebSocket
140
-
141
- ```ruby
142
- # Using Faye WebSocket
143
- ws.on :message do |msg|
144
- robot.run(msg.data) { |chunk| ws.send(chunk.content) }
145
- end
146
- ```
147
-
148
- ## Progress Tracking
149
-
150
- Chunk callbacks are the right place to count characters. Tool activity is
151
- tracked separately, through the robot's `on_tool_call:` / `on_tool_result:`
152
- callbacks:
153
-
154
- ```ruby
155
- class StreamProgress
156
- attr_reader :chars, :tools
157
-
158
- def initialize
159
- @chars = 0
160
- @tools = 0
161
- end
162
-
163
- # Returns the streaming block to hand to run().
164
- def content_callback
165
- ->(chunk) {
166
- @chars += chunk.content.to_s.length
167
- print "\rReceived #{@chars} characters..."
168
- }
169
- end
170
-
171
- def tool_callback
172
- ->(tool_call) {
173
- @tools += 1
174
- puts "\nTool call ##{@tools}: #{tool_call.name}"
175
- }
176
- end
177
- end
178
-
179
- progress = StreamProgress.new
180
-
181
- robot = RobotLab.build(
182
- name: "assistant",
183
- system_prompt: "You are helpful.",
184
- local_tools: [WeatherTool],
185
- on_content: progress.content_callback,
186
- on_tool_call: progress.tool_callback
187
- )
188
-
189
- robot.run("Process this complex request", tools: :inherit)
190
- puts "\nTotal: #{progress.chars} chars, #{progress.tools} tool calls"
191
- ```
192
-
193
- > [!NOTE]
194
- > `chunk.content` can be `nil` on chunks that carry only metadata (tool-call
195
- > deltas, usage). Guard with `to_s` when accumulating, as above.
196
-
197
- > [!WARNING]
198
- > `run` defaults to `tools: :none`, so the tool callback above never fires
199
- > unless you pass `tools: :inherit` (or an explicit allowlist). See
200
- > [Using Tools](using-tools.md#runtime-tool-filtering).
201
-
202
- ## Without Streaming
203
-
204
- When streaming is not needed, call `run` with no `on_content:` and no block:
205
-
206
- ```ruby
207
- result = robot.run("Hello!")
208
- puts result.last_text_content
209
- ```
210
-
211
- ## Tool Callbacks
212
-
213
- `on_tool_call:` and `on_tool_result:` are constructor/`RunConfig` callbacks
214
- alongside `on_content:`. Each receives **one** argument:
215
-
216
- ```ruby
217
- robot = RobotLab.build(
218
- name: "assistant",
219
- system_prompt: "You are helpful.",
220
- local_tools: [WeatherTool],
221
- on_tool_call: ->(tool_call) { puts "\n[Tool] Calling: #{tool_call.name}" },
222
- on_tool_result: ->(result) { puts "[Tool] returned: #{result}" }
223
- )
224
-
225
- robot.run("What's the weather in Tokyo?", tools: :inherit)
226
- ```
227
-
228
- Note that `on_tool_result` does **not** receive the originating tool call —
229
- only the result. If you need to correlate the two, record the call in
230
- `on_tool_call` and pair them up yourself.
231
-
232
- > [!NOTE]
233
- > RobotLab installs these two callbacks on the underlying chat's
234
- > `on_tool_call` / `on_tool_result` hooks, which RubyLLM 1.16 has deprecated.
235
- > Building a robot with either callback therefore prints a RubyLLM deprecation
236
- > warning. The callbacks themselves work; the warning comes from the layer
237
- > below.
238
-
239
- ## Legacy RubyLLM Chat Callbacks
240
-
241
- `Robot` also exposes RubyLLM's chat-level callbacks
242
- (`on_new_message`, `on_end_message`, `on_tool_call`, `on_tool_result`).
243
-
244
- > [!CAUTION]
245
- > All four are **deprecated in RubyLLM 1.16** and slated for removal in
246
- > RubyLLM 2.0. Registering one emits a deprecation warning naming its
247
- > replacement: `before_message`, `after_message`, `before_tool_call`, and
248
- > `after_tool_result`. Prefer RobotLab's `on_content:` /
249
- > `on_tool_call:` / `on_tool_result:` constructor callbacks instead.
250
-
251
- Their arities are not what you might expect:
252
-
253
- | Callback | Replacement | Arguments received |
254
- |----------|-------------|--------------------|
255
- | `on_new_message` | `before_message` | **none** |
256
- | `on_end_message` | `after_message` | the completed `RubyLLM::Message` |
257
- | `on_tool_call` | `before_tool_call` | the `ToolCall` |
258
- | `on_tool_result` | `after_tool_result` | the **result only** |
259
-
260
- > [!WARNING]
261
- > `on_new_message` is **not** a streaming hook. It fires once when a message
262
- > begins and is invoked with **zero arguments** — a block written as
263
- > `do |message| ... end` receives `nil`, so `message.content` raises
264
- > `NoMethodError: undefined method 'content' for nil`. Use `on_content:` or a
265
- > `run` block for content.
266
-
267
- ```ruby
268
- # Correct usage of the legacy hooks (still deprecated):
269
- robot.chat.on_new_message { puts "--- message starting ---" } # no argument
270
- robot.chat.on_end_message { |message| puts "Length: #{message.content&.length}" }
271
- robot.chat.on_tool_result { |result| puts "Tool returned: #{result}" }
272
- ```
273
-
274
- ## Best Practices
275
-
276
- ### 1. Wire streaming before the run
277
-
278
- `on_content:` belongs in the constructor (or the `config:` you pass it); a
279
- `run` block belongs on the call itself. There is no "register then run" step.
280
-
281
- ### 2. Handle errors inside the callback
282
-
283
- An exception raised in a streaming callback propagates out of `run` and aborts
284
- the turn. Swallow client-side failures you can survive:
285
-
286
- ```ruby
287
- robot = RobotLab.build(
288
- name: "broadcaster",
289
- system_prompt: "You are helpful.",
290
- on_content: ->(chunk) do
291
- broadcast(chunk.content)
292
- rescue BroadcastError => e
293
- # Client disconnected, but let the run finish.
294
- RobotLab.config.logger.warn("Broadcast failed: #{e.message}")
295
- end
296
- )
297
- ```
298
-
299
- ### 3. Clean up resources
300
-
301
- ```ruby
302
- begin
303
- robot.run("Hello") { |chunk| stream_to_client(chunk.content) }
304
- ensure
305
- close_stream_connection
306
- end
307
- ```
308
-
309
- ## What About `RobotLab::Streaming`?
310
-
311
- `RobotLab::Streaming::Context`, `Streaming::Events`, and
312
- `Streaming::SequenceCounter` exist in the codebase and are documented in the
313
- [API reference](../api/streaming/index.md), but **nothing in `Robot` or
314
- `Network` constructs or publishes to them**. They are a standalone event-object
315
- toolkit you would have to drive yourself — not the path that `run` takes. For
316
- actual token streaming, use `on_content:` or a `run` block as shown above.
317
-
318
- ## Next Steps
319
-
320
- - [Building Robots](building-robots.md) - Robot creation
321
- - [Creating Networks](creating-networks.md) - Network patterns
322
- - [examples/05_streaming.rb](https://github.com/MadBomber/robot_lab/blob/main/examples/05_streaming.rb) - Runnable streaming example
323
- - [API Reference: Streaming](../api/streaming/index.md) - The standalone `Streaming::*` event objects