robot_lab 0.1.0 → 0.2.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 (242) hide show
  1. checksums.yaml +4 -4
  2. data/.architecture/AGENTS.md +32 -0
  3. data/.architecture/config.yml +8 -0
  4. data/.architecture/members.yml +60 -0
  5. data/.architecture/reviews/feature-free-will.md +490 -0
  6. data/.architecture/reviews/overall-codebase.md +427 -0
  7. data/.claude/settings.local.json +57 -0
  8. data/.codex/config.toml +2 -0
  9. data/.irbrc +2 -2
  10. data/.rubocop.yml +172 -0
  11. data/CHANGELOG.md +72 -0
  12. data/CLAUDE.md +139 -0
  13. data/README.md +91 -95
  14. data/Rakefile +109 -3
  15. data/agent2agent_review.md +192 -0
  16. data/agentf_improvements.md +253 -0
  17. data/agents.md +14 -0
  18. data/docs/examples/index.md +37 -2
  19. data/docs/getting-started/configuration.md +20 -7
  20. data/docs/guides/index.md +16 -16
  21. data/docs/guides/knowledge.md +7 -1
  22. data/docs/guides/observability.md +132 -0
  23. data/docs/index.md +30 -3
  24. data/docs/superpowers/plans/2026-05-06-agentskills.md +1303 -0
  25. data/docs/superpowers/specs/2026-05-06-agentskills-design.md +247 -0
  26. data/examples/.envrc +1 -0
  27. data/examples/01_simple_robot.rb +5 -9
  28. data/examples/02_tools.rb +5 -9
  29. data/examples/03_network.rb +8 -9
  30. data/examples/04_mcp.rb +21 -29
  31. data/examples/05_streaming.rb +12 -18
  32. data/examples/06_prompt_templates.rb +11 -19
  33. data/examples/07_network_memory.rb +16 -31
  34. data/examples/08_llm_config.rb +10 -22
  35. data/examples/09_chaining.rb +16 -27
  36. data/examples/10_memory.rb +12 -28
  37. data/examples/11_network_introspection.rb +15 -29
  38. data/examples/12_message_bus.rb +5 -12
  39. data/examples/13_spawn.rb +5 -10
  40. data/examples/14_rusty_circuit/.envrc +1 -0
  41. data/examples/14_rusty_circuit/comic.rb +2 -0
  42. data/examples/14_rusty_circuit/heckler.rb +1 -1
  43. data/examples/14_rusty_circuit/open_mic.rb +1 -3
  44. data/examples/14_rusty_circuit/scout.rb +2 -0
  45. data/examples/15_memory_network_and_bus/.envrc +1 -0
  46. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +6 -3
  47. data/examples/15_memory_network_and_bus/linux_writer.rb +1 -1
  48. data/examples/15_memory_network_and_bus/output/combined_article.md +6 -6
  49. data/examples/15_memory_network_and_bus/output/final_article.md +6 -8
  50. data/examples/15_memory_network_and_bus/output/linux_draft.md +4 -2
  51. data/examples/15_memory_network_and_bus/output/mac_draft.md +3 -3
  52. data/examples/15_memory_network_and_bus/output/memory.json +6 -6
  53. data/examples/15_memory_network_and_bus/output/revision_1.md +10 -11
  54. data/examples/15_memory_network_and_bus/output/revision_2.md +6 -8
  55. data/examples/15_memory_network_and_bus/output/windows_draft.md +3 -3
  56. data/examples/16_writers_room/.envrc +1 -0
  57. data/examples/16_writers_room/writers_room.rb +2 -4
  58. data/examples/17_skills.rb +8 -17
  59. data/examples/18_rails/Gemfile +1 -0
  60. data/examples/19_token_tracking.rb +9 -15
  61. data/examples/20_circuit_breaker.rb +10 -19
  62. data/examples/21_learning_loop.rb +11 -20
  63. data/examples/22_context_compression.rb +6 -13
  64. data/examples/23_convergence.rb +6 -17
  65. data/examples/24_structured_delegation.rb +11 -15
  66. data/examples/25_history_search.rb +5 -12
  67. data/examples/26_document_store.rb +6 -13
  68. data/examples/27_incident_response/incident_response.rb +4 -5
  69. data/examples/28_mcp_discovery.rb +8 -11
  70. data/examples/29_ractor_tools.rb +4 -9
  71. data/examples/30_ractor_network.rb +10 -19
  72. data/examples/31_launch_assessment.rb +10 -23
  73. data/examples/32_newsletter_reader.rb +188 -0
  74. data/examples/33_stock_generator.rb +80 -0
  75. data/examples/33_stock_predictor.rb +306 -0
  76. data/examples/34_agentskills.rb +72 -0
  77. data/examples/README.md +1 -1
  78. data/examples/common.rb +76 -0
  79. data/examples/ruboruby.md +423 -0
  80. data/examples/temp.md +51 -0
  81. data/lib/robot_lab/agent_skill.rb +63 -0
  82. data/lib/robot_lab/agent_skill_catalog.rb +74 -0
  83. data/lib/robot_lab/ask_user.rb +2 -2
  84. data/lib/robot_lab/bus_poller.rb +12 -5
  85. data/lib/robot_lab/config.rb +1 -12
  86. data/lib/robot_lab/delegation_future.rb +1 -1
  87. data/lib/robot_lab/doom_loop_detector.rb +98 -0
  88. data/lib/robot_lab/history_compressor.rb +4 -10
  89. data/lib/robot_lab/mcp/client.rb +1 -2
  90. data/lib/robot_lab/mcp/connection_poller.rb +3 -3
  91. data/lib/robot_lab/mcp/server.rb +1 -1
  92. data/lib/robot_lab/mcp/server_discovery.rb +0 -2
  93. data/lib/robot_lab/memory.rb +32 -27
  94. data/lib/robot_lab/memory_change.rb +2 -2
  95. data/lib/robot_lab/message.rb +4 -4
  96. data/lib/robot_lab/network.rb +11 -6
  97. data/lib/robot_lab/robot/agent_skill_matching.rb +99 -0
  98. data/lib/robot_lab/robot/bus_messaging.rb +9 -27
  99. data/lib/robot_lab/robot/history_search.rb +4 -1
  100. data/lib/robot_lab/robot/mcp_management.rb +5 -11
  101. data/lib/robot_lab/robot/template_rendering.rb +60 -40
  102. data/lib/robot_lab/robot.rb +323 -206
  103. data/lib/robot_lab/robot_result.rb +6 -5
  104. data/lib/robot_lab/run_config.rb +5 -11
  105. data/lib/robot_lab/script_tool.rb +76 -0
  106. data/lib/robot_lab/state_proxy.rb +7 -5
  107. data/lib/robot_lab/tool.rb +3 -3
  108. data/lib/robot_lab/tool_config.rb +1 -1
  109. data/lib/robot_lab/tool_manifest.rb +5 -7
  110. data/lib/robot_lab/user_message.rb +2 -2
  111. data/lib/robot_lab/version.rb +1 -1
  112. data/lib/robot_lab/waiter.rb +1 -1
  113. data/lib/robot_lab.rb +41 -52
  114. data/logfile +8 -0
  115. data/mkdocs.yml +2 -3
  116. data/robot_concurrency.md +38 -0
  117. data/simple_acp_review.md +298 -0
  118. data/site/404.html +2300 -0
  119. data/site/api/core/index.html +2706 -0
  120. data/site/api/core/memory/index.html +3793 -0
  121. data/site/api/core/network/index.html +3500 -0
  122. data/site/api/core/robot/index.html +4566 -0
  123. data/site/api/core/state/index.html +3390 -0
  124. data/site/api/core/tool/index.html +3843 -0
  125. data/site/api/index.html +2635 -0
  126. data/site/api/mcp/client/index.html +3435 -0
  127. data/site/api/mcp/index.html +2783 -0
  128. data/site/api/mcp/server/index.html +3252 -0
  129. data/site/api/mcp/transports/index.html +3352 -0
  130. data/site/api/messages/index.html +2641 -0
  131. data/site/api/messages/text-message/index.html +3087 -0
  132. data/site/api/messages/tool-call-message/index.html +3159 -0
  133. data/site/api/messages/tool-result-message/index.html +3252 -0
  134. data/site/api/messages/user-message/index.html +3212 -0
  135. data/site/api/streaming/context/index.html +3282 -0
  136. data/site/api/streaming/events/index.html +3347 -0
  137. data/site/api/streaming/index.html +2738 -0
  138. data/site/architecture/core-concepts/index.html +3757 -0
  139. data/site/architecture/index.html +2797 -0
  140. data/site/architecture/message-flow/index.html +3238 -0
  141. data/site/architecture/network-orchestration/index.html +3433 -0
  142. data/site/architecture/robot-execution/index.html +3140 -0
  143. data/site/architecture/state-management/index.html +3498 -0
  144. data/site/assets/css/custom.css +56 -0
  145. data/site/assets/images/favicon.png +0 -0
  146. data/site/assets/images/robot_lab.jpg +0 -0
  147. data/site/assets/javascripts/bundle.79ae519e.min.js +16 -0
  148. data/site/assets/javascripts/bundle.79ae519e.min.js.map +7 -0
  149. data/site/assets/javascripts/lunr/min/lunr.ar.min.js +1 -0
  150. data/site/assets/javascripts/lunr/min/lunr.da.min.js +18 -0
  151. data/site/assets/javascripts/lunr/min/lunr.de.min.js +18 -0
  152. data/site/assets/javascripts/lunr/min/lunr.du.min.js +18 -0
  153. data/site/assets/javascripts/lunr/min/lunr.el.min.js +1 -0
  154. data/site/assets/javascripts/lunr/min/lunr.es.min.js +18 -0
  155. data/site/assets/javascripts/lunr/min/lunr.fi.min.js +18 -0
  156. data/site/assets/javascripts/lunr/min/lunr.fr.min.js +18 -0
  157. data/site/assets/javascripts/lunr/min/lunr.he.min.js +1 -0
  158. data/site/assets/javascripts/lunr/min/lunr.hi.min.js +1 -0
  159. data/site/assets/javascripts/lunr/min/lunr.hu.min.js +18 -0
  160. data/site/assets/javascripts/lunr/min/lunr.hy.min.js +1 -0
  161. data/site/assets/javascripts/lunr/min/lunr.it.min.js +18 -0
  162. data/site/assets/javascripts/lunr/min/lunr.ja.min.js +1 -0
  163. data/site/assets/javascripts/lunr/min/lunr.jp.min.js +1 -0
  164. data/site/assets/javascripts/lunr/min/lunr.kn.min.js +1 -0
  165. data/site/assets/javascripts/lunr/min/lunr.ko.min.js +1 -0
  166. data/site/assets/javascripts/lunr/min/lunr.multi.min.js +1 -0
  167. data/site/assets/javascripts/lunr/min/lunr.nl.min.js +18 -0
  168. data/site/assets/javascripts/lunr/min/lunr.no.min.js +18 -0
  169. data/site/assets/javascripts/lunr/min/lunr.pt.min.js +18 -0
  170. data/site/assets/javascripts/lunr/min/lunr.ro.min.js +18 -0
  171. data/site/assets/javascripts/lunr/min/lunr.ru.min.js +18 -0
  172. data/site/assets/javascripts/lunr/min/lunr.sa.min.js +1 -0
  173. data/site/assets/javascripts/lunr/min/lunr.stemmer.support.min.js +1 -0
  174. data/site/assets/javascripts/lunr/min/lunr.sv.min.js +18 -0
  175. data/site/assets/javascripts/lunr/min/lunr.ta.min.js +1 -0
  176. data/site/assets/javascripts/lunr/min/lunr.te.min.js +1 -0
  177. data/site/assets/javascripts/lunr/min/lunr.th.min.js +1 -0
  178. data/site/assets/javascripts/lunr/min/lunr.tr.min.js +18 -0
  179. data/site/assets/javascripts/lunr/min/lunr.vi.min.js +1 -0
  180. data/site/assets/javascripts/lunr/min/lunr.zh.min.js +1 -0
  181. data/site/assets/javascripts/lunr/tinyseg.js +206 -0
  182. data/site/assets/javascripts/lunr/wordcut.js +6708 -0
  183. data/site/assets/javascripts/workers/search.2c215733.min.js +42 -0
  184. data/site/assets/javascripts/workers/search.2c215733.min.js.map +7 -0
  185. data/site/assets/stylesheets/main.484c7ddc.min.css +1 -0
  186. data/site/assets/stylesheets/main.484c7ddc.min.css.map +1 -0
  187. data/site/assets/stylesheets/palette.ab4e12ef.min.css +1 -0
  188. data/site/assets/stylesheets/palette.ab4e12ef.min.css.map +1 -0
  189. data/site/concepts/index.html +3455 -0
  190. data/site/examples/basic-chat/index.html +2880 -0
  191. data/site/examples/index.html +2907 -0
  192. data/site/examples/mcp-server/index.html +3018 -0
  193. data/site/examples/multi-robot-network/index.html +3131 -0
  194. data/site/examples/rails-application/index.html +3329 -0
  195. data/site/examples/tool-usage/index.html +3085 -0
  196. data/site/getting-started/configuration/index.html +3745 -0
  197. data/site/getting-started/index.html +2572 -0
  198. data/site/getting-started/installation/index.html +2981 -0
  199. data/site/getting-started/quick-start/index.html +2942 -0
  200. data/site/guides/building-robots/index.html +4290 -0
  201. data/site/guides/creating-networks/index.html +3858 -0
  202. data/site/guides/index.html +2586 -0
  203. data/site/guides/mcp-integration/index.html +3581 -0
  204. data/site/guides/memory/index.html +3586 -0
  205. data/site/guides/rails-integration/index.html +4019 -0
  206. data/site/guides/streaming/index.html +3157 -0
  207. data/site/guides/using-tools/index.html +3802 -0
  208. data/site/index.html +2671 -0
  209. data/site/search/search_index.json +1 -0
  210. data/site/sitemap.xml +183 -0
  211. data/site/sitemap.xml.gz +0 -0
  212. data/site/tags.json +1 -0
  213. data/temp.md +6 -0
  214. data/tool_manifest_plan.md +155 -0
  215. metadata +154 -92
  216. data/docs/examples/rails-application.md +0 -419
  217. data/docs/guides/ractor-parallelism.md +0 -364
  218. data/docs/guides/rails-integration.md +0 -681
  219. data/docs/superpowers/plans/2026-04-14-ractor-integration.md +0 -1538
  220. data/docs/superpowers/specs/2026-04-14-ractor-integration-design.md +0 -258
  221. data/lib/generators/robot_lab/install_generator.rb +0 -90
  222. data/lib/generators/robot_lab/job_generator.rb +0 -40
  223. data/lib/generators/robot_lab/robot_generator.rb +0 -55
  224. data/lib/generators/robot_lab/templates/initializer.rb.tt +0 -42
  225. data/lib/generators/robot_lab/templates/job.rb.tt +0 -21
  226. data/lib/generators/robot_lab/templates/migration.rb.tt +0 -32
  227. data/lib/generators/robot_lab/templates/result_model.rb.tt +0 -52
  228. data/lib/generators/robot_lab/templates/robot.rb.tt +0 -31
  229. data/lib/generators/robot_lab/templates/robot_job.rb.tt +0 -18
  230. data/lib/generators/robot_lab/templates/robot_test.rb.tt +0 -34
  231. data/lib/generators/robot_lab/templates/routing_robot.rb.tt +0 -59
  232. data/lib/generators/robot_lab/templates/thread_model.rb.tt +0 -40
  233. data/lib/robot_lab/document_store.rb +0 -155
  234. data/lib/robot_lab/ractor_boundary.rb +0 -42
  235. data/lib/robot_lab/ractor_job.rb +0 -37
  236. data/lib/robot_lab/ractor_memory_proxy.rb +0 -85
  237. data/lib/robot_lab/ractor_network_scheduler.rb +0 -154
  238. data/lib/robot_lab/ractor_worker_pool.rb +0 -117
  239. data/lib/robot_lab/rails_integration/engine.rb +0 -29
  240. data/lib/robot_lab/rails_integration/job.rb +0 -158
  241. data/lib/robot_lab/rails_integration/railtie.rb +0 -51
  242. data/lib/robot_lab/rails_integration/turbo_stream_callbacks.rb +0 -72
@@ -1,7 +1,7 @@
1
1
  # win_writer Draft
2
2
 
3
- **Windows: The Ultimate Platform for Your Home AI Research Lab**
3
+ Windows is the strongest all-around choice for a home AI research lab because it gives you the best combination of raw GPU support, hardware flexibility, and practical developer workflow. For LLM fine-tuning, image generation, and local inference, NVIDIA CUDA support on Windows is first-class: PyTorch, TensorFlow, xFormers, bitsandbytes, TensorRT, ONNX Runtime, and popular tools like Ollama, LM Studio, ComfyUI, AUTOMATIC1111, and Stable Diffusion WebUI all have strong Windows paths. If you want the highest-performance consumer GPUs for AI work, Windows pairs especially well with NVIDIA RTX hardware, from a single 4070 Ti Super up to multi-GPU prosumer setups. And if you need Linux-native scripts or package workflows, WSL2 gives you a near-seamless Ubuntu environment on the same machine, so you can run Hugging Face Transformers, PEFT, DeepSpeed, vLLM, llama.cpp, JupyterLab, and CUDA-enabled Linux tooling without giving up native Windows apps.
4
4
 
5
- When building a home AI research lab focused on LLM fine-tuning, image generation, and local inference, Windows stands as the unequivocally superior choice. The platform's **first-class NVIDIA CUDA support** provides seamless access to the full GPU acceleration stack that modern AI workloads demand. Unlike other operating systems that often require complex driver configurations or suffer from compatibility issues, Windows delivers out-of-the-box support for CUDA 12.x, cuDNN, and TensorRT—essential for frameworks like PyTorch, TensorFlow, and JAX that power cutting-edge models like Llama 2, Stable Diffusion XL, and Mistral. Microsoft's **DirectML** further enhances this ecosystem by enabling hardware-agnostic ML acceleration across AMD, Intel, and NVIDIA GPUs, giving you unparalleled flexibility in hardware selection. With **WSL2**, you get native Linux compatibility without sacrificing Windows' superior hardware support, allowing you to run containerized models via Docker or Podman while maintaining access to Windows-exclusive tools.
5
+ Windows also wins on hardware compatibility and ecosystem breadth. A home lab often mixes gaming-class GPUs, high-core-count CPUs, fast NVMe storage, USB devices, capture cards, and sometimes unusual motherboard or networking gear; Windows consistently offers the widest driver support and easiest setup across that mix. Beyond CUDA, Windows also supports DirectML, which broadens acceleration options on AMD and Intel hardware for local inference and experimentation. On the tooling side, it integrates cleanly with Visual Studio Code, Visual Studio, Docker Desktop, PowerShell, Windows Terminal, Git for Windows, and enterprise-friendly tools for remote access, file sharing, backup, and virtualization. In practice, that means you can fine-tune a LoRA in WSL2, monitor GPUs with native Windows utilities, manage datasets on NTFS storage, launch ComfyUI or LM Studio from the desktop, and still have access to familiar scripting and automation.
6
6
 
7
- The Windows ecosystem excels in providing the **widest hardware compatibility** and vendor support in the industry. Whether you're deploying multiple RTX 4090s for distributed training, experimenting with Intel Arc GPUs for cost-effective inference, or scaling up to workstation-class Quadro cards, Windows ensures optimal driver support and performance tuning. The platform's enterprise-grade tooling integration shines for serious researchers: **Visual Studio Code** with GPU debugging extensions, **Weights & Biases** desktop integration, **Anaconda** with native Windows conda-forge packages, and seamless **Git LFS** support for managing large model checkpoints. Popular frameworks like **Hugging Face Transformers**, **ComfyUI** for Stable Diffusion workflows, and **text-generation-webui** for local LLM serving all provide superior Windows support with pre-compiled binaries and automated installers. The vibrant Windows AI community, centered around platforms like the AUTOMATIC1111 ecosystem and LM Studio, offers extensive documentation, troubleshooting resources, and model optimization guides specifically tailored to Windows environments—making your research journey both productive and well-supported.
7
+ Just as important, Windows has massive community support for home AI builders. Most consumer AI guides now assume either native Windows or Windows plus WSL2, especially for Stable Diffusion workflows, local LLM front ends, and single-node fine-tuning recipes. Troubleshooting is easier when there are abundant guides for CUDA driver versions, PyTorch wheels, Flash Attention builds, and model-serving stacks on common RTX cards. For someone building a serious home AI lab, Windows offers the most practical balance: top-tier NVIDIA performance, Linux compatibility when you need it, broad hardware support, and a mature tool ecosystem that scales from casual experimentation to disciplined research workflows.
@@ -0,0 +1 @@
1
+ export ROBOT_LAB_TEMPLATE_PATH="${PWD}/prompts"
@@ -35,14 +35,12 @@
35
35
  ENV["ROBOT_LAB_TEMPLATE_PATH"] ||= File.join(__dir__, "prompts")
36
36
 
37
37
  require "json"
38
- require_relative "../../lib/robot_lab"
38
+ require_relative "../common"
39
39
  require_relative "display"
40
40
  require_relative "tools"
41
41
  require_relative "room"
42
42
  require_relative "writer"
43
43
 
44
- RubyLLM.configure { |c| c.logger = Logger.new(File::NULL) }
45
-
46
44
  # ── Mode Descriptors ────────────────────────────────────────
47
45
 
48
46
  BOOK_MODE = {
@@ -128,7 +126,7 @@ FileUtils.mkdir_p(OUTPUT_DIR)
128
126
  display = Display.new(log_path: log_path)
129
127
 
130
128
  shared_config = RobotLab::RunConfig.new(
131
- model: "claude-sonnet-4-5-20250929",
129
+ model: LLM[:default].model,
132
130
  temperature: 0.7
133
131
  )
134
132
 
@@ -40,10 +40,7 @@
40
40
  # ├── pii_redactor.md — Leaf skill: redact PII
41
41
  # └── audit_trail.md — Leaf skill: audit metadata in every response
42
42
 
43
- # Configure template path before loading (MywayConfig reads env vars on init)
44
- ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
45
-
46
- require_relative "../lib/robot_lab"
43
+ require_relative "common"
47
44
 
48
45
  # Send logger output to a file instead of stdout
49
46
  require 'logger'
@@ -120,10 +117,7 @@ ALERTS = [
120
117
  # Build the Incident Responder with Composable Skills
121
118
  # =============================================================================
122
119
 
123
- puts "=" * 70
124
- puts "RobotLab Skills Demo — SRE Incident Response System"
125
- puts "=" * 70
126
- puts
120
+ banner "RobotLab Skills Demo — SRE Incident Response System"
127
121
 
128
122
  # Show the skill composition
129
123
  puts "Skill composition:"
@@ -142,6 +136,7 @@ puts
142
136
  # - sre_compliance: Recursive skill — auto-expands to include pii_redactor
143
137
  # and audit_trail before its own compliance instructions
144
138
  responder = RobotLab.build(
139
+ model: LLM[:default].model,
145
140
  name: "incident_responder",
146
141
  template: :incident_responder,
147
142
  skills: [:runbook_protocol, :structured_output, :sre_compliance],
@@ -165,13 +160,13 @@ messages = chat.instance_variable_get(:@messages)
165
160
  system_msg = messages.find { |m| m.role.to_s == "system" }
166
161
  if system_msg
167
162
  prompt = system_msg.content
168
- puts "-" * 70
163
+ hr
169
164
  puts "Assembled system prompt (#{prompt.length} chars, #{prompt.lines.count} lines):"
170
- puts "-" * 70
165
+ hr
171
166
  prompt.lines.each_with_index do |line, i|
172
167
  puts " %3d %s" % [i + 1, line]
173
168
  end
174
- puts "-" * 70
169
+ hr
175
170
  end
176
171
 
177
172
  # =============================================================================
@@ -179,6 +174,7 @@ end
179
174
  # =============================================================================
180
175
 
181
176
  plain_responder = RobotLab.build(
177
+ model: LLM[:default].model,
182
178
  name: "plain_responder",
183
179
  template: :incident_responder,
184
180
  context: {
@@ -202,11 +198,7 @@ end
202
198
  # =============================================================================
203
199
 
204
200
  ALERTS.each_with_index do |alert, index|
205
- puts
206
- puts "=" * 70
207
- puts "Incident #{index + 1}: #{alert[:label]}"
208
- puts "=" * 70
209
- puts
201
+ section "Incident #{index + 1}: #{alert[:label]}"
210
202
 
211
203
  result = responder.run(alert[:data])
212
204
  content = result.output.first&.content.to_s
@@ -217,7 +209,6 @@ ALERTS.each_with_index do |alert, index|
217
209
  puts
218
210
  end
219
211
 
220
- puts "=" * 70
221
212
  puts
222
213
  puts <<~FOOTER
223
214
  This example demonstrates:
@@ -8,3 +8,4 @@ gem "puma", "~> 6.0"
8
8
  gem "turbo-rails", "~> 2.0"
9
9
 
10
10
  gem "robot_lab", path: "../.."
11
+ gem "robot_lab-rails", path: "../../../robot_lab-rails"
@@ -20,9 +20,7 @@
20
20
  # Usage:
21
21
  # ANTHROPIC_API_KEY=your_key ruby examples/19_token_tracking.rb
22
22
 
23
- ENV["ROBOT_LAB_TEMPLATE_PATH"] ||= File.join(__dir__, "prompts")
24
-
25
- require_relative "../lib/robot_lab"
23
+ require_relative "common"
26
24
 
27
25
  # Anthropic claude-haiku-4-5 pricing (as of early 2026)
28
26
  HAIKU_INPUT_CPM = 0.80 # $ per 1M input tokens
@@ -41,15 +39,12 @@ def first_line(text)
41
39
  text&.strip&.lines&.first&.strip || ""
42
40
  end
43
41
 
44
- puts "=" * 60
45
- puts "Example 19: Per-Robot Token & Cost Tracking"
46
- puts "=" * 60
47
- puts
42
+ banner "Per-Robot Token & Cost Tracking"
48
43
 
49
44
  robot = RobotLab.build(
45
+ model: LLM[:default].model,
50
46
  name: "analyst",
51
- system_prompt: "You are a concise technical analyst. Keep every reply under 40 words.",
52
- model: "claude-haiku-4-5-20251001"
47
+ system_prompt: "You are a concise technical analyst. Keep every reply under 40 words."
53
48
  )
54
49
 
55
50
  prompts = [
@@ -71,7 +66,7 @@ prompts.each_with_index do |prompt, i|
71
66
  puts
72
67
  end
73
68
 
74
- puts "-" * 60
69
+ hr
75
70
  puts "After #{prompts.size} runs:"
76
71
  puts " Total tokens: #{token_summary(robot.total_input_tokens, robot.total_output_tokens)}"
77
72
  puts " Total cost: #{run_cost(robot.total_input_tokens, robot.total_output_tokens)}"
@@ -106,14 +101,14 @@ puts
106
101
  # To start truly fresh (new context, new counter), build a new robot.
107
102
  # ---------------------------------------------------------------
108
103
 
109
- puts "-" * 60
104
+ hr
110
105
  puts "Fresh robot — genuinely zero context:"
111
106
  puts
112
107
 
113
108
  fresh = RobotLab.build(
109
+ model: LLM[:default].model,
114
110
  name: "analyst2",
115
- system_prompt: "You are a concise technical analyst. Keep every reply under 40 words.",
116
- model: "claude-haiku-4-5-20251001"
111
+ system_prompt: "You are a concise technical analyst. Keep every reply under 40 words."
117
112
  )
118
113
 
119
114
  result = fresh.run("What is memoization?")
@@ -123,6 +118,5 @@ puts " This run: #{token_summary(result.input_tokens, result.output_tokens)}"
123
118
  puts " Cumulative: #{token_summary(fresh.total_input_tokens, fresh.total_output_tokens)}"
124
119
  puts
125
120
 
126
- puts "=" * 60
121
+ hr
127
122
  puts "Token tracking demo complete."
128
- puts "=" * 60
@@ -25,9 +25,7 @@
25
25
  # Usage:
26
26
  # ANTHROPIC_API_KEY=your_key ruby examples/20_circuit_breaker.rb
27
27
 
28
- ENV["ROBOT_LAB_TEMPLATE_PATH"] ||= File.join(__dir__, "prompts")
29
-
30
- require_relative "../lib/robot_lab"
28
+ require_relative "common"
31
29
 
32
30
  # -------------------------------------------------------------------------
33
31
  # A tool that always says "more steps remain" — designed to induce looping
@@ -62,10 +60,7 @@ class MultiStepProcessor < RubyLLM::Tool
62
60
  end
63
61
  end
64
62
 
65
- puts "=" * 60
66
- puts "Example 20: Tool Loop Circuit Breaker"
67
- puts "=" * 60
68
- puts
63
+ banner "Tool Loop Circuit Breaker"
69
64
 
70
65
  TASK = "Run the batch process from step 1 using the MultiStepProcessor tool. " \
71
66
  "Execute every step sequentially until the process reports 'complete'."
@@ -78,15 +73,14 @@ TASK = "Run the batch process from step 1 using the MultiStepProcessor tool. " \
78
73
  # history. Call clear_messages to flush the broken context before reuse.
79
74
  # -------------------------------------------------------------------------
80
75
 
81
- puts "--- Part 1: Circuit breaker fires (max_tool_rounds: 5) ---"
82
- puts
76
+ section "Part 1: Circuit Breaker Fires (max_tool_rounds: 5)"
83
77
 
84
78
  robot = RobotLab.build(
79
+ model: LLM[:default].model,
85
80
  name: "process_runner",
86
81
  system_prompt: "You are a process runner. Execute tasks exactly as instructed.",
87
82
  local_tools: [MultiStepProcessor],
88
- max_tool_rounds: 5,
89
- model: "claude-haiku-4-5-20251001"
83
+ max_tool_rounds: 5
90
84
  )
91
85
 
92
86
  begin
@@ -106,8 +100,7 @@ puts
106
100
  # Only the conversation history is cleared — the robot is then reusable.
107
101
  # -------------------------------------------------------------------------
108
102
 
109
- puts "--- Part 2: Recover with clear_messages ---"
110
- puts
103
+ section "Part 2: Recover with clear_messages"
111
104
 
112
105
  robot.clear_messages
113
106
  puts "Chat flushed. Robot config (tools, max_tool_rounds) is unchanged."
@@ -125,8 +118,7 @@ puts
125
118
  # not a tax on well-behaved tool interactions.
126
119
  # -------------------------------------------------------------------------
127
120
 
128
- puts "--- Part 3: No circuit breakertask terminates naturally ---"
129
- puts
121
+ section "Part 3: No Circuit BreakerTask Terminates Naturally"
130
122
 
131
123
  class SingleStep < RubyLLM::Tool
132
124
  description "Doubles a number and returns the result immediately."
@@ -138,16 +130,15 @@ class SingleStep < RubyLLM::Tool
138
130
  end
139
131
 
140
132
  unguarded = RobotLab.build(
133
+ model: LLM[:default].model,
141
134
  name: "calculator",
142
135
  system_prompt: "Use the provided tool to answer questions.",
143
- local_tools: [SingleStep],
144
- model: "claude-haiku-4-5-20251001"
136
+ local_tools: [SingleStep]
145
137
  )
146
138
 
147
139
  result = unguarded.run("Double the number 21 using the tool.")
148
140
  puts "Result: #{result.reply&.strip}"
149
141
  puts
150
142
 
151
- puts "=" * 60
143
+ hr
152
144
  puts "Circuit breaker demo complete."
153
- puts "=" * 60
@@ -19,9 +19,7 @@
19
19
  # Usage:
20
20
  # ANTHROPIC_API_KEY=your_key ruby examples/21_learning_loop.rb
21
21
 
22
- ENV["ROBOT_LAB_TEMPLATE_PATH"] ||= File.join(__dir__, "prompts")
23
-
24
- require_relative "../lib/robot_lab"
22
+ require_relative "common"
25
23
 
26
24
  SNIPPETS = [
27
25
  {
@@ -66,20 +64,17 @@ SNIPPETS = [
66
64
  }
67
65
  ].freeze
68
66
 
69
- puts "=" * 65
70
- puts "Example 21: Learning Accumulation Loop"
71
- puts "=" * 65
72
- puts
67
+ banner "Learning Accumulation Loop"
73
68
 
74
69
  robot = RobotLab.build(
70
+ model: LLM[:default].model,
75
71
  name: "code_reviewer",
76
- system_prompt: <<~PROMPT,
72
+ system_prompt: <<~PROMPT
77
73
  You are a concise Ruby code reviewer. For each snippet:
78
74
  1. Identify the main issue (one sentence).
79
75
  2. Show the improved version (code block).
80
76
  Keep responses under 80 words total.
81
77
  PROMPT
82
- model: "claude-haiku-4-5-20251001"
83
78
  )
84
79
 
85
80
  SNIPPETS.each_with_index do |item, i|
@@ -88,7 +83,7 @@ SNIPPETS.each_with_index do |item, i|
88
83
  # ---------------------------------------------------------------
89
84
  # Show what learnings are active going into this run
90
85
  # ---------------------------------------------------------------
91
- puts "--- Run #{run_number} ---"
86
+ section "Run #{run_number}"
92
87
  if robot.learnings.empty?
93
88
  puts "Learnings: (none yet)"
94
89
  else
@@ -117,16 +112,14 @@ end
117
112
  # ---------------------------------------------------------------
118
113
  # Show the full accumulated learning list
119
114
  # ---------------------------------------------------------------
120
- puts "=" * 65
121
- puts "Accumulated learnings (#{robot.learnings.size} total):"
115
+ section "Accumulated Learnings (#{robot.learnings.size} total)"
122
116
  robot.learnings.each_with_index { |l, i| puts " #{i + 1}. #{l}" }
123
117
  puts
124
118
 
125
119
  # ---------------------------------------------------------------
126
120
  # Demonstrate superset dedup: a broader learning replaces narrower ones
127
121
  # ---------------------------------------------------------------
128
- puts "--- Deduplication demo ---"
129
- puts
122
+ section "Deduplication Demo"
130
123
  robot2 = RobotLab.build(name: "reviewer2", system_prompt: "You review code.")
131
124
 
132
125
  robot2.learn("avoid using puts")
@@ -140,14 +133,13 @@ puts
140
133
  # Demonstrate persistence: learnings survive a robot rebuild using
141
134
  # the same Memory object
142
135
  # ---------------------------------------------------------------
143
- puts "--- Persistence across rebuild ---"
144
- puts
136
+ section "Persistence Across Rebuild"
145
137
 
146
138
  shared_memory = robot.instance_variable_get(:@memory)
147
139
  rebuilt = RobotLab.build(
140
+ model: LLM[:default].model,
148
141
  name: "code_reviewer",
149
- system_prompt: "You review code.",
150
- model: "claude-haiku-4-5-20251001"
142
+ system_prompt: "You review code."
151
143
  )
152
144
  rebuilt.instance_variable_set(:@memory, shared_memory)
153
145
 
@@ -159,6 +151,5 @@ puts "Learnings on rebuilt robot (#{rebuilt.learnings.size}):"
159
151
  rebuilt.learnings.each_with_index { |l, i| puts " #{i + 1}. #{l}" }
160
152
  puts
161
153
 
162
- puts "=" * 65
154
+ hr
163
155
  puts "Learning loop demo complete."
164
- puts "=" * 65
@@ -22,9 +22,7 @@
22
22
  # Usage:
23
23
  # ANTHROPIC_API_KEY=your_key ruby examples/22_context_compression.rb
24
24
 
25
- ENV["ROBOT_LAB_TEMPLATE_PATH"] ||= File.join(__dir__, "prompts")
26
-
27
- require_relative "../lib/robot_lab"
25
+ require_relative "common"
28
26
 
29
27
  # ---------------------------------------------------------------------------
30
28
  # Check optional dependency
@@ -47,15 +45,13 @@ def approx_tokens(messages)
47
45
  end
48
46
  end
49
47
 
50
- puts "=" * 60
51
- puts "Example 22: Context Window Compression"
52
- puts "=" * 60
53
- puts
48
+ banner "Context Window Compression"
54
49
 
55
50
  # ---------------------------------------------------------------------------
56
51
  # Build a robot and simulate a long conversation on two topics
57
52
  # ---------------------------------------------------------------------------
58
53
  bot = RobotLab.build(
54
+ model: LLM[:default].model,
59
55
  name: "assistant",
60
56
  system_prompt: "You are a concise Ruby expert. Reply in 2-3 sentences."
61
57
  )
@@ -157,12 +153,10 @@ puts
157
153
  # ---------------------------------------------------------------------------
158
154
  # Show the LLM summarizer pattern (not executed — requires API key)
159
155
  # ---------------------------------------------------------------------------
160
- puts "=" * 60
161
- puts "LLM summarizer pattern (requires API key):"
162
- puts "=" * 60
163
- puts <<~RUBY
164
-
156
+ section "LLM Summarizer Pattern (requires API key)"
157
+ show_code <<~RUBY
165
158
  summarizer_bot = RobotLab.build(
159
+ model: "gpt-5.4",
166
160
  name: "summarizer",
167
161
  system_prompt: "Summarize the following text in one sentence."
168
162
  )
@@ -173,7 +167,6 @@ puts <<~RUBY
173
167
  drop_threshold: 0.2,
174
168
  summarizer: ->(text) { summarizer_bot.run("Summarize: \#{text}").reply }
175
169
  )
176
-
177
170
  RUBY
178
171
 
179
172
  puts "Done."
@@ -19,9 +19,7 @@
19
19
  # Usage:
20
20
  # ANTHROPIC_API_KEY=your_key ruby examples/23_convergence.rb
21
21
 
22
- ENV["ROBOT_LAB_TEMPLATE_PATH"] ||= File.join(__dir__, "prompts")
23
-
24
- require_relative "../lib/robot_lab"
22
+ require_relative "common"
25
23
 
26
24
  # ---------------------------------------------------------------------------
27
25
  # Check optional dependency
@@ -34,10 +32,7 @@ rescue LoadError
34
32
  exit 1
35
33
  end
36
34
 
37
- puts "=" * 60
38
- puts "Example 23: Debate Convergence Detection"
39
- puts "=" * 60
40
- puts
35
+ banner "Debate Convergence Detection"
41
36
 
42
37
  # ---------------------------------------------------------------------------
43
38
  # Similarity scoring
@@ -61,8 +56,7 @@ pairs = {
61
56
  ]
62
57
  }
63
58
 
64
- puts "Similarity scores:"
65
- puts "-" * 60
59
+ section "Similarity Scores"
66
60
  pairs.each do |label, (a, b)|
67
61
  score = RobotLab::Convergence.similarity(a, b)
68
62
  converged = RobotLab::Convergence.detected?(a, b, threshold: 0.6)
@@ -74,11 +68,8 @@ end
74
68
  # ---------------------------------------------------------------------------
75
69
  # Router fast-path pattern
76
70
  # ---------------------------------------------------------------------------
77
- puts "=" * 60
78
- puts "Router fast-path pattern"
79
- puts "=" * 60
80
- puts <<~RUBY
81
-
71
+ section "Router Fast-Path Pattern"
72
+ show_code <<~RUBY
82
73
  # Two verifier robots run in parallel and store their replies in shared memory.
83
74
  # The router checks convergence before dispatching to the expensive reconciler.
84
75
 
@@ -100,14 +91,12 @@ puts <<~RUBY
100
91
  )
101
92
 
102
93
  result = network.run(message: "Is this claim accurate?")
103
-
104
94
  RUBY
105
95
 
106
96
  # ---------------------------------------------------------------------------
107
97
  # Demonstrate with simulated verifier outputs
108
98
  # ---------------------------------------------------------------------------
109
- puts "Simulating verifier fast-path:"
110
- puts "-" * 60
99
+ section "Simulating Verifier Fast-Path"
111
100
 
112
101
  verifier_outputs = [
113
102
  {
@@ -20,29 +20,27 @@
20
20
  # Usage:
21
21
  # ANTHROPIC_API_KEY=your_key ruby examples/24_structured_delegation.rb
22
22
 
23
- ENV["ROBOT_LAB_TEMPLATE_PATH"] ||= File.join(__dir__, "prompts")
23
+ require_relative "common"
24
24
 
25
- require_relative "../lib/robot_lab"
26
-
27
- puts "=" * 60
28
- puts "Example 24: Structured Delegation"
29
- puts "=" * 60
30
- puts
25
+ banner "Structured Delegation"
31
26
 
32
27
  # ---------------------------------------------------------------------------
33
28
  # Build a manager and two specialist robots
34
29
  # ---------------------------------------------------------------------------
35
30
  manager = RobotLab.build(
31
+ model: LLM[:default].model,
36
32
  name: "manager",
37
33
  system_prompt: "You are a project manager. Delegate tasks concisely."
38
34
  )
39
35
 
40
36
  summarizer = RobotLab.build(
37
+ model: LLM[:default].model,
41
38
  name: "summarizer",
42
39
  system_prompt: "You are a concise summarizer. Produce a 1-2 sentence summary."
43
40
  )
44
41
 
45
42
  analyst = RobotLab.build(
43
+ model: LLM[:default].model,
46
44
  name: "analyst",
47
45
  system_prompt: "You are a data analyst. Identify the key metric in one sentence."
48
46
  )
@@ -59,13 +57,12 @@ TEXT
59
57
 
60
58
  puts "Document:"
61
59
  puts document
62
- puts "-" * 60
60
+ hr
63
61
 
64
62
  # ---------------------------------------------------------------------------
65
63
  # Synchronous delegation — sequential, blocks until each result arrives
66
64
  # ---------------------------------------------------------------------------
67
- puts "── Synchronous (sequential) ──────────────────────────────"
68
- puts
65
+ section "Synchronous (sequential)"
69
66
 
70
67
  puts "Delegating to summarizer (blocking)..."
71
68
  summary_result = manager.delegate(to: summarizer, task: "Summarize this report:\n\n#{document}")
@@ -88,15 +85,16 @@ puts
88
85
  # ---------------------------------------------------------------------------
89
86
  # Asynchronous delegation — parallel fan-out, results collected later
90
87
  # ---------------------------------------------------------------------------
91
- puts "── Asynchronous (parallel fan-out) ───────────────────────"
92
- puts
88
+ section "Asynchronous (parallel fan-out)"
93
89
 
94
90
  # Fresh robots — each delegate call should start from a clean slate
95
91
  async_summarizer = RobotLab.build(
92
+ model: LLM[:default].model,
96
93
  name: "summarizer",
97
94
  system_prompt: "You are a concise summarizer. Produce a 1-2 sentence summary."
98
95
  )
99
96
  async_analyst = RobotLab.build(
97
+ model: LLM[:default].model,
100
98
  name: "analyst",
101
99
  system_prompt: "You are a data analyst. Identify the key metric in one sentence."
102
100
  )
@@ -127,9 +125,7 @@ puts
127
125
  # ---------------------------------------------------------------------------
128
126
  # Contrast with the alternatives
129
127
  # ---------------------------------------------------------------------------
130
- puts "=" * 60
131
- puts "When to use delegate vs. the alternatives"
132
- puts "=" * 60
128
+ section "When to Use delegate vs. the Alternatives"
133
129
  puts <<~TEXT
134
130
 
135
131
  bus messaging — fire-and-forget; no return value; async
@@ -13,19 +13,14 @@
13
13
  # Usage:
14
14
  # ruby examples/25_history_search.rb
15
15
 
16
- ENV["ROBOT_LAB_TEMPLATE_PATH"] ||= File.join(__dir__, "prompts")
17
-
18
16
  require "json"
19
- require_relative "../lib/robot_lab"
17
+ require_relative "common"
20
18
 
21
19
  CONVERSATION_TURNS = File.readlines(
22
20
  File.join(__dir__, "25_history_search", "conversation.jsonl"), chomp: true
23
21
  ).map { |line| JSON.parse(line, symbolize_names: true) }.freeze
24
22
 
25
- puts "=" * 60
26
- puts "Example 25: Chat History Search"
27
- puts "=" * 60
28
- puts
23
+ banner "Chat History Search"
29
24
 
30
25
  # ---------------------------------------------------------------------------
31
26
  # Minimal message stub — populates history without LLM calls
@@ -35,7 +30,7 @@ FakeMsg = Struct.new(:role, :content, :tool_calls)
35
30
  # ---------------------------------------------------------------------------
36
31
  # Build a robot and inject the conversation fixture
37
32
  # ---------------------------------------------------------------------------
38
- robot = RobotLab.build(name: "tech_lead", system_prompt: "You are a senior engineering advisor.")
33
+ robot = RobotLab.build(model: LLM[:default].model, name: "tech_lead", system_prompt: "You are a senior engineering advisor.")
39
34
 
40
35
  messages = CONVERSATION_TURNS.map { |t| FakeMsg.new(t[:role], t[:content], nil) }
41
36
  robot.instance_variable_get(:@chat).instance_variable_set(:@messages, messages)
@@ -88,7 +83,7 @@ end
88
83
  # ---------------------------------------------------------------------------
89
84
  # RAG pattern — retrieve the most relevant turns, then inject as context
90
85
  # ---------------------------------------------------------------------------
91
- puts "── RAG pattern: retrieve context, then call LLM ────────────────"
86
+ section "RAG Pattern: Retrieve Context, Then Call LLM"
92
87
  puts "(showing retrieved context — no actual LLM call)"
93
88
  puts
94
89
 
@@ -111,9 +106,7 @@ puts
111
106
  # ---------------------------------------------------------------------------
112
107
  # When to use search_history
113
108
  # ---------------------------------------------------------------------------
114
- puts "=" * 60
115
- puts "When to use search_history"
116
- puts "=" * 60
109
+ section "When to Use search_history"
117
110
  puts <<~'TEXT'
118
111
 
119
112
  Without search_history:
@@ -13,14 +13,10 @@
13
13
  # ruby examples/26_document_store.rb
14
14
  # (Downloads the ~23 MB ONNX model on first run; cached afterwards.)
15
15
 
16
- ENV["ROBOT_LAB_TEMPLATE_PATH"] ||= File.join(__dir__, "prompts")
16
+ require_relative "common"
17
+ require "robot_lab/document_store"
17
18
 
18
- require_relative "../lib/robot_lab"
19
-
20
- puts "=" * 60
21
- puts "Example 26: Embedding-Based Document Store"
22
- puts "=" * 60
23
- puts
19
+ banner "Embedding-Based Document Store"
24
20
  puts "Note: First run downloads the fastembed model (~23 MB, cached)."
25
21
  puts
26
22
 
@@ -97,7 +93,7 @@ end
97
93
  # ---------------------------------------------------------------------------
98
94
  # Delete and verify
99
95
  # ---------------------------------------------------------------------------
100
- puts "── Delete :redis_caching_guide, re-run cache query"
96
+ section "Delete :redis_caching_guide, Re-run Cache Query"
101
97
  store.delete(:redis_caching_guide)
102
98
  results = store.search("Redis evicting keys unexpectedly", limit: 2)
103
99
  puts " Remaining keys: #{store.keys.inspect}"
@@ -107,7 +103,7 @@ puts
107
103
  # ---------------------------------------------------------------------------
108
104
  # Memory integration
109
105
  # ---------------------------------------------------------------------------
110
- puts "── Memory integration"
106
+ section "Memory Integration"
111
107
  memory = RobotLab::Memory.new(enable_cache: false)
112
108
 
113
109
  DOCUMENTS.each { |key, text| memory.store_document(key, text) }
@@ -124,10 +120,7 @@ puts
124
120
  # ---------------------------------------------------------------------------
125
121
  # RAG pattern
126
122
  # ---------------------------------------------------------------------------
127
- puts "=" * 60
128
- puts "RAG Pattern: retrieve relevant docs, then generate with LLM"
129
- puts "=" * 60
130
- puts
123
+ section "RAG Pattern: Retrieve Relevant Docs, Then Generate with LLM"
131
124
 
132
125
  rag_query = "Our Sidekiq jobs exhaust retries and land in the dead queue after a Stripe outage."
133
126