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
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