robot_lab 0.3.0 → 0.5.0

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 (274) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +60 -0
  3. data/README.md +29 -19
  4. data/lib/robot_lab/errors.rb +4 -0
  5. data/lib/robot_lab/hook_context.rb +9 -2
  6. data/lib/robot_lab/mcp/registry.rb +99 -0
  7. data/lib/robot_lab/mcp/server.rb +47 -83
  8. data/lib/robot_lab/mcp/server_discovery.rb +22 -9
  9. data/lib/robot_lab/mcp.rb +259 -0
  10. data/lib/robot_lab/robot/mcp_management.rb +159 -105
  11. data/lib/robot_lab/robot/tool_call_gating.rb +144 -0
  12. data/lib/robot_lab/robot.rb +72 -99
  13. data/lib/robot_lab/run_config.rb +3 -2
  14. data/lib/robot_lab/tool.rb +8 -23
  15. data/lib/robot_lab/version.rb +1 -1
  16. data/lib/robot_lab.rb +1 -4
  17. metadata +16 -292
  18. data/.architecture/AGENTS.md +0 -32
  19. data/.architecture/config.yml +0 -8
  20. data/.architecture/members.yml +0 -60
  21. data/.architecture/reviews/feature-free-will.md +0 -490
  22. data/.architecture/reviews/overall-codebase.md +0 -427
  23. data/.claude/settings.local.json +0 -57
  24. data/.codex/config.toml +0 -2
  25. data/.envrc +0 -6
  26. data/.github/workflows/deploy-github-pages.yml +0 -52
  27. data/.irbrc +0 -6
  28. data/.loki +0 -65
  29. data/.quality/reek_baseline.txt +0 -43
  30. data/.rubocop.yml +0 -10
  31. data/Archspec.rb +0 -44
  32. data/CLAUDE.md +0 -141
  33. data/COMMITS.md +0 -196
  34. data/Rakefile +0 -193
  35. data/_typos.toml +0 -21
  36. data/agent2agent_review.md +0 -192
  37. data/agentf_improvements.md +0 -253
  38. data/agents.md +0 -14
  39. data/docs/api/core/index.md +0 -147
  40. data/docs/api/core/memory.md +0 -578
  41. data/docs/api/core/network.md +0 -553
  42. data/docs/api/core/result.md +0 -211
  43. data/docs/api/core/robot.md +0 -1667
  44. data/docs/api/core/state.md +0 -145
  45. data/docs/api/core/tool.md +0 -520
  46. data/docs/api/errors.md +0 -323
  47. data/docs/api/hooks.md +0 -469
  48. data/docs/api/index.md +0 -137
  49. data/docs/api/mcp/client.md +0 -387
  50. data/docs/api/mcp/index.md +0 -267
  51. data/docs/api/mcp/server.md +0 -252
  52. data/docs/api/mcp/transports.md +0 -339
  53. data/docs/api/messages/index.md +0 -126
  54. data/docs/api/messages/text-message.md +0 -170
  55. data/docs/api/messages/tool-call-message.md +0 -206
  56. data/docs/api/messages/tool-result-message.md +0 -242
  57. data/docs/api/messages/user-message.md +0 -232
  58. data/docs/api/skills.md +0 -423
  59. data/docs/api/streaming/context.md +0 -280
  60. data/docs/api/streaming/events.md +0 -185
  61. data/docs/api/streaming/index.md +0 -156
  62. data/docs/api/support.md +0 -846
  63. data/docs/architecture/core-concepts.md +0 -575
  64. data/docs/architecture/index.md +0 -252
  65. data/docs/architecture/message-flow.md +0 -366
  66. data/docs/architecture/network-orchestration.md +0 -478
  67. data/docs/architecture/robot-execution.md +0 -396
  68. data/docs/architecture/state-management.md +0 -410
  69. data/docs/assets/css/custom.css +0 -56
  70. data/docs/assets/images/robot_lab.jpg +0 -0
  71. data/docs/concepts.md +0 -641
  72. data/docs/examples/basic-chat.md +0 -258
  73. data/docs/examples/index.md +0 -425
  74. data/docs/examples/mcp-server.md +0 -403
  75. data/docs/examples/multi-robot-network.md +0 -476
  76. data/docs/examples/tool-usage.md +0 -468
  77. data/docs/getting-started/configuration.md +0 -645
  78. data/docs/getting-started/index.md +0 -56
  79. data/docs/getting-started/installation.md +0 -197
  80. data/docs/getting-started/quick-start.md +0 -293
  81. data/docs/guides/building-robots.md +0 -1187
  82. data/docs/guides/creating-networks.md +0 -736
  83. data/docs/guides/hooks.md +0 -1206
  84. data/docs/guides/index.md +0 -78
  85. data/docs/guides/knowledge.md +0 -219
  86. data/docs/guides/mcp-integration.md +0 -740
  87. data/docs/guides/memory.md +0 -458
  88. data/docs/guides/observability.md +0 -733
  89. data/docs/guides/streaming.md +0 -323
  90. data/docs/guides/using-tools.md +0 -780
  91. data/docs/index.md +0 -280
  92. data/docs/superpowers/plans/2026-05-06-agentskills.md +0 -1303
  93. data/docs/superpowers/specs/2026-05-06-agentskills-design.md +0 -247
  94. data/examples/.envrc +0 -3
  95. data/examples/01_simple_robot.rb +0 -31
  96. data/examples/02_tools.rb +0 -109
  97. data/examples/03_network.rb +0 -118
  98. data/examples/04_mcp.rb +0 -217
  99. data/examples/05_streaming.rb +0 -137
  100. data/examples/06_prompt_templates.rb +0 -325
  101. data/examples/07_network_memory.rb +0 -313
  102. data/examples/08_llm_config.rb +0 -162
  103. data/examples/09_chaining.rb +0 -251
  104. data/examples/10_memory.rb +0 -315
  105. data/examples/11_network_introspection.rb +0 -239
  106. data/examples/12_message_bus.rb +0 -67
  107. data/examples/13_spawn.rb +0 -85
  108. data/examples/14_rusty_circuit/.envrc +0 -1
  109. data/examples/14_rusty_circuit/.gitignore +0 -1
  110. data/examples/14_rusty_circuit/README.md +0 -1
  111. data/examples/14_rusty_circuit/comic.rb +0 -149
  112. data/examples/14_rusty_circuit/display.rb +0 -217
  113. data/examples/14_rusty_circuit/heckler.rb +0 -65
  114. data/examples/14_rusty_circuit/open_mic.rb +0 -132
  115. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +0 -20
  116. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +0 -23
  117. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +0 -20
  118. data/examples/14_rusty_circuit/scout.rb +0 -165
  119. data/examples/14_rusty_circuit/show.log +0 -234
  120. data/examples/15_memory_network_and_bus/.envrc +0 -1
  121. data/examples/15_memory_network_and_bus/README.md +0 -66
  122. data/examples/15_memory_network_and_bus/editor_in_chief.rb +0 -24
  123. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +0 -213
  124. data/examples/15_memory_network_and_bus/linux_writer.rb +0 -80
  125. data/examples/15_memory_network_and_bus/os_editor.rb +0 -48
  126. data/examples/15_memory_network_and_bus/os_writer.rb +0 -49
  127. data/examples/15_memory_network_and_bus/output/combined_article.md +0 -11
  128. data/examples/15_memory_network_and_bus/output/final_article.md +0 -8
  129. data/examples/15_memory_network_and_bus/output/linux_draft.md +0 -7
  130. data/examples/15_memory_network_and_bus/output/mac_draft.md +0 -7
  131. data/examples/15_memory_network_and_bus/output/memory.json +0 -13
  132. data/examples/15_memory_network_and_bus/output/revision_1.md +0 -29
  133. data/examples/15_memory_network_and_bus/output/revision_2.md +0 -50
  134. data/examples/15_memory_network_and_bus/output/revision_3.md +0 -8
  135. data/examples/15_memory_network_and_bus/output/windows_draft.md +0 -7
  136. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +0 -13
  137. data/examples/15_memory_network_and_bus/prompts/os_chief.md +0 -13
  138. data/examples/15_memory_network_and_bus/prompts/os_editor.md +0 -13
  139. data/examples/16_writers_room/.envrc +0 -1
  140. data/examples/16_writers_room/display.rb +0 -158
  141. data/examples/16_writers_room/output/.gitignore +0 -4
  142. data/examples/16_writers_room/output/README.md +0 -69
  143. data/examples/16_writers_room/output/opus_001.md +0 -263
  144. data/examples/16_writers_room/output/opus_001_notes.log +0 -470
  145. data/examples/16_writers_room/output/opus_002.md +0 -245
  146. data/examples/16_writers_room/output/opus_002_notes.log +0 -546
  147. data/examples/16_writers_room/output/opus_002_screenplay.md +0 -7989
  148. data/examples/16_writers_room/output/opus_002_screenplay_notes.md +0 -993
  149. data/examples/16_writers_room/prompts/screenplay_writer.md +0 -66
  150. data/examples/16_writers_room/prompts/writer.md +0 -37
  151. data/examples/16_writers_room/room.rb +0 -186
  152. data/examples/16_writers_room/tools.rb +0 -173
  153. data/examples/16_writers_room/writer.rb +0 -121
  154. data/examples/16_writers_room/writers_room.rb +0 -256
  155. data/examples/17_skills.rb +0 -234
  156. data/examples/18_rails/.envrc +0 -3
  157. data/examples/18_rails/.gitignore +0 -5
  158. data/examples/18_rails/Gemfile +0 -11
  159. data/examples/18_rails/README.md +0 -67
  160. data/examples/18_rails/Rakefile +0 -4
  161. data/examples/18_rails/app/controllers/application_controller.rb +0 -4
  162. data/examples/18_rails/app/controllers/chat_controller.rb +0 -50
  163. data/examples/18_rails/app/jobs/application_job.rb +0 -4
  164. data/examples/18_rails/app/jobs/robot_run_job.rb +0 -25
  165. data/examples/18_rails/app/models/application_record.rb +0 -5
  166. data/examples/18_rails/app/models/robot_lab_result.rb +0 -36
  167. data/examples/18_rails/app/models/robot_lab_thread.rb +0 -23
  168. data/examples/18_rails/app/robots/chat_robot.rb +0 -25
  169. data/examples/18_rails/app/tools/time_tool.rb +0 -9
  170. data/examples/18_rails/app/views/chat/_user_message.html.erb +0 -1
  171. data/examples/18_rails/app/views/chat/index.html.erb +0 -67
  172. data/examples/18_rails/app/views/layouts/application.html.erb +0 -49
  173. data/examples/18_rails/bin/dev +0 -7
  174. data/examples/18_rails/bin/rails +0 -6
  175. data/examples/18_rails/bin/setup +0 -15
  176. data/examples/18_rails/config/application.rb +0 -33
  177. data/examples/18_rails/config/cable.yml +0 -2
  178. data/examples/18_rails/config/database.yml +0 -5
  179. data/examples/18_rails/config/environment.rb +0 -4
  180. data/examples/18_rails/config/initializers/robot_lab.rb +0 -11
  181. data/examples/18_rails/config/routes.rb +0 -6
  182. data/examples/18_rails/config.ru +0 -4
  183. data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +0 -32
  184. data/examples/19_token_tracking.rb +0 -138
  185. data/examples/20_circuit_breaker.rb +0 -147
  186. data/examples/21_learning_loop.rb +0 -181
  187. data/examples/22_context_compression.rb +0 -172
  188. data/examples/23_convergence.rb +0 -133
  189. data/examples/24_structured_delegation.rb +0 -151
  190. data/examples/25_history_search/conversation.jsonl +0 -30
  191. data/examples/25_history_search.rb +0 -133
  192. data/examples/26_document_store/api_versioning_adr.md +0 -52
  193. data/examples/26_document_store/incident_postmortem.md +0 -46
  194. data/examples/26_document_store/postgres_runbook.md +0 -49
  195. data/examples/26_document_store/redis_caching_guide.md +0 -48
  196. data/examples/26_document_store/sidekiq_guide.md +0 -51
  197. data/examples/26_document_store.rb +0 -140
  198. data/examples/27_incident_response/README.md +0 -65
  199. data/examples/27_incident_response/incident_response.rb +0 -261
  200. data/examples/28_mcp_discovery.rb +0 -113
  201. data/examples/29_ractor_tools.rb +0 -240
  202. data/examples/30_ractor_network.rb +0 -252
  203. data/examples/31_launch_assessment.rb +0 -246
  204. data/examples/32_newsletter_reader.rb +0 -193
  205. data/examples/33_stock_generator.rb +0 -80
  206. data/examples/33_stock_predictor.rb +0 -327
  207. data/examples/34_agentskills.rb +0 -76
  208. data/examples/35_hooks.rb +0 -266
  209. data/examples/README.md +0 -550
  210. data/examples/common.rb +0 -180
  211. data/examples/prompts/assistant.md +0 -5
  212. data/examples/prompts/audit_trail.md +0 -8
  213. data/examples/prompts/billing.md +0 -10
  214. data/examples/prompts/classifier.md +0 -7
  215. data/examples/prompts/comedian.md +0 -6
  216. data/examples/prompts/comedy_critic.md +0 -10
  217. data/examples/prompts/configurable.md +0 -9
  218. data/examples/prompts/dispatcher.md +0 -12
  219. data/examples/prompts/entity_extractor.md +0 -14
  220. data/examples/prompts/escalation.md +0 -42
  221. data/examples/prompts/frontmatter_mcp_test.md +0 -9
  222. data/examples/prompts/frontmatter_named_test.md +0 -5
  223. data/examples/prompts/frontmatter_tools_test.md +0 -6
  224. data/examples/prompts/general.md +0 -7
  225. data/examples/prompts/github_assistant.md +0 -14
  226. data/examples/prompts/helper.md +0 -4
  227. data/examples/prompts/incident_responder.md +0 -18
  228. data/examples/prompts/keyword_extractor.md +0 -11
  229. data/examples/prompts/llm_config_demo.md +0 -20
  230. data/examples/prompts/order_support.md +0 -35
  231. data/examples/prompts/os_advocate.md +0 -13
  232. data/examples/prompts/os_chief.md +0 -13
  233. data/examples/prompts/os_editor.md +0 -13
  234. data/examples/prompts/parameterized_main_test.md +0 -6
  235. data/examples/prompts/pii_redactor.md +0 -7
  236. data/examples/prompts/product_support.md +0 -37
  237. data/examples/prompts/runbook_protocol.md +0 -12
  238. data/examples/prompts/sentiment_analyzer.md +0 -12
  239. data/examples/prompts/skill_a_test.md +0 -4
  240. data/examples/prompts/skill_b_test.md +0 -4
  241. data/examples/prompts/skill_config_test.md +0 -5
  242. data/examples/prompts/skill_cycle_a_test.md +0 -6
  243. data/examples/prompts/skill_cycle_b_test.md +0 -6
  244. data/examples/prompts/skill_description_test.md +0 -4
  245. data/examples/prompts/skill_leaf_test.md +0 -4
  246. data/examples/prompts/skill_nested_test.md +0 -6
  247. data/examples/prompts/skill_refs_main_test.md +0 -6
  248. data/examples/prompts/skill_self_ref_test.md +0 -6
  249. data/examples/prompts/skill_with_mcp_test.md +0 -9
  250. data/examples/prompts/skill_with_params_test.md +0 -6
  251. data/examples/prompts/skill_with_robot_name_test.md +0 -5
  252. data/examples/prompts/skill_with_tools_test.md +0 -6
  253. data/examples/prompts/sre_compliance.md +0 -10
  254. data/examples/prompts/structured_output.md +0 -7
  255. data/examples/prompts/synthesizer.md +0 -17
  256. data/examples/prompts/technical.md +0 -10
  257. data/examples/prompts/template_with_skills_test.md +0 -6
  258. data/examples/prompts/triage.md +0 -22
  259. data/examples/ruboruby.md +0 -423
  260. data/examples/run_all.rb +0 -60
  261. data/examples/xyzzy.rb +0 -97
  262. data/lib/robot_lab/mcp/client.rb +0 -223
  263. data/lib/robot_lab/mcp/connection_poller.rb +0 -195
  264. data/lib/robot_lab/mcp/transports/base.rb +0 -64
  265. data/lib/robot_lab/mcp/transports/sse.rb +0 -120
  266. data/lib/robot_lab/mcp/transports/stdio.rb +0 -170
  267. data/lib/robot_lab/mcp/transports/streamable_http.rb +0 -141
  268. data/lib/robot_lab/mcp/transports/websocket.rb +0 -111
  269. data/logfile +0 -8
  270. data/mkdocs.yml +0 -209
  271. data/robot_concurrency.md +0 -38
  272. data/simple_acp_review.md +0 -298
  273. data/temp.md +0 -6
  274. 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