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
data/examples/35_hooks.rb DELETED
@@ -1,266 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Example 35: Hooks Architecture with robot_lab-xyzzy
5
- #
6
- # Demonstrates the hook handler class pattern. Hooks are named classes that
7
- # inherit from RobotLab::Hook and implement class methods for each lifecycle
8
- # phase they care about. All hooks are Ractor-safe by default.
9
- #
10
- # Usage:
11
- # bundle exec ruby examples/35_hooks.rb
12
-
13
- require_relative "common"
14
-
15
- # RobotLab::Hook has no logger accessor — handlers own their own output.
16
- # xyzzy.rb freezes its log destination into a constant at load time, so the
17
- # redirect has to happen before the require.
18
- XYZZY_LOG = File.join(__dir__, "xyzzy_hooks.log")
19
- ENV["XYZZY_LOG_PATH"] = XYZZY_LOG
20
-
21
- require_relative "xyzzy"
22
-
23
- # ---------------------------------------------------------------------------
24
- # Handler classes used in this demo
25
- # ---------------------------------------------------------------------------
26
-
27
- class RuntimeDemoHook < RobotLab::Hook
28
- self.namespace = :runtime_demo
29
-
30
- def self.after_run(ctx)
31
- ctx.local.reply_seen = ctx.response.reply
32
- end
33
- end
34
-
35
- class LocalDemoHook < RobotLab::Hook
36
- self.namespace = :local_demo
37
-
38
- def self.before_run(ctx)
39
- ctx.local.note = "local robot hook state"
40
- ctx.local.demo = "robot_run"
41
- end
42
- end
43
-
44
- class PerfHook < RobotLab::Hook
45
- self.namespace = :perf
46
- @timings = []
47
- @mutex = Mutex.new
48
-
49
- class << self
50
- def reset!
51
- @mutex.synchronize { @timings = [] }
52
- end
53
-
54
- def timings
55
- @mutex.synchronize { @timings.dup }
56
- end
57
-
58
- def around_run(ctx, &block)
59
- t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
60
- result = block.call
61
- elapsed = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0) * 1_000).round(3)
62
- @mutex.synchronize { @timings << elapsed }
63
- puts " [perf] #{ctx.request.inspect} — #{elapsed}ms"
64
- result
65
- end
66
- end
67
- end
68
-
69
- class GenerationTracerHook < RobotLab::Hook
70
- self.namespace = :tracer
71
-
72
- def self.before_llm_generation(ctx)
73
- puts " [tracer] llm_generation start request=#{ctx.request.inspect}"
74
- end
75
-
76
- def self.after_llm_generation(ctx)
77
- msg = ctx.generation_response
78
- puts " [tracer] llm_generation done model=#{msg&.model_id} tokens=#{msg&.output_tokens}"
79
- end
80
- end
81
-
82
- class GenCacheHook < RobotLab::Hook
83
- self.namespace = :gen_cache
84
- @cache = {}
85
- @mutex = Mutex.new
86
- @call_count = 0
87
-
88
- class << self
89
- def reset!
90
- @mutex.synchronize do
91
- @cache = {}
92
- @call_count = 0
93
- end
94
- end
95
-
96
- def call_count
97
- @mutex.synchronize { @call_count }
98
- end
99
-
100
- def around_llm_generation(ctx, &block)
101
- cached = @mutex.synchronize { @cache[ctx.request] }
102
- if cached
103
- puts " [gen_cache] HIT — skipping LLM"
104
- cached
105
- else
106
- puts " [gen_cache] MISS — calling LLM"
107
- @mutex.synchronize { @call_count += 1 }
108
- block.call.tap { |r| @mutex.synchronize { @cache[ctx.request] = r } }
109
- end
110
- end
111
- end
112
- end
113
-
114
- class NetworkDemoHook < RobotLab::Hook
115
- self.namespace = :network_demo
116
-
117
- def self.before_task(ctx)
118
- ctx.local.task_note = "network-scoped task hook"
119
- end
120
- end
121
-
122
- # ---------------------------------------------------------------------------
123
-
124
- HookDemoResponse = Data.define(:content, :tool_calls, :stop_reason) do
125
- def initialize(content:, tool_calls: nil, stop_reason: "end_turn")
126
- super
127
- end
128
- end
129
-
130
- class HookDemoTool < RobotLab::Tool
131
- description "Returns a deterministic hook demo value"
132
- param :label, type: "string", desc: "The label to echo"
133
-
134
- def execute(label:)
135
- { label: label, status: "handled by HookDemoTool" }
136
- end
137
- end
138
-
139
- class HookDemo
140
- def run
141
- banner "Hooks Architecture with robot_lab-xyzzy"
142
-
143
- explain_setup
144
- run_robot
145
- run_llm_loop
146
- run_tool
147
- run_network
148
- run_error
149
-
150
- hr
151
- puts "Hook demo complete."
152
- end
153
-
154
- private
155
-
156
- def explain_setup
157
- section "Extension Registration"
158
- puts <<~TEXT
159
- xyzzy.rb is a single-file hook extension loaded from examples/.
160
-
161
- It is a RobotLab::Hook subclass registered under namespace :#{RobotLab::Xyzzy.namespace}.
162
- stdout : [xyzzy] timestamp hook_name (one line per call)
163
- logfile : full context snapshot → #{XYZZY_LOG}
164
- TEXT
165
- end
166
-
167
- def run_robot
168
- section "Robot Run Hooks"
169
- robot = hooked_robot("hook_demo_robot")
170
- robot.on(LocalDemoHook)
171
-
172
- result = robot.run("show the hook pipeline", hooks: RuntimeDemoHook)
173
-
174
- puts "Robot reply: #{result.reply}"
175
- end
176
-
177
- def run_llm_loop
178
- section "LLM Request/Response Loop"
179
- puts <<~TEXT
180
- Demonstrates before/around/after_llm_generation hooks.
181
- GenCacheHook implements around_llm_generation: on a cache hit the block
182
- (the real LLM call) is never invoked. PerfHook times each robot.run call.
183
- TEXT
184
-
185
- GenCacheHook.reset!
186
- PerfHook.reset!
187
-
188
- puts " provider=#{LLM[:default].provider} model=#{LLM[:default].model}\n\n"
189
-
190
- # with_model alone would leave the provider unset, and an Ollama model is
191
- # not in RubyLLM's registry — pass provider and model together.
192
- robot = RobotLab.build(
193
- name: "loop_demo_robot",
194
- system_prompt: "You are a helpful assistant. Answer every question in one concise sentence.",
195
- **llm_opts
196
- )
197
-
198
- robot.on(PerfHook)
199
- robot.on(GenerationTracerHook)
200
- robot.on(GenCacheHook)
201
-
202
- queries = [
203
- "what is the boiling point of water?",
204
- "what is 6 * 7?",
205
- "what is the boiling point of water?",
206
- "what is 6 * 7?"
207
- ]
208
-
209
- queries.each_with_index do |query, i|
210
- puts "\n--- query #{i + 1}: #{query.inspect} ---"
211
- result = robot.run(query)
212
- puts " reply: #{result.reply}"
213
- end
214
-
215
- timings = PerfHook.timings
216
- cache_hits = queries.size - GenCacheHook.call_count
217
- avg_ms = (timings.sum / timings.size).round(3)
218
- puts "\n--- summary ---"
219
- puts " robot.run calls : #{queries.size}"
220
- puts " LLM calls made : #{GenCacheHook.call_count} (#{cache_hits} served from gen_cache)"
221
- puts " avg run time : #{avg_ms}ms"
222
- end
223
-
224
- def run_tool
225
- section "Tool Call Hooks"
226
- tool = HookDemoTool.new
227
- result = tool.call({ "label" => "tool hook payload" })
228
- puts "Tool result: #{result.inspect}"
229
- end
230
-
231
- def run_network
232
- section "Network and Task Hooks"
233
- network = RobotLab::Network.new(name: "hook_demo_network")
234
- network.on(NetworkDemoHook)
235
- network.task(:summarize, hooked_robot("network_worker"), depends_on: :none)
236
-
237
- result = network.run(message: "summarize hook activity")
238
- reply = result.respond_to?(:reply) ? result.reply : result&.value&.reply
239
- puts "Network result: #{reply}"
240
- end
241
-
242
- def run_error
243
- section "Error Hook"
244
- failing_robot = RobotLab.build(name: "failing_hook_robot", system_prompt: "raise", **llm_opts)
245
- # Robot#chat is a public reader — no need to reach for @chat.
246
- failing_robot.chat.define_singleton_method(:ask) do |_message = nil, **_kwargs|
247
- raise "planned hook demo failure"
248
- end
249
-
250
- failing_robot.run("trigger the on_error hook")
251
- rescue RuntimeError => e
252
- puts "Caught expected error: #{e.message}"
253
- end
254
-
255
- # Stubs the chat so the hook pipeline is exercised deterministically with no
256
- # LLM traffic. Robot#chat is public, so this needs no ivar surgery.
257
- def hooked_robot(name)
258
- RobotLab.build(name: name, system_prompt: "deterministic hook demo", **llm_opts).tap do |robot|
259
- robot.chat.define_singleton_method(:ask) do |message = nil, **_kwargs, &_block|
260
- HookDemoResponse.new(content: "deterministic response to #{message.inspect}")
261
- end
262
- end
263
- end
264
- end
265
-
266
- HookDemo.new.run