robot_lab 0.3.0 → 0.5.0

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