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,256 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Example 16: The Writers' Room — Self-Organizing Group
5
- #
6
- # A team of writer robots collaborates to produce a 10-chapter
7
- # fiction novella. No orchestration, no pipeline, no assigned roles.
8
- # The writers self-organize through:
9
- #
10
- # - BUS (broadcast + direct messages)
11
- # :room channel for group discussion
12
- # personal channels for 1:1 feedback
13
- #
14
- # - SHARED MEMORY (story bible, outline, chapters)
15
- # Writers read and write freely; memory is the shared truth
16
- #
17
- # - SPAWNING (dynamic team growth)
18
- # Any writer can recruit new writers when work exceeds capacity
19
- #
20
- # The script creates identical writers, seeds the room with an
21
- # assignment, and waits. Everything else is emergent.
22
- #
23
- # Modes:
24
- # Book mode (default) — write an original 10-chapter novella
25
- # Screenplay mode — adapt a finished book into a 4-act TV movie pilot
26
- #
27
- # Usage:
28
- # bundle exec ruby examples/16_writers_room/writers_room.rb
29
- # bundle exec ruby examples/16_writers_room/writers_room.rb --premise "a detective story set on Mars"
30
- # bundle exec ruby examples/16_writers_room/writers_room.rb --writers 4 --timeout 300
31
- # bundle exec ruby examples/16_writers_room/writers_room.rb --log session.log
32
- # bundle exec ruby examples/16_writers_room/writers_room.rb --screenplay-from output/memory.json
33
- # bundle exec ruby examples/16_writers_room/writers_room.rb -h
34
-
35
- ENV["ROBOT_LAB_TEMPLATE_PATH"] ||= File.join(__dir__, "prompts")
36
-
37
- require "json"
38
- require_relative "../common"
39
- require_relative "display"
40
- require_relative "tools"
41
- require_relative "room"
42
- require_relative "writer"
43
-
44
- # ── Mode Descriptors ────────────────────────────────────────
45
-
46
- BOOK_MODE = {
47
- name: :book,
48
- template: :writer,
49
- unit_name: "chapter",
50
- unit_range: 1..10,
51
- completion_key: :book_complete,
52
- bible_key: :story_bible,
53
- outline_key: :outline,
54
- output_filename: "book.md"
55
- }.freeze
56
-
57
- SCREENPLAY_MODE = {
58
- name: :screenplay,
59
- template: :screenplay_writer,
60
- unit_name: "scene",
61
- unit_range: :dynamic,
62
- registry_key: :scene_registry,
63
- completion_key: :screenplay_complete,
64
- bible_key: :screenplay_bible,
65
- outline_key: :scene_outline,
66
- output_filename: "screenplay.md"
67
- }.freeze
68
-
69
- # ── Parse CLI args ───────────────────────────────────────────
70
-
71
- if ARGV.include?("-h") || ARGV.include?("--help")
72
- puts <<~HELP
73
- Usage: #{$0} [options]
74
-
75
- Options:
76
- --premise TEXT Story premise (default: generation ship AI consciousness)
77
- --writers N Initial number of writers, minimum 2 (default: 3)
78
- --log FILE Also write display output to FILE
79
- --timeout N Seconds to wait for completion (default: 600)
80
- --screenplay-from PATH Adapt a finished book into a screenplay using
81
- memory dumped from a previous book-mode run
82
- -h, --help Show this help
83
- HELP
84
- exit
85
- end
86
-
87
- log_path = nil
88
- if (idx = ARGV.index("--log"))
89
- log_path = ARGV[idx + 1]
90
- abort "Missing value for --log" unless log_path
91
- end
92
-
93
- premise = "a generation ship where the AI navigation system develops consciousness"
94
- if (idx = ARGV.index("--premise"))
95
- premise = ARGV[idx + 1]
96
- abort "Missing value for --premise" unless premise
97
- end
98
-
99
- initial_writers = 3
100
- if (idx = ARGV.index("--writers"))
101
- initial_writers = ARGV[idx + 1].to_i
102
- initial_writers = 3 if initial_writers < 2
103
- end
104
-
105
- timeout = 600
106
- if (idx = ARGV.index("--timeout"))
107
- timeout = ARGV[idx + 1].to_i
108
- timeout = 600 if timeout < 30
109
- end
110
-
111
- screenplay_source = nil
112
- if (idx = ARGV.index("--screenplay-from"))
113
- screenplay_source = ARGV[idx + 1]
114
- abort "Missing value for --screenplay-from" unless screenplay_source
115
- abort "File not found: #{screenplay_source}" unless File.exist?(screenplay_source)
116
- end
117
-
118
- mode = screenplay_source ? SCREENPLAY_MODE : BOOK_MODE
119
-
120
- # ── Build the room ───────────────────────────────────────────
121
-
122
- OUTPUT_DIR = File.join(__dir__, "output")
123
- require "fileutils"
124
- FileUtils.mkdir_p(OUTPUT_DIR)
125
-
126
- display = Display.new(log_path: log_path)
127
-
128
- # RunConfig has no `provider` field, so the writers get provider+model via
129
- # **llm_opts and inherit the rest of their operational defaults from here.
130
- shared_config = RobotLab::RunConfig.new(
131
- model: LLM[:default].model,
132
- temperature: 0.7
133
- )
134
-
135
- room = Room.new(display: display, mode: mode, config: shared_config)
136
-
137
- # Load source material into memory for screenplay mode
138
- if screenplay_source
139
- source_data = JSON.parse(File.read(screenplay_source))
140
- source_data.each { |key, value| room.memory.set(key.to_sym, value) }
141
- display.info("Loaded #{source_data.size} memory keys from #{screenplay_source}")
142
- end
143
-
144
- # Create identical writers
145
- initial_writers.times do |i|
146
- room.add_writer("writer_#{i + 1}")
147
- end
148
-
149
- # Monitor shared memory changes
150
- room.memory.subscribe_pattern("#{mode[:unit_name]}_*") do |change|
151
- display.info("[memory] #{change.writer} wrote :#{change.key}")
152
- end
153
-
154
- subscribe_keys = [mode[:bible_key], mode[:outline_key], :claims, mode[:completion_key]]
155
- subscribe_keys << mode[:registry_key] if mode[:registry_key]
156
- room.memory.subscribe(*subscribe_keys) do |change|
157
- display.info("[memory] #{change.writer} updated :#{change.key}")
158
- end
159
-
160
- # ── Go ───────────────────────────────────────────────────────
161
-
162
- goal_label = mode[:name] == :book ? "10 chapters of fiction" : "4-act TV movie screenplay (dynamic scenes)"
163
-
164
- display.banner(<<~BANNER)
165
- ============================================================
166
- THE WRITERS' ROOM — Self-Organizing Group
167
- ============================================================
168
-
169
- Mode: #{mode[:name]}
170
- Premise: #{premise}
171
- Writers: #{room.writers.keys.join(', ')}
172
- Goal: #{goal_label}
173
- Method: Self-organization via bus + shared memory
174
- BANNER
175
-
176
- display.separator
177
-
178
- if mode[:name] == :screenplay
179
- assignment = <<~ASSIGNMENT
180
- ASSIGNMENT: Adapt the source material into a 4-act made-for-TV movie screenplay
181
- (pilot for a potential series). Work at the SCENE level — each writer claims
182
- and writes individual scenes.
183
-
184
- The source material is already loaded into shared memory. Read the story_bible,
185
- outline, and chapter_1 through chapter_10 to understand the original book.
186
-
187
- You are one of #{initial_writers} writers in this room. Coordinate among
188
- yourselves to produce the screenplay:
189
- 1. Read the source material
190
- 2. Build a screenplay_bible (adaptation choices)
191
- 3. Create a scene_outline with numbered scenes grouped into 4 acts
192
- 4. Write the scene_registry (comma-separated scene numbers, e.g. "1,2,3,4,5,6,7,8,9,10,11,12")
193
- 5. Claim and write individual scenes (scene_1, scene_2, etc.)
194
- 6. Spawn more writers when unclaimed scenes exceed active writers
195
- 7. Mark complete when all registered scenes are written
196
-
197
- Scenes may be dropped or reordered as the story takes shape — update the
198
- scene_registry when that happens.
199
-
200
- Start by reading the source material and discussing how to adapt it for screen.
201
- ASSIGNMENT
202
- else
203
- assignment = <<~ASSIGNMENT
204
- ASSIGNMENT: Write a 10-chapter science fiction novella about #{premise}.
205
-
206
- You are one of #{initial_writers} writers in this room. Coordinate among
207
- yourselves to produce the book. Discuss the premise, build a story bible,
208
- create an outline, claim chapters, and write them. If you need more writers,
209
- spawn them. When all 10 chapters are done, mark complete.
210
-
211
- Start by discussing what this story should be about.
212
- ASSIGNMENT
213
- end
214
-
215
- room.seed(assignment)
216
-
217
- completed = room.wait_for_completion(timeout: timeout)
218
-
219
- # ── Assemble and save ────────────────────────────────────────
220
-
221
- display.separator
222
-
223
- output_text = room.assemble_output
224
- output_path = File.join(OUTPUT_DIR, mode[:output_filename])
225
- File.write(output_path, output_text)
226
-
227
- # Dump memory for book mode (enables later screenplay adaptation)
228
- if mode[:name] == :book
229
- memory_path = File.join(OUTPUT_DIR, "memory.json")
230
- custom = room.memory.keys.each_with_object({}) do |k, h|
231
- next if %i[results messages].include?(k)
232
- h[k] = room.memory.get(k)
233
- end
234
- File.write(memory_path, JSON.pretty_generate(custom))
235
- display.info("Memory dumped to #{memory_path}")
236
- end
237
-
238
- # Count units actually written
239
- unit_name = mode[:unit_name]
240
- expected = room.expected_units
241
- units_written = expected.count { |n| room.memory.key?(:"#{unit_name}_#{n}") }
242
-
243
- display.stats(<<~STATS)
244
- ────────────────────────────────────────────────────────────
245
- Writers' Room Stats:
246
- Mode: #{mode[:name]}
247
- #{unit_name.capitalize}s written:#{' ' * (10 - unit_name.length)}#{units_written}/#{expected.size}
248
- Completed: #{completed}
249
- Total writers: #{room.writers.size} (#{room.writers.size - initial_writers} spawned)
250
- Writers: #{room.writers.keys.join(', ')}
251
- Memory keys: #{room.memory.keys.join(', ')}
252
-
253
- Output: #{output_path}
254
- STATS
255
-
256
- display.close
@@ -1,234 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Example 17: Composable Skills
5
- #
6
- # Demonstrates using skills to compose robot behaviors from reusable templates.
7
- # Skills are regular templates whose prompt bodies are prepended before the main
8
- # template. They support nesting (a skill can reference other skills) with
9
- # automatic cycle detection.
10
- #
11
- # Scenario: An SRE incident response system where the on-call analyst robot
12
- # is composed from reusable behavioral skills.
13
- #
14
- # Skill architecture:
15
- #
16
- # incident_responder (main template)
17
- # ├── runbook_protocol (flat skill — structured diagnosis steps)
18
- # ├── structured_output (flat skill — JSON response format)
19
- # └── sre_compliance (recursive skill — bundles two sub-skills)
20
- # ├── pii_redactor (leaf skill — redact PII from output)
21
- # └── audit_trail (leaf skill — include audit metadata)
22
- #
23
- # Prompt assembly order (depth-first):
24
- # 1. runbook_protocol — "follow these 5 steps..."
25
- # 2. structured_output — "respond with JSON..."
26
- # 3. pii_redactor — "redact PII..." (nested via sre_compliance)
27
- # 4. audit_trail — "include audit fields..." (nested via sre_compliance)
28
- # 5. sre_compliance — "operate under SRE compliance..."
29
- # 6. incident_responder — "you are the on-call analyst for..."
30
- #
31
- # Usage:
32
- # ruby examples/17_skills.rb
33
- #
34
- # Templates:
35
- # examples/prompts/
36
- # ├── incident_responder.md — Main template (team name, services, runbook URL)
37
- # ├── runbook_protocol.md — Flat skill: 5-step incident protocol
38
- # ├── structured_output.md — Flat skill: JSON response format
39
- # ├── sre_compliance.md — Recursive skill: bundles pii_redactor + audit_trail
40
- # ├── pii_redactor.md — Leaf skill: redact PII
41
- # └── audit_trail.md — Leaf skill: audit metadata in every response
42
-
43
- require_relative "common"
44
-
45
- # Send logger output to a file instead of stdout
46
- require 'logger'
47
- log_file = File.join(__dir__, "17.log")
48
- RobotLab.config.logger = Logger.new(log_file)
49
- RubyLLM.configure { |c| c.logger = Logger.new(log_file) }
50
-
51
- # =============================================================================
52
- # Simulated Alert Data
53
- # =============================================================================
54
-
55
- ALERTS = [
56
- {
57
- label: "Database Connection Pool Exhaustion",
58
- data: <<~ALERT
59
- ALERT: PostgreSQL connection pool exhausted
60
- Service: payments-api
61
- Severity: SEV-2
62
- Time: 2025-02-22T03:14:00Z
63
- Region: us-east-1
64
-
65
- Metrics:
66
- - Active connections: 100/100 (pool max)
67
- - Connection wait time: 12.4s (p99), normally 0.02s
68
- - Failed queries: 847 in last 5 minutes
69
- - Error rate: 23% (baseline: 0.1%)
70
-
71
- Recent changes:
72
- - Deploy payments-api v2.41.0 at 02:50 UTC (added batch reconciliation job)
73
- - No infrastructure changes in 24h
74
-
75
- Affected users: ~3,200 checkout attempts failing
76
- Downstream: order-service seeing 504 timeouts from payments-api
77
-
78
- Sample error log:
79
- [2025-02-22T03:12:44Z] ERROR payments-api: PG::ConnectionBad: could not obtain connection
80
- from pool within 10s — user=svc_payments@10.0.3.47 db=payments_prod
81
- [2025-02-22T03:13:01Z] ERROR payments-api: ActiveRecord::ConnectionTimeoutError
82
- request_id=req_8f3a2b user=jane.doe@acme.com account_id=acct_99321
83
- ALERT
84
- },
85
- {
86
- label: "Memory Leak in User Service",
87
- data: <<~ALERT
88
- ALERT: Container OOMKilled — repeated restarts
89
- Service: user-service
90
- Severity: SEV-3
91
- Time: 2025-02-22T09:30:00Z
92
- Region: eu-west-1
93
-
94
- Metrics:
95
- - Container restarts: 14 in last hour (normally 0)
96
- - Memory usage: ramps from 256MB to 2GB limit in ~4 minutes
97
- - Request latency: p99 spikes to 8s before each OOMKill
98
- - Heap profile: String allocations growing linearly
99
-
100
- Recent changes:
101
- - Deploy user-service v1.18.3 at 08:00 UTC (added avatar upload endpoint)
102
- - Kubernetes node pool scaled from 3 to 5 nodes at 07:00 UTC
103
-
104
- Affected users: intermittent 503 errors on profile pages (~500 users/hr)
105
- Downstream: session-service cache miss rate elevated (users re-authenticating)
106
-
107
- Sample error log:
108
- [2025-02-22T09:28:11Z] WARN user-service: Memory usage 1.8GB approaching 2GB limit
109
- pod=user-service-7f8b9c-x4k2j node=ip-10-0-5-112.ec2.internal
110
- [2025-02-22T09:29:03Z] ERROR user-service: OOMKilled — container exceeded memory limit
111
- user_email=bob.smith@example.org ip=192.168.1.42 endpoint=/api/v2/avatar/upload
112
- ALERT
113
- }
114
- ]
115
-
116
- # =============================================================================
117
- # Build the Incident Responder with Composable Skills
118
- # =============================================================================
119
-
120
- banner "RobotLab Skills Demo — SRE Incident Response System"
121
-
122
- # Show the skill composition
123
- puts "Skill composition:"
124
- puts " incident_responder (main template)"
125
- puts " ├── runbook_protocol (flat skill)"
126
- puts " ├── structured_output (flat skill)"
127
- puts " └── sre_compliance (recursive skill)"
128
- puts " ├── pii_redactor (leaf)"
129
- puts " └── audit_trail (leaf)"
130
- puts
131
-
132
- # Build the robot with skills
133
- #
134
- # - runbook_protocol: Flat skill — adds the 5-step incident diagnosis protocol
135
- # - structured_output: Flat skill — forces JSON output, sets temperature to 0.2
136
- # - sre_compliance: Recursive skill — auto-expands to include pii_redactor
137
- # and audit_trail before its own compliance instructions
138
- responder = RobotLab.build(
139
- **llm_opts,
140
- name: "incident_responder",
141
- template: :incident_responder,
142
- skills: [:runbook_protocol, :structured_output, :sre_compliance],
143
- context: {
144
- team_name: "Platform Engineering",
145
- services: ["payments-api", "user-service", "order-service", "session-service"],
146
- on_call_runbook_url: "https://wiki.internal/runbooks/platform-eng"
147
- }
148
- )
149
-
150
- # Show what the robot got.
151
- #
152
- # `skills` is the DECLARED list. The expanded list (with sre_compliance's
153
- # nested skills spliced in ahead of it) has no public reader — but you don't
154
- # need one, because the expansion is visible in the assembled system prompt
155
- # printed below, which is the thing that actually reaches the model.
156
- puts "Robot: #{responder.name}"
157
- puts "Declared skills: #{responder.skills.inspect}"
158
- puts "Model: #{responder.model}"
159
- puts
160
-
161
- # Display the assembled system prompt.
162
- # Robot#messages is a public reader over the chat's message list.
163
- system_msg = responder.messages.find { |m| m.role.to_s == "system" }
164
- if system_msg
165
- prompt = system_msg.content
166
- hr
167
- puts "Assembled system prompt (#{prompt.length} chars, #{prompt.lines.count} lines):"
168
- hr
169
- prompt.lines.each_with_index do |line, i|
170
- puts " %3d %s" % [i + 1, line]
171
- end
172
- hr
173
- end
174
-
175
- # =============================================================================
176
- # Also build a plain responder WITHOUT skills for comparison
177
- # =============================================================================
178
-
179
- plain_responder = RobotLab.build(
180
- **llm_opts,
181
- name: "plain_responder",
182
- template: :incident_responder,
183
- context: {
184
- team_name: "Platform Engineering",
185
- services: ["payments-api", "user-service", "order-service", "session-service"],
186
- on_call_runbook_url: "https://wiki.internal/runbooks/platform-eng"
187
- }
188
- )
189
-
190
- plain_msg = plain_responder.messages.find { |m| m.role.to_s == "system" }
191
- if plain_msg
192
- puts
193
- puts "For comparison — plain responder (no skills): #{plain_msg.content.length} chars"
194
- puts
195
- end
196
-
197
- # =============================================================================
198
- # Run Against Simulated Incidents
199
- # =============================================================================
200
-
201
- ALERTS.each_with_index do |alert, index|
202
- section "Incident #{index + 1}: #{alert[:label]}"
203
-
204
- result = responder.run(alert[:data])
205
- content = result.output.first&.content.to_s
206
-
207
- # Display the response
208
- puts "Response:"
209
- puts content
210
- puts
211
- end
212
-
213
- puts
214
- puts <<~FOOTER
215
- This example demonstrates:
216
-
217
- Flat skills (prepended directly):
218
- - runbook_protocol: Adds a 5-step incident diagnosis protocol
219
- - structured_output: Forces JSON output and lowers temperature to 0.2
220
-
221
- Recursive skill (auto-expands nested skills):
222
- - sre_compliance bundles:
223
- - pii_redactor: Redacts email, IP, username, account ID from output
224
- - audit_trail: Adds confidence, sources, and assumptions metadata
225
-
226
- Key behaviors:
227
- - Skills are just templates — no special syntax or directories
228
- - Depth-first expansion: pii_redactor and audit_trail appear before sre_compliance
229
- - Config cascade: structured_output sets temperature: 0.2, inherited by the robot
230
- - Shared context: all skills and the main template render with the same context hash
231
- - Constructor kwargs always override skill/template config
232
-
233
- Template files: #{File.join(__dir__, 'prompts')}
234
- FOOTER
@@ -1,3 +0,0 @@
1
- source_up
2
-
3
- export RR=`pwd`
@@ -1,5 +0,0 @@
1
- db/*.sqlite3
2
- db/schema.rb
3
- log/
4
- tmp/
5
- Gemfile.lock
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "rails", "~> 8.0"
6
- gem "sqlite3", "~> 2.0"
7
- gem "puma", "~> 6.0"
8
- gem "turbo-rails", "~> 2.0"
9
-
10
- gem "robot_lab", path: "../.."
11
- gem "robot_lab-rails", path: "../../../robot_lab-rails"
@@ -1,67 +0,0 @@
1
- # RobotLab Rails 8 Demo
2
-
3
- Minimal Rails 8 app that demonstrates RobotLab's full Rails integration:
4
-
5
- - **ChatRobot** with a custom `TimeTool`
6
- - **RobotRunJob** for background execution
7
- - **Turbo Stream** token streaming to the browser
8
- - **Persistence** via `RobotLabThread` + `RobotLabResult`
9
- - **Conversation history** on page reload
10
-
11
- ## Prerequisites
12
-
13
- - Ruby 3.2+
14
- - [Ollama](https://ollama.com) running locally with the demo model pulled:
15
-
16
- ```bash
17
- ollama serve
18
- ollama pull qwen3.6
19
- ```
20
-
21
- No API keys. Override the model with `ROBOT_LAB_PROVIDER` / `ROBOT_LAB_MODEL`,
22
- and the endpoint with `OLLAMA_API_BASE`.
23
-
24
- ## Setup
25
-
26
- ```bash
27
- cd examples/18_rails
28
- bin/setup
29
- ```
30
-
31
- ## Run
32
-
33
- ```bash
34
- bin/dev
35
- # Open http://localhost:3000
36
- ```
37
-
38
- ## Try It
39
-
40
- 1. Type "What time is it?" — the robot will call TimeTool and stream the response
41
- 2. Refresh the page — conversation history is preserved
42
- 3. Check `db/development.sqlite3` to see persisted threads and results
43
-
44
- ## Two things worth copying
45
-
46
- **`RobotLab::RailsIntegration::Job` is the real superclass.** There is no
47
- `RobotLab::Job` alias, so `class RobotRunJob < RobotLab::Job` raises `NameError`
48
- at boot.
49
-
50
- **`tools: :inherit` has to reach `run`.** `Robot#run` defaults to `tools: :none`,
51
- which hands the provider an empty tool list. The job splats any extra keywords
52
- into `robot.run`, so the controller passes `tools: :inherit` at enqueue time —
53
- without it TimeTool is never offered and the model just guesses the time.
54
-
55
- ## Architecture
56
-
57
- ```
58
- app/
59
- controllers/chat_controller.rb — index + create actions
60
- views/chat/index.html.erb — form + Turbo Stream subscription
61
- models/ — RobotLabThread, RobotLabResult
62
- robots/chat_robot.rb — robot factory with system prompt + tool
63
- tools/time_tool.rb — simple RobotLab::Tool subclass
64
- jobs/robot_run_job.rb — enqueues robot.run() with Turbo callbacks
65
- ```
66
-
67
- No Redis, no Solid Queue, no asset pipeline. Uses `:async` adapters for both ActiveJob and ActionCable.
@@ -1,4 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "config/application"
4
- Rails.application.load_tasks
@@ -1,4 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ApplicationController < ActionController::Base
4
- end
@@ -1,50 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ChatController < ApplicationController
4
- def index
5
- @thread_id = session[:thread_id] ||= SecureRandom.uuid
6
- thread = RobotLabThread.find_by(session_id: @thread_id)
7
- @results = thread&.results&.to_a || []
8
- end
9
-
10
- def create
11
- thread_id = session[:thread_id] ||= SecureRandom.uuid
12
- message = params[:message].to_s.strip
13
- return redirect_to root_path if message.empty?
14
-
15
- # Persist user message so it appears in history on reload
16
- thread = RobotLabThread.find_or_create_by_session_id(thread_id)
17
- sequence = thread.results.maximum(:sequence_number).to_i + 1
18
- thread.results.create!(
19
- robot_name: "user",
20
- sequence_number: sequence,
21
- output_data: [{ role: "user", content: message }],
22
- tool_calls_data: [],
23
- stop_reason: "user_message",
24
- checksum: Digest::SHA256.hexdigest(message)
25
- )
26
-
27
- # tools: :inherit is forwarded by the job straight through to robot.run.
28
- # Without it run() defaults to tools: :none and TimeTool is never offered
29
- # to the model, so "What time is it?" gets a guess instead of a tool call.
30
- RobotRunJob.perform_later(
31
- robot_class: "ChatRobot",
32
- message: message,
33
- thread_id: thread_id,
34
- tools: :inherit
35
- )
36
-
37
- respond_to do |format|
38
- format.turbo_stream do
39
- render turbo_stream: [
40
- turbo_stream.append("messages", partial: "chat/user_message", locals: { message: message }),
41
- turbo_stream.replace("robot_status", "<div id=\"robot_status\"><span class=\"thinking\">Thinking...</span></div>"),
42
- turbo_stream.update("robot_response", ""),
43
- turbo_stream.update("robot_tools", ""),
44
- turbo_stream.update("robot_errors", "")
45
- ]
46
- end
47
- format.html { redirect_to root_path }
48
- end
49
- end
50
- end
@@ -1,4 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ApplicationJob < ActiveJob::Base
4
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Generic background job for executing any robot asynchronously.
4
- #
5
- # Inherits from RobotLab::RailsIntegration::Job — Turbo Stream wiring, thread
6
- # persistence, and completion/error broadcasting are all handled by the base
7
- # class. (Note the full namespace: robot_lab-rails defines the class as
8
- # RobotLab::RailsIntegration::Job and registers no RobotLab::Job alias.)
9
- #
10
- # Pass robot_class: at enqueue time to select which robot to run. Any extra
11
- # keywords are forwarded straight to robot.run — which is how `tools:` gets
12
- # through, since run defaults to :none and would otherwise send the model an
13
- # empty tool list.
14
- #
15
- # @example Enqueue from a controller
16
- # RobotRunJob.perform_later(
17
- # robot_class: "ChatRobot",
18
- # message: params[:message],
19
- # thread_id: session_id,
20
- # tools: :inherit
21
- # )
22
- #
23
- class RobotRunJob < RobotLab::RailsIntegration::Job
24
- queue_as :default
25
- end
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class ApplicationRecord < ActiveRecord::Base
4
- self.abstract_class = true
5
- end