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
@@ -1,239 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Example 11: Network Visualization & Introspection
5
- #
6
- # Demonstrates network inspection and visualization without making any
7
- # LLM calls. Covers:
8
- # - to_mermaid() — Mermaid diagram export
9
- # - to_dot() — Graphviz DOT export
10
- # - execution_plan() — text execution order
11
- # - visualize() — ASCII pipeline visualization
12
- # - robot(name) / [name] — access individual robots
13
- # - available_robots() — list all robots
14
- # - add_robot() — dynamically add a robot
15
- # - to_h() — network introspection hash (via amazing_print)
16
- # - Task-specific config (context:, depends_on:)
17
- # - broadcast() and on_broadcast
18
- #
19
- # Usage:
20
- # bundle exec ruby examples/11_network_introspection.rb
21
-
22
- require_relative "common"
23
- require "amazing_print"
24
- require "tempfile"
25
-
26
- # On macOS + iTerm2, render DOT as a PNG and display inline via imgcat.
27
- # Returns true if the image was displayed, false otherwise.
28
- def render_dot_image(dot_source)
29
- return false unless RUBY_PLATFORM.include?("darwin")
30
-
31
- dot_cmd = `which dot 2>/dev/null`.chomp
32
- imgcat = File.expand_path("~/.iterm2/imgcat")
33
- imgcat = `which imgcat 2>/dev/null`.chomp unless File.executable?(imgcat)
34
-
35
- return false unless File.executable?(dot_cmd) && File.executable?(imgcat)
36
- return false unless ENV["TERM_PROGRAM"] == "iTerm.app"
37
-
38
- Tempfile.create(["pipeline", ".png"]) do |png|
39
- IO.popen([dot_cmd, "-Tpng", "-o", png.path], "w") { |io| io.write(dot_source) }
40
-
41
- if $?.success? && File.size(png.path) > 0
42
- system(imgcat, png.path)
43
- true
44
- else
45
- false
46
- end
47
- end
48
- end
49
-
50
- banner "Network Visualization & Introspection"
51
-
52
- # Shared RunConfig for all robots in this network
53
- shared_config = RobotLab::RunConfig.new(model: LLM[:default].model, temperature: 0.5)
54
-
55
- # Per-task RunConfig override for the writer (higher creativity)
56
- creative_config = RobotLab::RunConfig.new(temperature: 0.9)
57
-
58
- # Build robots (no LLM calls, just instances)
59
- classifier = RobotLab.build(**llm_opts, name: "classifier", system_prompt: "Classify input")
60
- analyst = RobotLab.build(**llm_opts, name: "analyst", system_prompt: "Analyze data")
61
- writer = RobotLab.build(**llm_opts, name: "writer", system_prompt: "Write summary")
62
-
63
- # Build network with RunConfig, dependencies, and per-task config
64
- network = RobotLab.create_network(name: "demo_pipeline", config: shared_config) do
65
- task :classify, classifier, depends_on: :none
66
- task :analyze, analyst, context: { depth: "deep" }, depends_on: [:classify]
67
- task :write, writer, config: creative_config, depends_on: [:analyze]
68
- end
69
-
70
- # =============================================================================
71
- # Section 1: Visualization outputs
72
- # =============================================================================
73
-
74
- section "Section 1: Visualization"
75
-
76
- mermaid = network.to_mermaid
77
- if mermaid
78
- puts "Mermaid diagram:"
79
- puts mermaid
80
- puts
81
- end
82
-
83
- dot = network.to_dot
84
- if dot
85
- puts "Graphviz DOT:"
86
- puts dot
87
- puts
88
-
89
- if render_dot_image(dot)
90
- puts "(Rendered pipeline graph above via Graphviz + imgcat)"
91
- puts
92
- end
93
- end
94
-
95
- plan = network.execution_plan
96
- if plan
97
- puts "Execution plan:"
98
- puts plan
99
- puts
100
- end
101
-
102
- ascii = network.visualize
103
- if ascii
104
- puts "ASCII visualization:"
105
- puts ascii
106
- puts
107
- end
108
-
109
- # If none of the visualization methods returned output, note it
110
- unless mermaid || dot || plan || ascii
111
- puts "(Visualization methods returned nil — depends on simple_flow version)"
112
- puts
113
- end
114
-
115
- # =============================================================================
116
- # Section 2: Robot access
117
- # =============================================================================
118
-
119
- section "Section 2: Robot Access"
120
-
121
- # Access by task name with robot() method
122
- # Note: robots are keyed by task name (the first arg to task()), not robot.name
123
- puts "network.robot('classify').name = #{network.robot('classify').name.inspect}"
124
-
125
- # Access with [] shorthand
126
- puts "network['analyze'].name = #{network['analyze'].name.inspect}"
127
-
128
- # Also works with symbols
129
- puts "network[:write].name = #{network[:write].name.inspect}"
130
- puts
131
-
132
- # List all robots
133
- puts "available_robots:"
134
- ap network.available_robots.map(&:name)
135
- puts
136
-
137
- # =============================================================================
138
- # Section 3: Dynamic robot addition
139
- # =============================================================================
140
-
141
- section "Section 3: Dynamic Robot Addition"
142
-
143
- reviewer = RobotLab.build(**llm_opts, name: "reviewer", system_prompt: "Review output")
144
- network.add_robot(reviewer)
145
-
146
- puts "After add_robot(reviewer):"
147
- ap network.available_robots.map(&:name)
148
- puts
149
-
150
- # Attempting to add a duplicate raises an error
151
- begin
152
- network.add_robot(reviewer)
153
- rescue ArgumentError => e
154
- puts "Duplicate add_robot raises: #{e.message}"
155
- end
156
- puts
157
-
158
- # =============================================================================
159
- # Section 4: Network introspection
160
- # =============================================================================
161
-
162
- section "Section 4: Network Introspection"
163
-
164
- puts "network.to_h:"
165
- ap network.to_h
166
- puts
167
-
168
- # Individual robot introspection
169
- puts "network['classify'].to_h:"
170
- ap network["classify"].to_h
171
- puts
172
-
173
- puts "network['analyze'].to_h:"
174
- ap network["analyze"].to_h
175
- puts
176
-
177
- # =============================================================================
178
- # Section 5: RunConfig Introspection
179
- # =============================================================================
180
-
181
- section "Section 5: RunConfig Introspection"
182
-
183
- puts "Network RunConfig (shared defaults):"
184
- ap network.config.to_h
185
- puts
186
-
187
- puts "Merged effective config for :write task (network + task override):"
188
- effective = shared_config.merge(creative_config)
189
- ap effective.to_h
190
- puts " model inherited from network, temperature overridden by task"
191
- puts
192
-
193
- # =============================================================================
194
- # Section 6: Broadcast
195
- # =============================================================================
196
-
197
- section "Section 6: Broadcast"
198
-
199
- broadcast_messages = []
200
-
201
- network.on_broadcast do |msg|
202
- broadcast_messages << msg
203
- end
204
-
205
- puts "Registered broadcast handler"
206
-
207
- network.broadcast(event: :demo, message: "Hello from network!")
208
-
209
- # Give async handler time to fire
210
- sleep 0.1
211
-
212
- if broadcast_messages.any?
213
- puts "Broadcast received:"
214
- ap broadcast_messages.first
215
- else
216
- puts "(No broadcast received — handler may be async)"
217
- end
218
- puts
219
-
220
- # =============================================================================
221
- # Section 7: Shared memory access
222
- # =============================================================================
223
-
224
- section "Section 7: Shared Network Memory"
225
-
226
- puts "network.memory is a #{network.memory.class}"
227
- puts "network.memory.network_name = #{network.memory.network_name.inspect}"
228
-
229
- # Robots in a network share this memory during run()
230
- network.memory.set(:demo_key, "shared value")
231
- puts "network.memory.get(:demo_key) = #{network.memory.get(:demo_key).inspect}"
232
- puts
233
-
234
- puts "network.memory.to_h:"
235
- ap network.memory.to_h
236
- puts
237
-
238
- hr
239
- puts "All sections completed without any LLM calls."
@@ -1,67 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Example 12: Message Bus — Converging on a Funny Robot Joke
5
- #
6
- # Alice tasks Bob to tell a robot joke. Alice uses her LLM to
7
- # evaluate each joke. If she's not impressed she asks Bob to try
8
- # again. The loop continues until Alice approves or MAX_ATTEMPTS.
9
- #
10
- # Usage:
11
- # bundle exec ruby examples/12_message_bus.rb
12
-
13
- require_relative "common"
14
-
15
- MAX_ATTEMPTS = 5
16
-
17
- class Comedian < RobotLab::Robot
18
- TEMP_START = 0.2
19
- TEMP_STEP = 0.2
20
-
21
- def initialize(bus:)
22
- super(name: "bob", **llm_opts, template: :comedian, bus: bus, temperature: TEMP_START)
23
- @attempts = 0
24
- on_message do |message|
25
- @attempts += 1
26
- temp = [TEMP_START + TEMP_STEP * (@attempts - 1), 1.0].min
27
- with_temperature(temp)
28
- joke = run(message.content.to_s).reply.strip
29
- puts " Bob [##{@attempts}, t=#{"%.1f" % temp}]: #{joke}"
30
- send_reply(to: message.from.to_sym, content: joke, in_reply_to: message.key)
31
- end
32
- end
33
-
34
- attr_reader :attempts
35
- end
36
-
37
- class ComedyCritic < RobotLab::Robot
38
- def initialize(bus:)
39
- super(name: "alice", **llm_opts, template: :comedy_critic, bus: bus)
40
- @accepted = false
41
- @rounds = 0
42
- on_message do |message|
43
- @rounds += 1
44
- verdict = run("Evaluate this joke:\n\n#{message.content}").reply.strip
45
- puts " Alice: #{verdict}"
46
- puts
47
- @accepted = verdict.start_with?("FUNNY")
48
- send_message(to: :bob, content: "Not funny enough. Try again.") unless @accepted || @rounds >= MAX_ATTEMPTS
49
- end
50
- end
51
-
52
- attr_reader :accepted
53
- end
54
-
55
- bus = TypedBus::MessageBus.new
56
- bob = Comedian.new(bus: bus)
57
- alice = ComedyCritic.new(bus: bus)
58
-
59
- banner "Tell Me a Funny Robot Joke"
60
-
61
- puts "Alice: Tell me a funny robot joke."
62
- puts
63
- alice.send_message(to: :bob, content: "Tell me a funny robot joke.")
64
-
65
- hr
66
- puts "Attempts: #{bob.attempts} / #{MAX_ATTEMPTS}"
67
- puts "Accepted: #{alice.accepted}"
data/examples/13_spawn.rb DELETED
@@ -1,85 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Example 13: Spawning Robots — Dynamic Specialist Creation
5
- #
6
- # A dispatcher robot receives a question, decides what kind of
7
- # specialist is needed, spawns one on the fly, and hands off
8
- # the work. The spawned robot replies via the shared message bus.
9
- #
10
- # This demonstrates Werner's "objects that create new objects to
11
- # deal with problems nobody anticipated."
12
- #
13
- # Usage:
14
- # bundle exec ruby examples/13_spawn.rb
15
-
16
- require_relative "common"
17
-
18
- QUESTIONS = [
19
- "Why did the Roman Empire fall?",
20
- "Write a haiku about recursion.",
21
- "What is the square root of 144?",
22
- ].freeze
23
-
24
- class Dispatcher < RobotLab::Robot
25
- attr_reader :spawned
26
-
27
- def initialize(bus: nil)
28
- super(name: "dispatcher", **llm_opts, template: :dispatcher, bus: bus)
29
- @spawned = {}
30
- @pending = {}
31
-
32
- on_message do |message|
33
- puts " Dispatcher <- :#{message.from} replied"
34
- puts " | #{message.content.to_s.lines.first&.strip}"
35
- @pending.delete(message.from)
36
- end
37
- end
38
-
39
- def dispatch(question)
40
- # Ask the LLM which specialist to spawn
41
- plan = run(question).reply.strip
42
- role, instruction = plan.split("\n", 2)
43
- role = role.strip.downcase.gsub(/\s+/, "_")
44
- instruction = instruction&.strip || "You are a helpful #{role}."
45
-
46
- puts " Dispatcher -> spawn :#{role}"
47
- puts " | #{instruction}"
48
-
49
- # Spawn the specialist (reuse if already spawned)
50
- specialist = @spawned[role] ||= spawn(
51
- name: role,
52
- **llm_opts,
53
- system_prompt: instruction
54
- )
55
-
56
- # Record what we're waiting for
57
- @pending[role] = question
58
-
59
- # Ask the specialist to work on the question
60
- specialist.send_message(to: :dispatcher, content:
61
- specialist.run(question).reply.strip
62
- )
63
- end
64
-
65
- def done?
66
- @pending.empty?
67
- end
68
- end
69
-
70
- # ── main ──────────────────────────────────────────────────────
71
-
72
- dispatcher = Dispatcher.new
73
-
74
- banner "Spawning Specialist Robots"
75
-
76
- QUESTIONS.each_with_index do |question, i|
77
- puts
78
- puts "Question #{i + 1}: #{question}"
79
- dispatcher.dispatch(question)
80
- end
81
-
82
- puts
83
- hr
84
- puts "Specialists spawned: #{dispatcher.spawned.keys.join(', ')}"
85
- puts "Total robots on bus: #{dispatcher.spawned.size + 1}"
@@ -1 +0,0 @@
1
- export ROBOT_LAB_TEMPLATE_PATH="${PWD}/prompts"
@@ -1 +0,0 @@
1
- /output/
@@ -1 +0,0 @@
1
- execute the open_mic.rb file
@@ -1,149 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # ── Comic Tools ─────────────────────────────────────────────
4
- #
5
- # Each tool accesses the owning robot via the `robot` accessor
6
- # inherited from RobotLab::Tool.
7
-
8
- class ReinventStyle < RobotLab::Tool
9
- description "Completely rewrite your comedy persona and approach. " \
10
- "Use when your current style is clearly not working. " \
11
- "Be bold — try something totally different. " \
12
- "The new style takes effect on your next bit."
13
-
14
- param :new_persona, type: "string",
15
- desc: "Your new comedy persona, style, and approach. " \
16
- "Be specific: what kind of humor, what voice, what attitude."
17
-
18
- def execute(new_persona:)
19
- robot.pending_reinvention = new_persona
20
- robot.style_changes += 1
21
- robot.display&.comic_tool("[reinvent_style] -> #{new_persona[0..70]}...")
22
- "Style reinvention accepted: #{new_persona}. " \
23
- "Commit to this new approach starting now."
24
- end
25
- end
26
-
27
- class AdjustEnergy < RobotLab::Tool
28
- description "Adjust your creative energy level. " \
29
- "Higher (0.8-1.0) = wilder, riskier, more unpredictable. " \
30
- "Lower (0.2-0.4) = tighter, more controlled, precise."
31
-
32
- param :level, type: "number",
33
- desc: "Energy level from 0.1 (very controlled) to 1.0 (unhinged)"
34
- param :reason, type: "string",
35
- desc: "Why you're adjusting", required: false
36
-
37
- def execute(level:, reason: "tactical adjustment")
38
- clamped = [[level.to_f, 0.1].max, 1.0].min
39
- robot.with_temperature(clamped)
40
- robot.display&.comic_tool("[adjust_energy] -> %.1f (%s)" % [clamped, reason])
41
- "Energy adjusted to #{clamped}. Reason: #{reason}"
42
- end
43
- end
44
-
45
- class GetCoaching < RobotLab::Tool
46
- description "Summon a comedy coach backstage for quick advice. " \
47
- "Use when you're struggling with the crowd and need " \
48
- "an outside perspective on what to try next."
49
-
50
- param :situation, type: "string",
51
- desc: "Describe what's happening and what you need help with"
52
-
53
- def execute(situation:)
54
- @coaches ||= {}
55
- coach = @coaches["comedy_coach"] ||= begin
56
- robot.coaches_spawned += 1
57
- robot.spawn(
58
- name: "comedy_coach",
59
- **llm_opts,
60
- system_prompt:
61
- "You are a veteran comedy coach backstage at a live show. " \
62
- "A comedian is struggling and needs quick, actionable advice. " \
63
- "Be direct and specific. One paragraph max. Tell them " \
64
- "exactly what to do differently in their next bit."
65
- )
66
- end
67
- robot.display&.working("Comic", "backstage with the coach")
68
- advice = coach.run(situation).reply.strip
69
- robot.display&.comic_tool("[get_coaching] -> #{advice[0..70]}...")
70
- advice
71
- rescue => e
72
- robot.display&.comic_tool("[get_coaching] ERROR: #{e.message}")
73
- "Coach unavailable right now. Trust your instincts."
74
- end
75
- end
76
-
77
-
78
- # ── The Comic ────────────────────────────────────────────────
79
- #
80
- # Has three tools, all with side effects on the robot itself:
81
- #
82
- # reinvent_style — queues a system prompt rewrite (applied next round)
83
- # adjust_energy — changes the comic's temperature immediately
84
- # get_coaching — spawns a comedy coach on the shared bus
85
- #
86
- # The LLM decides when to call them. The developer provides the
87
- # mechanism; the robot provides the judgment.
88
- #
89
- # Listens on personal :comic channel for heckler feedback.
90
- # Publishes performances to the shared :room channel.
91
- #
92
- class Comic < RobotLab::Robot
93
- attr_accessor :round, :style_changes, :coaches_spawned,
94
- :pending_reinvention, :display
95
-
96
- def initialize(bus:, display:)
97
- @round = 0
98
- @style_changes = 0
99
- @coaches_spawned = 0
100
- @pending_reinvention = nil
101
- @display = display
102
-
103
- super(
104
- name: "comic",
105
- **llm_opts,
106
- template: :open_mic_comic,
107
- bus: bus,
108
- local_tools: [
109
- ReinventStyle.new(robot: self),
110
- AdjustEnergy.new(robot: self),
111
- GetCoaching.new(robot: self)
112
- ]
113
- )
114
-
115
- # Listen on personal channel for heckler feedback
116
- on_message do |message|
117
- next unless message.from == "heckler"
118
-
119
- @round += 1
120
-
121
- # Build the prompt, injecting any queued style reinvention.
122
- # This embeds self-modification in the user prompt rather than
123
- # rewriting system messages, avoiding chat message ordering issues.
124
- prompt = "Round #{@round}."
125
-
126
- if @pending_reinvention
127
- prompt += "\n\nSTYLE REINVENTION: You are now #{@pending_reinvention}. " \
128
- "Commit fully to this new style. Abandon your previous approach."
129
- @pending_reinvention = nil
130
- end
131
-
132
- prompt += "\n\nThe heckler just shouted: \"#{message.content}\"\n\n" \
133
- "Process their feedback. If your material isn't landing, " \
134
- "use your tools to adapt — reinvent your style, adjust " \
135
- "your energy, or get coaching. Then deliver your next bit."
136
-
137
- # tools: :inherit — without it run() defaults to :none and the comic's
138
- # three self-modification tools are never offered to the model, which
139
- # kills the whole point of the demo.
140
- result = run(prompt, tools: :inherit)
141
- bit = result.reply.strip
142
-
143
- @display.comic("Comic [Round #{@round}]", bit)
144
-
145
- # Publish to the room — heckler and scout both pick it up
146
- send_message(to: :room, content: "ROUND #{@round}: #{bit}")
147
- end
148
- end
149
- end