robot_lab 0.2.8 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -0
  3. data/README.md +16 -18
  4. data/lib/robot_lab/ask_user.rb +3 -3
  5. data/lib/robot_lab/config/defaults.yml +5 -5
  6. data/lib/robot_lab/config.rb +2 -2
  7. data/lib/robot_lab/mcp/transports/streamable_http.rb +2 -4
  8. data/lib/robot_lab/memory.rb +16 -7
  9. data/lib/robot_lab/robot/hooking.rb +26 -0
  10. data/lib/robot_lab/robot/result_building.rb +119 -0
  11. data/lib/robot_lab/robot.rb +37 -120
  12. data/lib/robot_lab/run_config.rb +52 -20
  13. data/lib/robot_lab/tool.rb +7 -12
  14. data/lib/robot_lab/version.rb +1 -1
  15. data/lib/robot_lab.rb +3 -3
  16. metadata +15 -274
  17. data/.architecture/AGENTS.md +0 -32
  18. data/.architecture/config.yml +0 -8
  19. data/.architecture/members.yml +0 -60
  20. data/.architecture/reviews/feature-free-will.md +0 -490
  21. data/.architecture/reviews/overall-codebase.md +0 -427
  22. data/.claude/settings.local.json +0 -57
  23. data/.codex/config.toml +0 -2
  24. data/.envrc +0 -6
  25. data/.github/workflows/deploy-github-pages.yml +0 -52
  26. data/.irbrc +0 -6
  27. data/.loki +0 -65
  28. data/.quality/reek_baseline.txt +0 -43
  29. data/.rubocop.yml +0 -10
  30. data/Archspec.rb +0 -44
  31. data/CLAUDE.md +0 -139
  32. data/COMMITS.md +0 -196
  33. data/Rakefile +0 -193
  34. data/_typos.toml +0 -21
  35. data/agent2agent_review.md +0 -192
  36. data/agentf_improvements.md +0 -253
  37. data/agents.md +0 -14
  38. data/docs/api/core/index.md +0 -147
  39. data/docs/api/core/memory.md +0 -578
  40. data/docs/api/core/network.md +0 -553
  41. data/docs/api/core/result.md +0 -211
  42. data/docs/api/core/robot.md +0 -1667
  43. data/docs/api/core/state.md +0 -145
  44. data/docs/api/core/tool.md +0 -520
  45. data/docs/api/errors.md +0 -323
  46. data/docs/api/hooks.md +0 -469
  47. data/docs/api/index.md +0 -137
  48. data/docs/api/mcp/client.md +0 -387
  49. data/docs/api/mcp/index.md +0 -267
  50. data/docs/api/mcp/server.md +0 -252
  51. data/docs/api/mcp/transports.md +0 -339
  52. data/docs/api/messages/index.md +0 -126
  53. data/docs/api/messages/text-message.md +0 -170
  54. data/docs/api/messages/tool-call-message.md +0 -206
  55. data/docs/api/messages/tool-result-message.md +0 -242
  56. data/docs/api/messages/user-message.md +0 -232
  57. data/docs/api/skills.md +0 -423
  58. data/docs/api/streaming/context.md +0 -280
  59. data/docs/api/streaming/events.md +0 -185
  60. data/docs/api/streaming/index.md +0 -156
  61. data/docs/api/support.md +0 -846
  62. data/docs/architecture/core-concepts.md +0 -575
  63. data/docs/architecture/index.md +0 -252
  64. data/docs/architecture/message-flow.md +0 -366
  65. data/docs/architecture/network-orchestration.md +0 -478
  66. data/docs/architecture/robot-execution.md +0 -396
  67. data/docs/architecture/state-management.md +0 -410
  68. data/docs/assets/css/custom.css +0 -56
  69. data/docs/assets/images/robot_lab.jpg +0 -0
  70. data/docs/concepts.md +0 -641
  71. data/docs/examples/basic-chat.md +0 -258
  72. data/docs/examples/index.md +0 -425
  73. data/docs/examples/mcp-server.md +0 -403
  74. data/docs/examples/multi-robot-network.md +0 -476
  75. data/docs/examples/tool-usage.md +0 -468
  76. data/docs/getting-started/configuration.md +0 -645
  77. data/docs/getting-started/index.md +0 -56
  78. data/docs/getting-started/installation.md +0 -200
  79. data/docs/getting-started/quick-start.md +0 -293
  80. data/docs/guides/building-robots.md +0 -1187
  81. data/docs/guides/creating-networks.md +0 -736
  82. data/docs/guides/hooks.md +0 -1206
  83. data/docs/guides/index.md +0 -78
  84. data/docs/guides/knowledge.md +0 -219
  85. data/docs/guides/mcp-integration.md +0 -740
  86. data/docs/guides/memory.md +0 -458
  87. data/docs/guides/observability.md +0 -733
  88. data/docs/guides/streaming.md +0 -323
  89. data/docs/guides/using-tools.md +0 -780
  90. data/docs/index.md +0 -280
  91. data/docs/superpowers/plans/2026-05-06-agentskills.md +0 -1303
  92. data/docs/superpowers/specs/2026-05-06-agentskills-design.md +0 -247
  93. data/examples/.envrc +0 -1
  94. data/examples/01_simple_robot.rb +0 -31
  95. data/examples/02_tools.rb +0 -109
  96. data/examples/03_network.rb +0 -118
  97. data/examples/04_mcp.rb +0 -215
  98. data/examples/05_streaming.rb +0 -137
  99. data/examples/06_prompt_templates.rb +0 -325
  100. data/examples/07_network_memory.rb +0 -313
  101. data/examples/08_llm_config.rb +0 -162
  102. data/examples/09_chaining.rb +0 -251
  103. data/examples/10_memory.rb +0 -315
  104. data/examples/11_network_introspection.rb +0 -239
  105. data/examples/12_message_bus.rb +0 -67
  106. data/examples/13_spawn.rb +0 -85
  107. data/examples/14_rusty_circuit/.envrc +0 -1
  108. data/examples/14_rusty_circuit/.gitignore +0 -1
  109. data/examples/14_rusty_circuit/README.md +0 -1
  110. data/examples/14_rusty_circuit/comic.rb +0 -149
  111. data/examples/14_rusty_circuit/display.rb +0 -217
  112. data/examples/14_rusty_circuit/heckler.rb +0 -65
  113. data/examples/14_rusty_circuit/open_mic.rb +0 -132
  114. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +0 -20
  115. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +0 -23
  116. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +0 -20
  117. data/examples/14_rusty_circuit/scout.rb +0 -165
  118. data/examples/14_rusty_circuit/show.log +0 -234
  119. data/examples/15_memory_network_and_bus/.envrc +0 -1
  120. data/examples/15_memory_network_and_bus/editor_in_chief.rb +0 -24
  121. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +0 -213
  122. data/examples/15_memory_network_and_bus/linux_writer.rb +0 -80
  123. data/examples/15_memory_network_and_bus/os_editor.rb +0 -48
  124. data/examples/15_memory_network_and_bus/os_writer.rb +0 -49
  125. data/examples/15_memory_network_and_bus/output/combined_article.md +0 -13
  126. data/examples/15_memory_network_and_bus/output/final_article.md +0 -13
  127. data/examples/15_memory_network_and_bus/output/linux_draft.md +0 -7
  128. data/examples/15_memory_network_and_bus/output/mac_draft.md +0 -7
  129. data/examples/15_memory_network_and_bus/output/memory.json +0 -13
  130. data/examples/15_memory_network_and_bus/output/revision_1.md +0 -18
  131. data/examples/15_memory_network_and_bus/output/revision_2.md +0 -13
  132. data/examples/15_memory_network_and_bus/output/windows_draft.md +0 -7
  133. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +0 -13
  134. data/examples/15_memory_network_and_bus/prompts/os_chief.md +0 -13
  135. data/examples/15_memory_network_and_bus/prompts/os_editor.md +0 -13
  136. data/examples/16_writers_room/.envrc +0 -1
  137. data/examples/16_writers_room/display.rb +0 -158
  138. data/examples/16_writers_room/output/.gitignore +0 -4
  139. data/examples/16_writers_room/output/README.md +0 -69
  140. data/examples/16_writers_room/output/opus_001.md +0 -263
  141. data/examples/16_writers_room/output/opus_001_notes.log +0 -470
  142. data/examples/16_writers_room/output/opus_002.md +0 -245
  143. data/examples/16_writers_room/output/opus_002_notes.log +0 -546
  144. data/examples/16_writers_room/output/opus_002_screenplay.md +0 -7989
  145. data/examples/16_writers_room/output/opus_002_screenplay_notes.md +0 -993
  146. data/examples/16_writers_room/prompts/screenplay_writer.md +0 -66
  147. data/examples/16_writers_room/prompts/writer.md +0 -37
  148. data/examples/16_writers_room/room.rb +0 -186
  149. data/examples/16_writers_room/tools.rb +0 -173
  150. data/examples/16_writers_room/writer.rb +0 -121
  151. data/examples/16_writers_room/writers_room.rb +0 -256
  152. data/examples/17_skills.rb +0 -234
  153. data/examples/18_rails/.envrc +0 -3
  154. data/examples/18_rails/.gitignore +0 -5
  155. data/examples/18_rails/Gemfile +0 -11
  156. data/examples/18_rails/README.md +0 -67
  157. data/examples/18_rails/Rakefile +0 -4
  158. data/examples/18_rails/app/controllers/application_controller.rb +0 -4
  159. data/examples/18_rails/app/controllers/chat_controller.rb +0 -50
  160. data/examples/18_rails/app/jobs/application_job.rb +0 -4
  161. data/examples/18_rails/app/jobs/robot_run_job.rb +0 -25
  162. data/examples/18_rails/app/models/application_record.rb +0 -5
  163. data/examples/18_rails/app/models/robot_lab_result.rb +0 -36
  164. data/examples/18_rails/app/models/robot_lab_thread.rb +0 -23
  165. data/examples/18_rails/app/robots/chat_robot.rb +0 -25
  166. data/examples/18_rails/app/tools/time_tool.rb +0 -9
  167. data/examples/18_rails/app/views/chat/_user_message.html.erb +0 -1
  168. data/examples/18_rails/app/views/chat/index.html.erb +0 -67
  169. data/examples/18_rails/app/views/layouts/application.html.erb +0 -49
  170. data/examples/18_rails/bin/dev +0 -7
  171. data/examples/18_rails/bin/rails +0 -6
  172. data/examples/18_rails/bin/setup +0 -15
  173. data/examples/18_rails/config/application.rb +0 -33
  174. data/examples/18_rails/config/cable.yml +0 -2
  175. data/examples/18_rails/config/database.yml +0 -5
  176. data/examples/18_rails/config/environment.rb +0 -4
  177. data/examples/18_rails/config/initializers/robot_lab.rb +0 -11
  178. data/examples/18_rails/config/routes.rb +0 -6
  179. data/examples/18_rails/config.ru +0 -4
  180. data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +0 -32
  181. data/examples/19_token_tracking.rb +0 -138
  182. data/examples/20_circuit_breaker.rb +0 -147
  183. data/examples/21_learning_loop.rb +0 -181
  184. data/examples/22_context_compression.rb +0 -172
  185. data/examples/23_convergence.rb +0 -133
  186. data/examples/24_structured_delegation.rb +0 -151
  187. data/examples/25_history_search/conversation.jsonl +0 -30
  188. data/examples/25_history_search.rb +0 -133
  189. data/examples/26_document_store/api_versioning_adr.md +0 -52
  190. data/examples/26_document_store/incident_postmortem.md +0 -46
  191. data/examples/26_document_store/postgres_runbook.md +0 -49
  192. data/examples/26_document_store/redis_caching_guide.md +0 -48
  193. data/examples/26_document_store/sidekiq_guide.md +0 -51
  194. data/examples/26_document_store.rb +0 -140
  195. data/examples/27_incident_response/incident_response.rb +0 -261
  196. data/examples/28_mcp_discovery.rb +0 -113
  197. data/examples/29_ractor_tools.rb +0 -240
  198. data/examples/30_ractor_network.rb +0 -252
  199. data/examples/31_launch_assessment.rb +0 -246
  200. data/examples/32_newsletter_reader.rb +0 -193
  201. data/examples/33_stock_generator.rb +0 -80
  202. data/examples/33_stock_predictor.rb +0 -327
  203. data/examples/34_agentskills.rb +0 -76
  204. data/examples/35_hooks.rb +0 -266
  205. data/examples/README.md +0 -533
  206. data/examples/common.rb +0 -148
  207. data/examples/prompts/assistant.md +0 -5
  208. data/examples/prompts/audit_trail.md +0 -8
  209. data/examples/prompts/billing.md +0 -10
  210. data/examples/prompts/classifier.md +0 -7
  211. data/examples/prompts/comedian.md +0 -6
  212. data/examples/prompts/comedy_critic.md +0 -10
  213. data/examples/prompts/configurable.md +0 -9
  214. data/examples/prompts/dispatcher.md +0 -12
  215. data/examples/prompts/entity_extractor.md +0 -14
  216. data/examples/prompts/escalation.md +0 -42
  217. data/examples/prompts/frontmatter_mcp_test.md +0 -9
  218. data/examples/prompts/frontmatter_named_test.md +0 -5
  219. data/examples/prompts/frontmatter_tools_test.md +0 -6
  220. data/examples/prompts/general.md +0 -7
  221. data/examples/prompts/github_assistant.md +0 -14
  222. data/examples/prompts/helper.md +0 -4
  223. data/examples/prompts/incident_responder.md +0 -18
  224. data/examples/prompts/keyword_extractor.md +0 -11
  225. data/examples/prompts/llm_config_demo.md +0 -20
  226. data/examples/prompts/order_support.md +0 -35
  227. data/examples/prompts/os_advocate.md +0 -13
  228. data/examples/prompts/os_chief.md +0 -13
  229. data/examples/prompts/os_editor.md +0 -13
  230. data/examples/prompts/parameterized_main_test.md +0 -6
  231. data/examples/prompts/pii_redactor.md +0 -7
  232. data/examples/prompts/product_support.md +0 -37
  233. data/examples/prompts/runbook_protocol.md +0 -12
  234. data/examples/prompts/sentiment_analyzer.md +0 -12
  235. data/examples/prompts/skill_a_test.md +0 -4
  236. data/examples/prompts/skill_b_test.md +0 -4
  237. data/examples/prompts/skill_config_test.md +0 -5
  238. data/examples/prompts/skill_cycle_a_test.md +0 -6
  239. data/examples/prompts/skill_cycle_b_test.md +0 -6
  240. data/examples/prompts/skill_description_test.md +0 -4
  241. data/examples/prompts/skill_leaf_test.md +0 -4
  242. data/examples/prompts/skill_nested_test.md +0 -6
  243. data/examples/prompts/skill_refs_main_test.md +0 -6
  244. data/examples/prompts/skill_self_ref_test.md +0 -6
  245. data/examples/prompts/skill_with_mcp_test.md +0 -9
  246. data/examples/prompts/skill_with_params_test.md +0 -6
  247. data/examples/prompts/skill_with_robot_name_test.md +0 -5
  248. data/examples/prompts/skill_with_tools_test.md +0 -6
  249. data/examples/prompts/sre_compliance.md +0 -10
  250. data/examples/prompts/structured_output.md +0 -7
  251. data/examples/prompts/synthesizer.md +0 -17
  252. data/examples/prompts/technical.md +0 -10
  253. data/examples/prompts/template_with_skills_test.md +0 -6
  254. data/examples/prompts/triage.md +0 -22
  255. data/examples/ruboruby.md +0 -423
  256. data/examples/xyzzy.rb +0 -97
  257. data/logfile +0 -8
  258. data/mkdocs.yml +0 -209
  259. data/robot_concurrency.md +0 -38
  260. data/simple_acp_review.md +0 -298
  261. data/temp.md +0 -6
  262. data/tool_manifest_plan.md +0 -155
@@ -1,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