robot_lab 0.0.1 → 0.0.6

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 (187) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/deploy-github-pages.yml +9 -9
  3. data/.irbrc +6 -0
  4. data/CHANGELOG.md +140 -0
  5. data/README.md +263 -48
  6. data/Rakefile +71 -1
  7. data/docs/api/core/index.md +53 -46
  8. data/docs/api/core/memory.md +200 -154
  9. data/docs/api/core/network.md +13 -3
  10. data/docs/api/core/robot.md +490 -130
  11. data/docs/api/core/state.md +55 -73
  12. data/docs/api/core/tool.md +205 -209
  13. data/docs/api/index.md +7 -28
  14. data/docs/api/mcp/client.md +119 -48
  15. data/docs/api/mcp/index.md +75 -60
  16. data/docs/api/mcp/server.md +120 -136
  17. data/docs/api/mcp/transports.md +172 -184
  18. data/docs/api/messages/index.md +35 -20
  19. data/docs/api/messages/text-message.md +67 -21
  20. data/docs/api/messages/tool-call-message.md +80 -41
  21. data/docs/api/messages/tool-result-message.md +119 -50
  22. data/docs/api/messages/user-message.md +48 -24
  23. data/docs/api/streaming/context.md +157 -74
  24. data/docs/api/streaming/events.md +114 -166
  25. data/docs/api/streaming/index.md +74 -72
  26. data/docs/architecture/core-concepts.md +360 -116
  27. data/docs/architecture/index.md +97 -59
  28. data/docs/architecture/message-flow.md +138 -129
  29. data/docs/architecture/network-orchestration.md +197 -50
  30. data/docs/architecture/robot-execution.md +199 -146
  31. data/docs/architecture/state-management.md +255 -187
  32. data/docs/concepts.md +311 -49
  33. data/docs/examples/basic-chat.md +89 -77
  34. data/docs/examples/index.md +222 -47
  35. data/docs/examples/mcp-server.md +207 -203
  36. data/docs/examples/multi-robot-network.md +129 -35
  37. data/docs/examples/rails-application.md +159 -160
  38. data/docs/examples/tool-usage.md +295 -204
  39. data/docs/getting-started/configuration.md +347 -154
  40. data/docs/getting-started/index.md +1 -1
  41. data/docs/getting-started/installation.md +22 -13
  42. data/docs/getting-started/quick-start.md +166 -121
  43. data/docs/guides/building-robots.md +418 -212
  44. data/docs/guides/creating-networks.md +143 -24
  45. data/docs/guides/index.md +0 -5
  46. data/docs/guides/mcp-integration.md +152 -113
  47. data/docs/guides/memory.md +220 -164
  48. data/docs/guides/rails-integration.md +244 -162
  49. data/docs/guides/streaming.md +137 -187
  50. data/docs/guides/using-tools.md +259 -212
  51. data/docs/index.md +46 -41
  52. data/examples/01_simple_robot.rb +6 -9
  53. data/examples/02_tools.rb +6 -9
  54. data/examples/03_network.rb +19 -17
  55. data/examples/04_mcp.rb +5 -8
  56. data/examples/05_streaming.rb +5 -8
  57. data/examples/06_prompt_templates.rb +42 -37
  58. data/examples/07_network_memory.rb +13 -14
  59. data/examples/08_llm_config.rb +169 -0
  60. data/examples/09_chaining.rb +262 -0
  61. data/examples/10_memory.rb +331 -0
  62. data/examples/11_network_introspection.rb +253 -0
  63. data/examples/12_message_bus.rb +74 -0
  64. data/examples/13_spawn.rb +90 -0
  65. data/examples/14_rusty_circuit/comic.rb +143 -0
  66. data/examples/14_rusty_circuit/display.rb +203 -0
  67. data/examples/14_rusty_circuit/heckler.rb +63 -0
  68. data/examples/14_rusty_circuit/open_mic.rb +123 -0
  69. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +20 -0
  70. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +23 -0
  71. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +20 -0
  72. data/examples/14_rusty_circuit/scout.rb +156 -0
  73. data/examples/14_rusty_circuit/scout_notes.md +89 -0
  74. data/examples/14_rusty_circuit/show.log +234 -0
  75. data/examples/15_memory_network_and_bus/editor_in_chief.rb +24 -0
  76. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +206 -0
  77. data/examples/15_memory_network_and_bus/linux_writer.rb +80 -0
  78. data/examples/15_memory_network_and_bus/os_editor.rb +46 -0
  79. data/examples/15_memory_network_and_bus/os_writer.rb +46 -0
  80. data/examples/15_memory_network_and_bus/output/combined_article.md +13 -0
  81. data/examples/15_memory_network_and_bus/output/final_article.md +15 -0
  82. data/examples/15_memory_network_and_bus/output/linux_draft.md +5 -0
  83. data/examples/15_memory_network_and_bus/output/mac_draft.md +7 -0
  84. data/examples/15_memory_network_and_bus/output/memory.json +13 -0
  85. data/examples/15_memory_network_and_bus/output/revision_1.md +19 -0
  86. data/examples/15_memory_network_and_bus/output/revision_2.md +15 -0
  87. data/examples/15_memory_network_and_bus/output/windows_draft.md +7 -0
  88. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +13 -0
  89. data/examples/15_memory_network_and_bus/prompts/os_chief.md +13 -0
  90. data/examples/15_memory_network_and_bus/prompts/os_editor.md +13 -0
  91. data/examples/16_writers_room/display.rb +158 -0
  92. data/examples/16_writers_room/output/.gitignore +2 -0
  93. data/examples/16_writers_room/output/opus_001.md +263 -0
  94. data/examples/16_writers_room/output/opus_001_notes.log +470 -0
  95. data/examples/16_writers_room/prompts/writer.md +37 -0
  96. data/examples/16_writers_room/room.rb +150 -0
  97. data/examples/16_writers_room/tools.rb +162 -0
  98. data/examples/16_writers_room/writer.rb +121 -0
  99. data/examples/16_writers_room/writers_room.rb +162 -0
  100. data/examples/README.md +197 -0
  101. data/examples/prompts/{assistant/system.txt.erb → assistant.md} +3 -0
  102. data/examples/prompts/{billing/system.txt.erb → billing.md} +3 -0
  103. data/examples/prompts/{classifier/system.txt.erb → classifier.md} +3 -0
  104. data/examples/prompts/comedian.md +6 -0
  105. data/examples/prompts/comedy_critic.md +10 -0
  106. data/examples/prompts/configurable.md +9 -0
  107. data/examples/prompts/dispatcher.md +12 -0
  108. data/examples/prompts/{entity_extractor/system.txt.erb → entity_extractor.md} +3 -0
  109. data/examples/prompts/{escalation/system.txt.erb → escalation.md} +7 -0
  110. data/examples/prompts/frontmatter_mcp_test.md +9 -0
  111. data/examples/prompts/frontmatter_named_test.md +5 -0
  112. data/examples/prompts/frontmatter_tools_test.md +6 -0
  113. data/examples/prompts/{general/system.txt.erb → general.md} +3 -0
  114. data/examples/prompts/{github_assistant/system.txt.erb → github_assistant.md} +8 -0
  115. data/examples/prompts/{helper/system.txt.erb → helper.md} +3 -0
  116. data/examples/prompts/{keyword_extractor/system.txt.erb → keyword_extractor.md} +3 -0
  117. data/examples/prompts/llm_config_demo.md +20 -0
  118. data/examples/prompts/{order_support/system.txt.erb → order_support.md} +8 -0
  119. data/examples/prompts/os_advocate.md +13 -0
  120. data/examples/prompts/os_chief.md +13 -0
  121. data/examples/prompts/os_editor.md +13 -0
  122. data/examples/prompts/{product_support/system.txt.erb → product_support.md} +7 -0
  123. data/examples/prompts/{sentiment_analyzer/system.txt.erb → sentiment_analyzer.md} +3 -0
  124. data/examples/prompts/{synthesizer/system.txt.erb → synthesizer.md} +3 -0
  125. data/examples/prompts/{technical/system.txt.erb → technical.md} +3 -0
  126. data/examples/prompts/{triage/system.txt.erb → triage.md} +6 -0
  127. data/lib/generators/robot_lab/templates/initializer.rb.tt +0 -13
  128. data/lib/robot_lab/ask_user.rb +75 -0
  129. data/lib/robot_lab/config/defaults.yml +121 -0
  130. data/lib/robot_lab/config.rb +183 -0
  131. data/lib/robot_lab/error.rb +6 -0
  132. data/lib/robot_lab/mcp/client.rb +1 -1
  133. data/lib/robot_lab/memory.rb +10 -34
  134. data/lib/robot_lab/network.rb +13 -20
  135. data/lib/robot_lab/robot/bus_messaging.rb +239 -0
  136. data/lib/robot_lab/robot/mcp_management.rb +88 -0
  137. data/lib/robot_lab/robot/template_rendering.rb +130 -0
  138. data/lib/robot_lab/robot.rb +240 -330
  139. data/lib/robot_lab/robot_message.rb +44 -0
  140. data/lib/robot_lab/robot_result.rb +1 -0
  141. data/lib/robot_lab/run_config.rb +184 -0
  142. data/lib/robot_lab/state_proxy.rb +2 -12
  143. data/lib/robot_lab/streaming/context.rb +1 -1
  144. data/lib/robot_lab/task.rb +8 -1
  145. data/lib/robot_lab/tool.rb +108 -172
  146. data/lib/robot_lab/tool_config.rb +1 -1
  147. data/lib/robot_lab/tool_manifest.rb +2 -18
  148. data/lib/robot_lab/utils.rb +39 -0
  149. data/lib/robot_lab/version.rb +1 -1
  150. data/lib/robot_lab.rb +89 -57
  151. data/mkdocs.yml +0 -11
  152. metadata +121 -135
  153. data/docs/api/adapters/anthropic.md +0 -121
  154. data/docs/api/adapters/gemini.md +0 -133
  155. data/docs/api/adapters/index.md +0 -104
  156. data/docs/api/adapters/openai.md +0 -134
  157. data/docs/api/history/active-record-adapter.md +0 -195
  158. data/docs/api/history/config.md +0 -191
  159. data/docs/api/history/index.md +0 -132
  160. data/docs/api/history/thread-manager.md +0 -144
  161. data/docs/guides/history.md +0 -359
  162. data/examples/prompts/assistant/user.txt.erb +0 -1
  163. data/examples/prompts/billing/user.txt.erb +0 -1
  164. data/examples/prompts/classifier/user.txt.erb +0 -1
  165. data/examples/prompts/entity_extractor/user.txt.erb +0 -3
  166. data/examples/prompts/escalation/user.txt.erb +0 -34
  167. data/examples/prompts/general/user.txt.erb +0 -1
  168. data/examples/prompts/github_assistant/user.txt.erb +0 -1
  169. data/examples/prompts/helper/user.txt.erb +0 -1
  170. data/examples/prompts/keyword_extractor/user.txt.erb +0 -3
  171. data/examples/prompts/order_support/user.txt.erb +0 -22
  172. data/examples/prompts/product_support/user.txt.erb +0 -32
  173. data/examples/prompts/sentiment_analyzer/user.txt.erb +0 -3
  174. data/examples/prompts/synthesizer/user.txt.erb +0 -15
  175. data/examples/prompts/technical/user.txt.erb +0 -1
  176. data/examples/prompts/triage/user.txt.erb +0 -17
  177. data/lib/robot_lab/adapters/anthropic.rb +0 -163
  178. data/lib/robot_lab/adapters/base.rb +0 -85
  179. data/lib/robot_lab/adapters/gemini.rb +0 -193
  180. data/lib/robot_lab/adapters/openai.rb +0 -159
  181. data/lib/robot_lab/adapters/registry.rb +0 -81
  182. data/lib/robot_lab/configuration.rb +0 -143
  183. data/lib/robot_lab/errors.rb +0 -70
  184. data/lib/robot_lab/history/active_record_adapter.rb +0 -146
  185. data/lib/robot_lab/history/config.rb +0 -115
  186. data/lib/robot_lab/history/thread_manager.rb +0 -93
  187. data/lib/robot_lab/robotic_model.rb +0 -324
@@ -2,107 +2,115 @@
2
2
 
3
3
  Stream LLM responses in real-time for better user experience.
4
4
 
5
- ## Basic Streaming
5
+ ## Streaming via Callbacks
6
6
 
7
- Pass a callback to receive streaming events:
7
+ RobotLab robots support streaming through callback methods inherited from RubyLLM::Agent. Register callbacks before calling `run`:
8
8
 
9
9
  ```ruby
10
- robot.run(
11
- state: state,
12
- network: network,
13
- streaming: ->(event) {
14
- puts event.inspect
15
- }
10
+ robot = RobotLab.build(
11
+ name: "storyteller",
12
+ system_prompt: "You are a creative storyteller."
16
13
  )
14
+
15
+ # Register streaming callback
16
+ robot.on_new_message do |message|
17
+ print message.content if message.content
18
+ end
19
+
20
+ result = robot.run("Tell me a story about a brave robot")
17
21
  ```
18
22
 
19
- ## Event Types
23
+ ## Available Callbacks
20
24
 
21
- ### Text Deltas
25
+ ### on_new_message
22
26
 
23
- Receive text as it's generated:
27
+ Called when the assistant starts generating a new message, with streaming chunks:
24
28
 
25
29
  ```ruby
26
- streaming: ->(event) {
27
- if event[:event] == "delta"
28
- print event[:data][:content]
29
- end
30
- }
30
+ robot.on_new_message do |message|
31
+ print message.content if message.content
32
+ end
31
33
  ```
32
34
 
33
- ### Tool Calls
35
+ ### on_end_message
34
36
 
35
- Know when tools are being called:
37
+ Called when the assistant finishes a message:
36
38
 
37
39
  ```ruby
38
- streaming: ->(event) {
39
- case event[:event]
40
- when "tool_call.start"
41
- puts "\nCalling: #{event[:data][:name]}"
42
- when "tool_call.complete"
43
- puts "Done: #{event[:data][:result]}"
44
- end
45
- }
40
+ robot.on_end_message do |message|
41
+ puts "\n--- Response complete ---"
42
+ puts "Content length: #{message.content&.length}"
43
+ end
46
44
  ```
47
45
 
48
- ### Lifecycle Events
46
+ ### on_tool_call
49
47
 
50
- Track execution lifecycle:
48
+ Called when the LLM invokes a tool:
51
49
 
52
50
  ```ruby
53
- streaming: ->(event) {
54
- case event[:event]
55
- when "run.started"
56
- puts "Starting run #{event[:data][:run_id]}"
57
- when "run.completed"
58
- puts "Completed!"
59
- when "run.failed"
60
- puts "Failed: #{event[:data][:error]}"
61
- end
62
- }
51
+ robot.on_tool_call do |tool_call|
52
+ puts "Calling tool: #{tool_call.name}"
53
+ end
63
54
  ```
64
55
 
65
- ## Event Reference
56
+ ### on_tool_result
66
57
 
67
- | Event | Description | Data |
68
- |-------|-------------|------|
69
- | `run.started` | Network run began | `run_id`, `network` |
70
- | `run.completed` | Network run finished | `run_id`, `robot_count` |
71
- | `run.failed` | Error occurred | `run_id`, `error` |
72
- | `delta` | Text content chunk | `content` |
73
- | `tool_call.start` | Tool execution starting | `name`, `input` |
74
- | `tool_call.complete` | Tool execution done | `name`, `result` |
58
+ Called when a tool returns its result:
75
59
 
76
- ## Streaming Context
60
+ ```ruby
61
+ robot.on_tool_result do |tool_call, result|
62
+ puts "Tool #{tool_call.name} returned: #{result}"
63
+ end
64
+ ```
77
65
 
78
- For advanced control, use `Streaming::Context`:
66
+ ## Comprehensive Callback Setup
67
+
68
+ Register all callbacks for full visibility:
79
69
 
80
70
  ```ruby
81
- context = RobotLab::Streaming::Context.new(
82
- run_id: SecureRandom.uuid,
83
- message_id: SecureRandom.uuid,
84
- scope: "network",
85
- publish: ->(event) { broadcast_to_client(event) }
71
+ robot = RobotLab.build(
72
+ name: "assistant",
73
+ system_prompt: "You are helpful.",
74
+ local_tools: [WeatherTool]
86
75
  )
87
- ```
88
76
 
89
- ### Context Properties
77
+ robot.on_new_message do |message|
78
+ print message.content if message.content
79
+ end
90
80
 
91
- ```ruby
92
- context.run_id # Unique run identifier
93
- context.message_id # Unique message identifier
94
- context.scope # "network" or "robot"
81
+ robot.on_end_message do |_message|
82
+ puts "\n--- Done ---"
83
+ end
84
+
85
+ robot.on_tool_call do |tool_call|
86
+ puts "\n[Tool] Calling: #{tool_call.name}"
87
+ end
88
+
89
+ robot.on_tool_result do |tool_call, result|
90
+ puts "[Tool] #{tool_call.name} returned: #{result}"
91
+ end
92
+
93
+ result = robot.run("What's the weather in Tokyo?")
95
94
  ```
96
95
 
97
- ### Publishing Events
96
+ ## Streaming via Chat Block
97
+
98
+ For more control, pass a block directly to `chat.ask` (the underlying RubyLLM method):
98
99
 
99
100
  ```ruby
100
- context.publish_event(
101
- event: "custom.event",
102
- data: { key: "value" }
101
+ robot = RobotLab.build(
102
+ name: "chat_bot",
103
+ system_prompt: "You are a helpful assistant."
103
104
  )
105
+
106
+ # Use the underlying chat directly with a streaming block
107
+ robot.chat.ask("Tell me a story") do |chunk|
108
+ print chunk.content if chunk.content
109
+ end
104
110
  ```
105
111
 
112
+ Note: Using `chat.ask` directly bypasses Robot's memory resolution and tool hierarchy. Use callbacks with `robot.run` when you need those features.
113
+
106
114
  ## Web Integration
107
115
 
108
116
  ### Rails Action Cable
@@ -110,14 +118,20 @@ context.publish_event(
110
118
  ```ruby
111
119
  class ChatChannel < ApplicationCable::Channel
112
120
  def receive(data)
113
- state = RobotLab.create_state(message: data["message"])
114
-
115
- network.run(
116
- state: state,
117
- streaming: ->(event) {
118
- transmit(event)
119
- }
121
+ robot = RobotLab.build(
122
+ name: "chat_bot",
123
+ system_prompt: "You are a helpful chat assistant."
120
124
  )
125
+
126
+ robot.on_new_message do |message|
127
+ transmit({ event: "text.delta", content: message.content }) if message.content
128
+ end
129
+
130
+ robot.on_end_message do |_message|
131
+ transmit({ event: "run.completed" })
132
+ end
133
+
134
+ robot.run(data["message"])
121
135
  end
122
136
  end
123
137
  ```
@@ -131,14 +145,20 @@ class StreamController < ApplicationController
131
145
  def create
132
146
  response.headers["Content-Type"] = "text/event-stream"
133
147
 
134
- state = RobotLab.create_state(message: params[:message])
135
-
136
- network.run(
137
- state: state,
138
- streaming: ->(event) {
139
- response.stream.write("data: #{event.to_json}\n\n")
140
- }
148
+ robot = RobotLab.build(
149
+ name: "stream_bot",
150
+ system_prompt: "You are helpful."
141
151
  )
152
+
153
+ robot.on_new_message do |message|
154
+ response.stream.write("data: #{message.content}\n\n") if message.content
155
+ end
156
+
157
+ robot.on_end_message do |_message|
158
+ response.stream.write("data: [DONE]\n\n")
159
+ end
160
+
161
+ robot.run(params[:message])
142
162
  ensure
143
163
  response.stream.close
144
164
  end
@@ -150,62 +170,17 @@ end
150
170
  ```ruby
151
171
  # Using Faye WebSocket
152
172
  ws.on :message do |msg|
153
- state = RobotLab.create_state(message: msg.data)
154
-
155
- network.run(
156
- state: state,
157
- streaming: ->(event) {
158
- ws.send(event.to_json)
159
- }
160
- )
161
- end
162
- ```
163
-
164
- ## Event Filtering
165
-
166
- ### Check Event Type
167
-
168
- ```ruby
169
- streaming: ->(event) {
170
- return unless RobotLab::Streaming::Events.delta?(event)
171
- print event[:data][:content]
172
- }
173
- ```
174
-
175
- ### Available Predicates
176
-
177
- ```ruby
178
- Streaming::Events.lifecycle?(event) # run.started, run.completed, etc.
179
- Streaming::Events.delta?(event) # Text content
180
- Streaming::Events.valid?(event) # Has required fields
181
- ```
182
-
183
- ## Buffering
184
-
185
- Buffer content for batch processing:
186
-
187
- ```ruby
188
- buffer = []
189
-
190
- streaming: ->(event) {
191
- if event[:event] == "delta"
192
- buffer << event[:data][:content]
193
-
194
- # Flush every 10 chunks
195
- if buffer.size >= 10
196
- process_batch(buffer.join)
197
- buffer.clear
198
- end
173
+ robot.on_new_message do |message|
174
+ ws.send(message.content) if message.content
199
175
  end
200
- }
201
176
 
202
- # Don't forget final flush
203
- process_batch(buffer.join) if buffer.any?
177
+ robot.run(msg.data)
178
+ end
204
179
  ```
205
180
 
206
181
  ## Progress Tracking
207
182
 
208
- Track streaming progress:
183
+ Track streaming progress with callbacks:
209
184
 
210
185
  ```ruby
211
186
  class StreamProgress
@@ -214,94 +189,69 @@ class StreamProgress
214
189
  @tools = 0
215
190
  end
216
191
 
217
- def handle(event)
218
- case event[:event]
219
- when "delta"
220
- @chars += event[:data][:content].length
221
- puts "\rReceived #{@chars} characters..."
222
- when "tool_call.start"
192
+ attr_reader :chars, :tools
193
+
194
+ def attach(robot)
195
+ robot.on_new_message do |message|
196
+ @chars += message.content.length if message.content
197
+ print "\rReceived #{@chars} characters..."
198
+ end
199
+
200
+ robot.on_tool_call do |tool_call|
223
201
  @tools += 1
224
- puts "\nTool call ##{@tools}: #{event[:data][:name]}"
202
+ puts "\nTool call ##{@tools}: #{tool_call.name}"
225
203
  end
226
204
  end
227
205
  end
228
206
 
229
207
  progress = StreamProgress.new
230
- network.run(state: state, streaming: progress.method(:handle))
231
- ```
232
-
233
- ## Error Handling
234
-
235
- Handle streaming errors gracefully:
208
+ progress.attach(robot)
236
209
 
237
- ```ruby
238
- streaming: ->(event) {
239
- case event[:event]
240
- when "run.failed"
241
- log_error(event[:data][:error])
242
- notify_user("An error occurred")
243
- when "delta"
244
- begin
245
- broadcast(event)
246
- rescue BroadcastError => e
247
- # Client disconnected, but continue processing
248
- logger.warn "Broadcast failed: #{e.message}"
249
- end
250
- end
251
- }
210
+ result = robot.run("Process this complex request")
211
+ puts "\nTotal: #{progress.chars} chars, #{progress.tools} tool calls"
252
212
  ```
253
213
 
254
- ## Disabling Streaming
214
+ ## Without Streaming
255
215
 
256
- Disable streaming when not needed:
216
+ When streaming is not needed, simply call `run` without registering callbacks:
257
217
 
258
218
  ```ruby
259
- RobotLab.configure do |config|
260
- config.streaming_enabled = false
261
- end
262
-
263
- # Or per-run
264
- network.run(state: state, streaming: nil)
219
+ # No streaming - returns RobotResult directly
220
+ result = robot.run("Hello!")
221
+ puts result.last_text_content
265
222
  ```
266
223
 
267
224
  ## Best Practices
268
225
 
269
- ### 1. Handle All Event Types
226
+ ### 1. Register Callbacks Before Run
270
227
 
271
228
  ```ruby
272
- streaming: ->(event) {
273
- case event[:event]
274
- when "delta" then handle_delta(event)
275
- when "tool_call.start" then show_tool_indicator(event)
276
- when "tool_call.complete" then hide_tool_indicator(event)
277
- when "run.completed" then finalize_response
278
- when "run.failed" then show_error(event)
279
- end
280
- }
229
+ # Correct: register first, then run
230
+ robot.on_new_message { |msg| print msg.content if msg.content }
231
+ robot.run("Hello")
281
232
  ```
282
233
 
283
- ### 2. Provide User Feedback
234
+ ### 2. Handle Errors in Callbacks
284
235
 
285
236
  ```ruby
286
- streaming: ->(event) {
287
- case event[:event]
288
- when "run.started"
289
- show_typing_indicator
290
- when "delta"
291
- update_message(event[:data][:content])
292
- when "tool_call.start"
293
- show_status("Looking up information...")
294
- when "run.completed"
295
- hide_typing_indicator
237
+ robot.on_new_message do |message|
238
+ begin
239
+ broadcast(message.content) if message.content
240
+ rescue BroadcastError => e
241
+ # Client disconnected, but continue processing
242
+ logger.warn "Broadcast failed: #{e.message}"
296
243
  end
297
- }
244
+ end
298
245
  ```
299
246
 
300
247
  ### 3. Clean Up Resources
301
248
 
302
249
  ```ruby
303
250
  begin
304
- network.run(state: state, streaming: callback)
251
+ robot.on_new_message do |message|
252
+ stream_to_client(message.content) if message.content
253
+ end
254
+ robot.run("Hello")
305
255
  ensure
306
256
  close_stream_connection
307
257
  end