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