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
@@ -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
- parameter :new_persona, type: "string",
15
- description: "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
- parameter :level, type: "number",
33
- description: "Energy level from 0.1 (very controlled) to 1.0 (unhinged)"
34
- parameter :reason, type: "string",
35
- description: "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
- parameter :situation, type: "string",
51
- description: "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