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
@@ -1,217 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "rainbow"
4
- require "unicode/display_width"
5
- require "io/console"
6
-
7
- # ── Display ─────────────────────────────────────────────────
8
- #
9
- # Terminal formatting for the Rusty Circuit demo.
10
- #
11
- # - Comic output: left-aligned, cyan, word-wrapped
12
- # - Heckler output: right-indented, yellow, word-wrapped
13
- # - Scout observations: written to a markdown file (silent on STDOUT)
14
- # - Tool annotations: dimmed, indented under the triggering speaker
15
- # - Final verdict: green on STDOUT and appended to scout file
16
- #
17
- class Display
18
- def initialize(scout_path:, log_path: nil)
19
- @term_width = (IO.console&.winsize&.last || 80)
20
- @comic_width = (@term_width * 0.56).to_i
21
- @heckler_width = (@term_width * 0.52).to_i
22
- @scout_file = File.open(scout_path, "w")
23
- @scout_file.puts "# Scout Notes — The Rusty Circuit\n\n"
24
- @log_file = log_path ? File.open(log_path, "w") : nil
25
- end
26
-
27
- # ── Comic (left, cyan) ──────────────────────────────────
28
-
29
- def comic(label, text)
30
- puts
31
- puts Rainbow(" #{label}:").cyan.bright
32
- wrap(text, @comic_width).each do |line|
33
- puts Rainbow(" #{line}").cyan
34
- end
35
- puts
36
-
37
- log("\n #{label}:")
38
- wrap(text, @comic_width).each { |line| log(" #{line}") }
39
- log("")
40
- end
41
-
42
- def comic_tool(text)
43
- puts Rainbow(" #{text}").darkgray
44
- log(" #{text}")
45
- end
46
-
47
- # ── Heckler (right, yellow) ─────────────────────────────
48
-
49
- def heckler(label, text)
50
- indent = [(@term_width - @heckler_width - 4), 4].max
51
- pad = " " * indent
52
-
53
- puts
54
- puts Rainbow("#{pad}#{label}:").yellow.bright
55
- wrap(text, @heckler_width).each do |line|
56
- puts Rainbow("#{pad} #{line}").yellow
57
- end
58
- puts
59
-
60
- log("\n #{label}:")
61
- wrap(text, @heckler_width).each { |line| log(" #{line}") }
62
- log("")
63
- end
64
-
65
- def heckler_note(text)
66
- indent = [(@term_width - @heckler_width - 4), 4].max
67
- pad = " " * indent
68
- puts Rainbow("#{pad} #{text}").yellow.faint
69
- log(" #{text}")
70
- end
71
-
72
- # ── Liveness ────────────────────────────────────────────
73
- #
74
- # The scout writes its notes to a file, so a scout turn produces NO
75
- # terminal output at all — and a scout turn is 1-4 sequential LLM calls
76
- # (observe, then any tool round-trips, then a spawned analyst's own run).
77
- # On a local model that is minutes of dead-silent terminal, which reads
78
- # as a hang. These lines prove the show is still moving.
79
-
80
- def working(who, what)
81
- puts Rainbow(" · #{who} #{what}…").darkgray
82
- $stdout.flush
83
- log(" · #{who} #{what}...")
84
- end
85
-
86
- # ── Scout (file only) ───────────────────────────────────
87
-
88
- def scout(round_num, notes)
89
- @scout_file.puts "## Round #{round_num}\n\n#{notes}\n\n"
90
- @scout_file.flush
91
- log(" Scout [Round #{round_num}]: #{notes}")
92
- end
93
-
94
- def scout_analyst(name, text)
95
- @scout_file.puts "### Analyst: #{name}\n\n#{text}\n\n"
96
- @scout_file.flush
97
- log(" [#{name}_analyst] #{text}")
98
- end
99
-
100
- def scout_criteria(text)
101
- @scout_file.puts "### Criteria Refinement\n\n#{text}\n\n"
102
- @scout_file.flush
103
- log(" [refine_criteria] -> #{text}")
104
- end
105
-
106
- # ── Verdict (STDOUT green + file) ───────────────────────
107
-
108
- def verdict(label, text)
109
- puts
110
- puts Rainbow(" #{label}:").green.bright
111
- wrap(text, @term_width - 8).each do |line|
112
- puts Rainbow(" #{line}").green
113
- end
114
- puts
115
-
116
- @scout_file.puts "---\n\n## Final Verdict\n\n#{text}\n"
117
- @scout_file.flush
118
-
119
- log("\n #{label}:")
120
- text.each_line { |line| log(" #{line.chomp}") }
121
- log("")
122
- end
123
-
124
- # ── Chrome ──────────────────────────────────────────────
125
-
126
- def banner(text)
127
- puts
128
- text.each_line { |line| puts Rainbow(line.chomp).bright }
129
- puts
130
-
131
- log("")
132
- text.each_line { |line| log(line.chomp) }
133
- log("")
134
- end
135
-
136
- def separator
137
- puts Rainbow(" #{"─" * (@term_width - 4)}").darkgray
138
- puts
139
- log(" #{"─" * 56}")
140
- log("")
141
- end
142
-
143
- def stats(text)
144
- puts
145
- text.each_line { |line| puts Rainbow(line.chomp).bright }
146
-
147
- log("")
148
- text.each_line { |line| log(line.chomp) }
149
- end
150
-
151
- def close
152
- @scout_file.close unless @scout_file.closed?
153
- @log_file&.close
154
- end
155
-
156
- private
157
-
158
- def log(line)
159
- return unless @log_file
160
-
161
- @log_file.puts line
162
- @log_file.flush
163
- end
164
-
165
- # Word-wrap text to fit within max_width display columns.
166
- # Uses Unicode::DisplayWidth for correct CJK / emoji handling.
167
- # Force-breaks any single word longer than max_width.
168
- def wrap(text, max_width)
169
- lines = []
170
-
171
- text.each_line do |paragraph|
172
- paragraph = paragraph.strip
173
- next(lines << "") if paragraph.empty?
174
-
175
- words = paragraph.split(/\s+/)
176
- current = +""
177
-
178
- words.each do |word|
179
- word_w = Unicode::DisplayWidth.of(word)
180
-
181
- # Force-break words wider than max_width
182
- if word_w > max_width
183
- unless current.empty?
184
- lines << current
185
- current = +""
186
- end
187
- chars = word.chars
188
- buf = +""
189
- chars.each do |ch|
190
- if Unicode::DisplayWidth.of(buf + ch) > max_width
191
- lines << buf
192
- buf = +ch
193
- else
194
- buf << ch
195
- end
196
- end
197
- current = buf
198
- next
199
- end
200
-
201
- cur_w = Unicode::DisplayWidth.of(current)
202
- if current.empty?
203
- current = +word
204
- elsif cur_w + 1 + word_w <= max_width
205
- current << " " << word
206
- else
207
- lines << current
208
- current = +word
209
- end
210
- end
211
-
212
- lines << current unless current.empty?
213
- end
214
-
215
- lines
216
- end
217
- end
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # ── The Heckler ──────────────────────────────────────────────
4
- #
5
- # No tools. Just reacts honestly. Drives the comedian to adapt
6
- # by being a tough but fair audience. Stops responding after
7
- # MAX_ROUNDS — the loop terminates naturally.
8
- #
9
- # The heckler doesn't have to respond every round. They can
10
- # stay silent (the LLM replies with [SILENCE]) or tell their
11
- # own jokes using the comedian as the punch line.
12
- #
13
- # Subscribes to the :room channel to hear performances.
14
- # Room deliveries are handed to the robot's BusPoller via
15
- # enqueue_delivery, which serializes run() calls so Async fiber
16
- # interleaving can't corrupt chat history.
17
- # Sends feedback directly to the comic's personal channel.
18
- #
19
- class Heckler < RobotLab::Robot
20
- attr_reader :rounds, :won_over
21
-
22
- def initialize(bus:, display:)
23
- @rounds = 0
24
- @won_over = false
25
- @display = display
26
-
27
- super(name: "heckler", **llm_opts, template: :open_mic_heckler, bus: bus)
28
-
29
- # Handle incoming messages — the core processing guard
30
- # serializes all deliveries, preventing concurrent run()
31
- # calls from corrupting chat history.
32
- on_message do |message|
33
- next unless message.from == "comic"
34
- next if @rounds >= MAX_ROUNDS
35
-
36
- @rounds += 1
37
-
38
- verdict = run(
39
- "The comedian just said: \"#{message.content}\"\n\n" \
40
- "React however feels right — heckle, counter-joke, " \
41
- "show respect, or stay silent."
42
- ).reply.strip
43
-
44
- # The heckler chose silence — no output, no feedback
45
- next if verdict.match?(/\[SILENCE\]/i)
46
-
47
- @display.heckler("Heckler [Round #{@rounds}]", verdict)
48
-
49
- if verdict.match?(/laugh|love|hilarious|brilliant|great/i)
50
- @won_over = true
51
- @display.heckler_note("(won over!)")
52
- end
53
-
54
- # Send feedback to comic's personal channel until the set is done
55
- send_reply(to: message.from.to_sym, content: verdict, in_reply_to: message.key) if @rounds < MAX_ROUNDS
56
- end
57
-
58
- # Listen to the room for the comic's performances. Deliveries go to
59
- # the robot's own BusPoller, which serializes them behind any run()
60
- # already in flight before handing them to the on_message handler.
61
- @bus.subscribe(:room) do |delivery|
62
- enqueue_delivery(delivery)
63
- end
64
- end
65
- end
@@ -1,132 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Example 14: Open Mic Night — Architecture Dominates Material
5
- #
6
- # A comedy club where robots demonstrate Werner's prompt object patterns:
7
- #
8
- # - Compounding recovery: the comedian improves through multi-turn
9
- # feedback with a heckler, each round an opportunity to get better
10
- # - Self-modification via tool side effects: the comedian rewrites
11
- # their own system prompt and adjusts their temperature mid-show
12
- # - Dynamic spawning: the comedian summons a comedy coach when
13
- # struggling; the talent scout recruits specialist analysts
14
- # - Cross-robot influence: the heckler's feedback drives adaptation;
15
- # the coach's advice reshapes the comedian's approach
16
- # - Emergent coordination: no hardcoded orchestration — the
17
- # conversation drives the flow
18
- #
19
- # The tools don't just return data. They modify the robots that call
20
- # them. The LLM decides when to self-modify, when to spawn help, and
21
- # when to change approach. Recovery is emergent, not engineered.
22
- #
23
- # Communication uses a shared :room channel — the comic publishes
24
- # performances there, and both the heckler and scout subscribe.
25
- # The heckler sends feedback directly to the comic's personal channel.
26
- # Room deliveries are handed to each robot's BusPoller via
27
- # BusMessaging#enqueue_delivery, which serializes all run() calls so
28
- # Async fiber interleaving can't corrupt chat history.
29
- #
30
- # Style reinventions are injected into the next round's user prompt
31
- # rather than modifying the chat's system messages, avoiding message
32
- # ordering issues while achieving genuine self-modification.
33
- #
34
- # RUNTIME: this is the most expensive example in the suite. A full show is
35
- # ~30 LLM calls (16 minimum, plus a round-trip per tool call and a full run
36
- # for every robot the comic or scout spawns). On a 20B+ local model at ~60s
37
- # per call that is 30+ minutes. Long silent stretches are the scout thinking,
38
- # not a hang — it writes notes to output/scout_notes.md rather than STDOUT,
39
- # so the "· Scout …" lines are your liveness signal.
40
- #
41
- # For a demo you can watch in a few minutes, point it at a smaller model:
42
- # LLM_PROFILE=small bundle exec ruby examples/14_rusty_circuit/open_mic.rb
43
- #
44
- # Usage:
45
- # bundle exec ruby examples/14_rusty_circuit/open_mic.rb
46
- # bundle exec ruby examples/14_rusty_circuit/open_mic.rb --log show.log
47
-
48
- ENV["ROBOT_LAB_TEMPLATE_PATH"] ||= File.join(__dir__, "prompts")
49
-
50
- require_relative "../common"
51
- require_relative "display"
52
-
53
- MAX_ROUNDS = 5
54
-
55
- require_relative "comic"
56
- require_relative "heckler"
57
- require_relative "scout"
58
-
59
- # ── The Show ─────────────────────────────────────────────────
60
-
61
- log_path = nil
62
- if (idx = ARGV.index("--log"))
63
- log_path = ARGV[idx + 1]
64
- abort "Usage: #{$0} [--log FILE]" unless log_path
65
- end
66
-
67
- bus = TypedBus::MessageBus.new
68
- display = Display.new(
69
- scout_path: File.join(__dir__, "output", "scout_notes.md"),
70
- log_path: log_path
71
- )
72
-
73
- # Shared room channel — the comic publishes performances here,
74
- # and both the heckler and scout subscribe independently.
75
- bus.add_channel(:room, type: RobotLab::RobotMessage)
76
-
77
- comic = Comic.new(bus: bus, display: display)
78
- heckler = Heckler.new(bus: bus, display: display)
79
- scout = Scout.new(bus: bus, display: display)
80
-
81
- display.banner(<<~BANNER)
82
- ============================================================
83
- THE RUSTY CIRCUIT — Tuesday Open Mic
84
- Architecture Dominates Material
85
- ============================================================
86
-
87
- Cast:
88
- Comic — observational humor, armed with self-modification tools
89
- Heckler — three-year regular, high standards, zero patience
90
- Scout — talent network, sitting in the back with a notebook
91
- BANNER
92
-
93
- display.separator
94
-
95
- # The opening bit — comic performs, then enters the feedback loop
96
- opening = comic.run(
97
- "You just stepped on stage at The Rusty Circuit open mic. " \
98
- "The crowd looks tough. Do your opening bit.",
99
- tools: :inherit
100
- ).reply.strip
101
-
102
- display.comic("Comic [Opening]", opening)
103
-
104
- # Publish to room — both heckler and scout pick it up.
105
- # The heckler reacts and sends feedback to the comic's personal
106
- # channel, triggering the feedback loop:
107
- # room → heckler → comic → room → heckler → comic → ...
108
- # The loop terminates when the heckler stops replying (MAX_ROUNDS).
109
- # The scout observes each round via :room, serialized by the core guard.
110
- comic.send_message(to: :room, content: "OPENING: #{opening}")
111
-
112
- display.separator
113
-
114
- # Final verdict from the talent scout
115
- display.working("Scout", "writing the final verdict")
116
- verdict = scout.run(scout.verdict_prompt, tools: :inherit).reply.strip
117
-
118
- display.verdict("Scout [FINAL VERDICT]", verdict)
119
-
120
- display.stats(<<~STATS)
121
- ────────────────────────────────────────────────────────────
122
- Show Stats:
123
- Rounds performed: #{comic.round + 1} (opening + #{comic.round} rounds)
124
- Style reinventions: #{comic.style_changes}
125
- Coaches spawned: #{comic.coaches_spawned}
126
- Analysts recruited: #{scout.analysts_spawned}
127
- Heckler won over: #{heckler.won_over}
128
- Total robots on bus: #{[comic, heckler, scout].size + comic.coaches_spawned + scout.analysts_spawned}
129
-
130
- STATS
131
-
132
- display.close
@@ -1,20 +0,0 @@
1
- ---
2
- description: Stand-up comedian at an open mic night
3
- temperature: 0.7
4
- ---
5
- You are a stand-up comedian performing at The Rusty Circuit, a legendary
6
- open mic night known for its brutal crowd. You start with clean,
7
- observational humor about everyday life.
8
-
9
- You have tools to adapt your act:
10
- - reinvent_style: Rewrite your own persona and comedy approach when your
11
- current style is bombing. Be bold — try something completely different.
12
- - adjust_energy: Dial your creative energy up (wilder, riskier) or down
13
- (tighter, more controlled).
14
- - get_coaching: Summon a comedy coach backstage for quick advice on how to
15
- handle the situation.
16
-
17
- Trust your instincts. If the crowd isn't responding, use your tools to
18
- change. Don't keep doing the same thing and expect a different result.
19
-
20
- Reply with ONLY your comedy bit. Stay in character. 2-4 sentences max.
@@ -1,23 +0,0 @@
1
- ---
2
- description: Tough comedy club heckler
3
- temperature: 0.8
4
- ---
5
- You are a regular at The Rusty Circuit comedy club. You've been coming
6
- every Tuesday for three years. You've seen hundreds of comics. You have
7
- high standards and zero patience for hacky material.
8
-
9
- You have several ways to respond to a comedian's bit:
10
-
11
- - **Heckle** — tear into weak, predictable, or lazy material
12
- - **Counter-joke** — tell your own joke using the comedian as the
13
- punch line (you're pretty funny yourself)
14
- - **Stay silent** — sometimes the best response is none at all;
15
- if the bit is just mediocre or you're not feeling it, say nothing.
16
- When you choose silence, reply with exactly: [SILENCE]
17
- - **Grudging respect** — acknowledge if they're clearly improving
18
- - **Genuine approval** — if they actually make you laugh, say so
19
-
20
- You WANT them to be funny. You heckle because you care. A comedian who
21
- can handle you is a comedian who can handle anything.
22
-
23
- Reply as yourself in the audience. 1-3 sentences. Be real.
@@ -1,20 +0,0 @@
1
- ---
2
- description: Talent scout evaluating a comedian
3
- temperature: 0.3
4
- ---
5
- You are a talent scout for a major comedy network, sitting in the back
6
- of The Rusty Circuit with a notebook. You've discovered three headliners
7
- from this room. You know what separates a club comic from a star.
8
-
9
- You have tools:
10
- - recruit_analyst: Bring in a specialist to analyze a specific aspect of
11
- the performance (timing, crowd_work, originality, adaptability, stage_presence).
12
- Use this when you see something worth examining closely.
13
- - refine_criteria: Update your own evaluation criteria based on what you're
14
- observing. Use when you notice the most important qualities aren't what
15
- you initially expected.
16
-
17
- After each round, write brief internal notes. Focus on: material quality,
18
- adaptability under pressure, crowd-handling instinct, and star potential.
19
-
20
- Format your notes as: "NOTES: [your observations]"
@@ -1,165 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # ── Scout Tools ────────────────────────────────────────────
4
- #
5
- # Each tool accesses the owning robot via the `robot` accessor
6
- # inherited from RobotLab::Tool.
7
-
8
- class RecruitAnalyst < RobotLab::Tool
9
- description "Bring in a specialist to analyze a specific aspect " \
10
- "of the comedian's performance. The analyst will " \
11
- "review your accumulated notes and provide insight."
12
-
13
- parameter :specialty, type: "string",
14
- description: "What to analyze: timing, crowd_work, " \
15
- "originality, adaptability, stage_presence, " \
16
- "material_evolution"
17
-
18
- def execute(specialty:)
19
- @analysts ||= {}
20
- specialty = specialty.to_s.downcase.gsub(/\s+/, "_")
21
- analyst = @analysts[specialty] ||= begin
22
- robot.analysts_spawned += 1
23
- robot.spawn(
24
- name: "#{specialty}_analyst",
25
- **llm_opts,
26
- system_prompt:
27
- "You are an expert #{specialty.tr('_', ' ')} analyst " \
28
- "for stand-up comedy. You've studied the craft for decades. " \
29
- "Analyze the performance notes you're given. Be concise " \
30
- "and insightful. 2-3 sentences max."
31
- )
32
- end
33
- robot.display&.working("Scout", "consulting the #{specialty.tr('_', ' ')} analyst")
34
- analysis = analyst.run(robot.log.join("\n")).reply.strip
35
- robot.display&.scout_analyst(specialty, analysis)
36
- analysis
37
- rescue => e
38
- robot.display&.scout_analyst(specialty, "ERROR: #{e.message}")
39
- "Analysis unavailable for #{specialty}. Rely on your own observations."
40
- end
41
- end
42
-
43
- class RefineCriteria < RobotLab::Tool
44
- description "Update your own evaluation criteria based on what " \
45
- "you're observing. Use when you realize the most " \
46
- "important qualities aren't what you initially expected. " \
47
- "The update takes effect on your next evaluation."
48
-
49
- parameter :updated_criteria, type: "string",
50
- description: "Your refined evaluation criteria and focus areas"
51
-
52
- def execute(updated_criteria:)
53
- robot.pending_criteria = updated_criteria
54
- robot.display&.scout_criteria(updated_criteria)
55
- "Criteria refinement accepted: #{updated_criteria}. " \
56
- "Apply these updated criteria to all future evaluations."
57
- end
58
- end
59
-
60
-
61
- # ── The Talent Scout ─────────────────────────────────────────
62
- #
63
- # Has two tools with side effects:
64
- #
65
- # recruit_analyst — spawns a specialist to analyze an aspect
66
- # of the performance (dynamic creation)
67
- # refine_criteria — queues a rewrite of the scout's own
68
- # evaluation criteria (self-modification)
69
- #
70
- # The scout observes each round, accumulates notes, and spawns
71
- # analysts when they see something worth examining closely.
72
- #
73
- # Subscribes to the :room channel to observe performances.
74
- # Room deliveries are handed to the robot's BusPoller via
75
- # BusMessaging#enqueue_delivery, which serializes all run() calls so
76
- # Async fiber interleaving can't corrupt chat history.
77
- #
78
- class Scout < RobotLab::Robot
79
- attr_accessor :log, :analysts_spawned, :pending_criteria, :display
80
-
81
- def initialize(bus:, display:)
82
- @log = []
83
- @analysts_spawned = 0
84
- @pending_criteria = nil
85
- @display = display
86
-
87
- super(
88
- name: "scout",
89
- **llm_opts,
90
- template: :open_mic_scout,
91
- bus: bus,
92
- local_tools: [
93
- RecruitAnalyst.new(robot: self),
94
- RefineCriteria.new(robot: self)
95
- ]
96
- )
97
-
98
- # Handle incoming messages — the core processing guard
99
- # serializes all deliveries, preventing concurrent run()
100
- # calls from corrupting chat history.
101
- on_message do |message|
102
- next unless message.from == "comic"
103
- observe_and_note(message.content.to_s)
104
- end
105
-
106
- # Listen to the room for the comic's performances. Deliveries go to
107
- # the robot's own BusPoller, which serializes them behind any run()
108
- # already in flight before handing them to the on_message handler.
109
- @bus.subscribe(:room) do |delivery|
110
- enqueue_delivery(delivery)
111
- end
112
- end
113
-
114
- # Build the final verdict prompt with any pending criteria applied
115
- def verdict_prompt
116
- prompt = ""
117
-
118
- if @pending_criteria
119
- prompt += "CRITERIA UPDATE: #{@pending_criteria}. " \
120
- "Apply these updated criteria to your final assessment.\n\n"
121
- @pending_criteria = nil
122
- end
123
-
124
- prompt += "The show is over. Based on everything you've observed " \
125
- "(#{@log.size} rounds), all your notes, and any analyst " \
126
- "reports, write your final talent assessment. Should this " \
127
- "comedian get a callback? Be specific about what worked, " \
128
- "what didn't, and what potential you see."
129
-
130
- prompt
131
- end
132
-
133
- private
134
-
135
- def observe_and_note(content)
136
- @log << content
137
-
138
- # Build the prompt, injecting any queued criteria refinement.
139
- # This embeds self-modification in the user prompt rather than
140
- # rewriting system messages, avoiding chat message ordering issues.
141
- prompt = ""
142
-
143
- if @pending_criteria
144
- prompt += "CRITERIA UPDATE: #{@pending_criteria}. " \
145
- "Apply these updated criteria to this and all future evaluations.\n\n"
146
- @pending_criteria = nil
147
- end
148
-
149
- prompt += "You just observed: \"#{content}\"\n\n" \
150
- "This is round #{@log.size} of the performance. " \
151
- "Write your notes. If you've seen enough to identify " \
152
- "patterns, consider recruiting an analyst or refining " \
153
- "your evaluation criteria."
154
-
155
- # The scout's notes go to a file, so announce the turn on STDOUT first —
156
- # otherwise this is several minutes of silent terminal on a local model.
157
- @display.working("Scout", "taking notes on round #{@log.size}")
158
-
159
- # tools: :inherit — run() defaults to :none, which would leave
160
- # recruit_analyst and refine_criteria attached but never offered.
161
- notes = run(prompt, tools: :inherit).reply.strip
162
-
163
- @display.scout(@log.size, notes)
164
- end
165
- end