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
@@ -0,0 +1,306 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example 33: XYZZY Stock Price Predictor
5
+ #
6
+ # Consumes fake streaming prices for ticker XYZZY from a Redis channel,
7
+ # predicts the high and low over the next price window using an SMA + EMA
8
+ # ensemble, and uses a RobotLab learning robot to tune predictor parameters
9
+ # after each window closes.
10
+ #
11
+ # Run alongside:
12
+ # ruby examples/33_stock_generator.rb (in a separate terminal)
13
+ #
14
+ # Prerequisites:
15
+ # gem install redis
16
+ # Redis server running on localhost:6379
17
+ #
18
+ # Usage:
19
+ # ruby examples/33_stock_predictor.rb
20
+
21
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
22
+ require "robot_lab"
23
+ require "robot_lab/durable"
24
+ require "redis"
25
+ require "json"
26
+
27
+ CHANNEL = "stock:xyzzy"
28
+ WINDOW_SIZE = 12 # ticks per prediction window
29
+
30
+ # ── Mutable predictor parameters ──────────────────────────────────────────────
31
+
32
+ module PredictorConfig
33
+ @sma_window = 10
34
+ @sma_std_multiplier = 1.5
35
+ @ema_alpha = 0.2
36
+ @ema_vol_multiplier = 2.0
37
+ @sma_weight = 0.5
38
+
39
+ class << self
40
+ attr_accessor :sma_window, :sma_std_multiplier, :ema_alpha,
41
+ :ema_vol_multiplier, :sma_weight
42
+
43
+ def summary
44
+ format(
45
+ "sma_window=%d sma_std=%.2f ema_alpha=%.2f ema_vol=%.2f sma_weight=%.2f",
46
+ sma_window, sma_std_multiplier, ema_alpha, ema_vol_multiplier, sma_weight
47
+ )
48
+ end
49
+ end
50
+ end
51
+
52
+ # ── SMA predictor ──────────────────────────────────────────────────────────────
53
+
54
+ module SMAPredictor
55
+ def self.predict(prices)
56
+ window = prices.last(PredictorConfig.sma_window)
57
+ mean = window.sum / window.size.to_f
58
+ var = window.sum { |p| (p - mean)**2 } / window.size.to_f
59
+ std = Math.sqrt(var)
60
+ mult = PredictorConfig.sma_std_multiplier
61
+
62
+ {
63
+ high: (mean + mult * std).round(2),
64
+ low: [mean - mult * std, 1.0].max.round(2)
65
+ }
66
+ end
67
+ end
68
+
69
+ # ── EMA predictor (stateful — updated every tick) ──────────────────────────────
70
+
71
+ module EMAPredictor
72
+ @ema = nil
73
+ @var_ema = nil
74
+
75
+ class << self
76
+ def update(price)
77
+ alpha = PredictorConfig.ema_alpha
78
+ if @ema.nil?
79
+ @ema = price
80
+ @var_ema = 0.0
81
+ else
82
+ delta = price - @ema
83
+ @ema = alpha * price + (1 - alpha) * @ema
84
+ @var_ema = alpha * delta**2 + (1 - alpha) * @var_ema
85
+ end
86
+ end
87
+
88
+ def predict
89
+ return nil if @ema.nil?
90
+
91
+ vol = Math.sqrt(@var_ema) * PredictorConfig.ema_vol_multiplier
92
+ {
93
+ high: (@ema + vol).round(2),
94
+ low: [@ema - vol, 1.0].max.round(2)
95
+ }
96
+ end
97
+ end
98
+ end
99
+
100
+ # ── Ensemble predictor ─────────────────────────────────────────────────────────
101
+
102
+ module EnsemblePredictor
103
+ def self.predict(prices)
104
+ sma = SMAPredictor.predict(prices)
105
+ ema = EMAPredictor.predict
106
+ return sma unless ema
107
+
108
+ w = PredictorConfig.sma_weight
109
+ {
110
+ high: (w * sma[:high] + (1 - w) * ema[:high]).round(2),
111
+ low: (w * sma[:low] + (1 - w) * ema[:low]).round(2)
112
+ }
113
+ end
114
+ end
115
+
116
+ # ── AdjustParameters tool ──────────────────────────────────────────────────────
117
+
118
+ class AdjustParameters < RobotLab::Tool
119
+ description "Adjust one predictor parameter to improve future prediction accuracy. " \
120
+ "Make at most one or two targeted changes per window."
121
+
122
+ param :parameter, type: "string",
123
+ desc: "Parameter to adjust: sma_window, sma_std_multiplier, ema_alpha, ema_vol_multiplier, sma_weight"
124
+ param :value, type: "number",
125
+ desc: "New value (sma_window: 3-30 int; std/vol multipliers: 0.5-4.0; ema_alpha: 0.05-0.5; sma_weight: 0.0-1.0)"
126
+ param :reasoning, type: "string",
127
+ desc: "Why this change should reduce prediction error"
128
+
129
+ LIMITS = {
130
+ "sma_window" => { min: 3, max: 30, integer: true },
131
+ "sma_std_multiplier" => { min: 0.5, max: 4.0, integer: false },
132
+ "ema_alpha" => { min: 0.05, max: 0.5, integer: false },
133
+ "ema_vol_multiplier" => { min: 0.5, max: 4.0, integer: false },
134
+ "sma_weight" => { min: 0.0, max: 1.0, integer: false }
135
+ }.freeze
136
+
137
+ def execute(parameter:, value:, reasoning:)
138
+ spec = LIMITS[parameter]
139
+ return "Unknown parameter '#{parameter}'. Valid: #{LIMITS.keys.join(", ")}" unless spec
140
+
141
+ clamped = value.to_f.clamp(spec[:min], spec[:max])
142
+ clamped = clamped.round if spec[:integer]
143
+
144
+ PredictorConfig.send(:"#{parameter}=", clamped)
145
+
146
+ "Set #{parameter} = #{clamped}. #{reasoning}"
147
+ end
148
+ end
149
+
150
+ # ── Error metrics ──────────────────────────────────────────────────────────────
151
+
152
+ WindowResult = Data.define(
153
+ :window_num,
154
+ :predicted_high, :predicted_low,
155
+ :actual_high, :actual_low,
156
+ :high_err, :low_err, :mean_err
157
+ )
158
+
159
+ def evaluate_window(window_num, predicted, actuals)
160
+ actual_high = actuals.max.round(2)
161
+ actual_low = actuals.min.round(2)
162
+ high_err = (predicted[:high] - actual_high).abs.round(2)
163
+ low_err = (predicted[:low] - actual_low).abs.round(2)
164
+ mean_err = ((high_err + low_err) / 2.0).round(2)
165
+
166
+ WindowResult.new(
167
+ window_num:,
168
+ predicted_high: predicted[:high], predicted_low: predicted[:low],
169
+ actual_high:, actual_low:,
170
+ high_err:, low_err:, mean_err:
171
+ )
172
+ end
173
+
174
+ def tuner_prompt(result)
175
+ <<~PROMPT
176
+ Window #{result.window_num} just closed.
177
+
178
+ Prediction vs Actual:
179
+ Predicted: high=$#{result.predicted_high} low=$#{result.predicted_low}
180
+ Actual: high=$#{result.actual_high} low=$#{result.actual_low}
181
+ Error: high_err=$#{result.high_err} low_err=$#{result.low_err} mean_err=$#{result.mean_err}
182
+
183
+ Current parameters:
184
+ #{PredictorConfig.summary}
185
+
186
+ Window size: #{WINDOW_SIZE} ticks.
187
+
188
+ First call RecallKnowledge to check what has worked before.
189
+ Then decide whether to adjust a parameter via AdjustParameters.
190
+ If the error is acceptable or you are uncertain, do nothing.
191
+ If you notice a clear pattern worth preserving, call RecordKnowledge.
192
+ PROMPT
193
+ end
194
+
195
+ # ── Main ──────────────────────────────────────────────────────────────────────
196
+
197
+ puts "=" * 60
198
+ puts "XYZZY Stock Predictor"
199
+ puts "=" * 60
200
+ puts "Channel : #{CHANNEL}"
201
+ puts "Window : #{WINDOW_SIZE} ticks"
202
+ puts "Model : SMA + EMA Ensemble with Durable Learning"
203
+ puts "Warmup : #{PredictorConfig.sma_window} ticks"
204
+ puts "Press Ctrl-C to stop."
205
+ puts "-" * 60
206
+
207
+ redis = Redis.new
208
+ prices = []
209
+ robot = RobotLab.build(
210
+ model: "gpt-5.4",
211
+ name: "predictor_tuner",
212
+ system_prompt: <<~PROMPT,
213
+ You are a quantitative analyst tuning an ensemble stock price range
214
+ predictor for ticker XYZZY. Each prediction covers the high and low
215
+ price over the next #{WINDOW_SIZE} ticks.
216
+
217
+ The ensemble combines a Simple Moving Average (SMA) band and an
218
+ Exponential Moving Average (EMA) band. Adjustable parameters:
219
+
220
+ sma_window (3-30 int) — lookback period for SMA
221
+ sma_std_multiplier (0.5-4.0) — band width relative to SMA stddev
222
+ ema_alpha (0.05-0.5) — EMA smoothing (higher = more reactive)
223
+ ema_vol_multiplier (0.5-4.0) — band width relative to EMA volatility
224
+ sma_weight (0.0-1.0) — SMA share in ensemble (EMA = 1 - weight)
225
+
226
+ Workflow per window:
227
+ 1. Call RecallKnowledge to check past findings before acting.
228
+ 2. If the error is clearly too high/low in one direction, adjust the
229
+ relevant band multiplier via AdjustParameters.
230
+ 3. Make at most two adjustments per window to isolate cause and effect.
231
+ 4. If you observe a reliable pattern, call RecordKnowledge to preserve it.
232
+ 5. When uncertain, do nothing rather than guess.
233
+ PROMPT
234
+ local_tools: [AdjustParameters],
235
+ learn: true,
236
+ learn_domain: "xyzzy stock prediction"
237
+ )
238
+
239
+ warmed_up = false
240
+ pending_pred = nil # { prediction: {high:, low:}, window_prices: [] }
241
+ window_num = 0
242
+
243
+ trap("INT") { puts "\nPredictor stopped."; exit }
244
+
245
+ puts "Connecting to Redis and subscribing to #{CHANNEL}..."
246
+
247
+ redis.subscribe(CHANNEL) do |on|
248
+ on.message do |_channel, payload|
249
+ data = JSON.parse(payload, symbolize_names: true)
250
+ tick = data[:tick]
251
+ price = data[:price].to_f
252
+
253
+ EMAPredictor.update(price)
254
+ prices << price
255
+
256
+ # ── Warmup phase ──────────────────────────────────────────────
257
+ unless warmed_up
258
+ if prices.size < PredictorConfig.sma_window
259
+ puts "Tick %5d $%8.2f [warming up %d/%d]" % [tick, price, prices.size, PredictorConfig.sma_window]
260
+ next
261
+ end
262
+
263
+ warmed_up = true
264
+ pred = EnsemblePredictor.predict(prices)
265
+ pending_pred = { prediction: pred, window_prices: [] }
266
+
267
+ puts "Tick %5d $%8.2f [warmup done]" % [tick, price]
268
+ puts " First prediction → high=$#{pred[:high]} low=$#{pred[:low]}"
269
+ next
270
+ end
271
+
272
+ # ── Accumulate current window ──────────────────────────────────
273
+ pending_pred[:window_prices] << price
274
+ progress = pending_pred[:window_prices].size
275
+ pred = pending_pred[:prediction]
276
+
277
+ puts "Tick %5d $%8.2f [%2d/#{WINDOW_SIZE}] (pred high=$#{pred[:high]} low=$#{pred[:low]})" %
278
+ [tick, price, progress]
279
+
280
+ next unless progress >= WINDOW_SIZE
281
+
282
+ # ── Window closed — evaluate ───────────────────────────────────
283
+ window_num += 1
284
+ result = evaluate_window(window_num, pred, pending_pred[:window_prices])
285
+
286
+ puts "\n#{"─" * 60}"
287
+ puts " Window #{result.window_num} result:"
288
+ puts " Predicted high=$%-8.2f low=$%-.2f" % [result.predicted_high, result.predicted_low]
289
+ puts " Actual high=$%-8.2f low=$%-.2f" % [result.actual_high, result.actual_low]
290
+ puts " Error high=%-8.2f low=%-8.2f mean=%.2f" % [result.high_err, result.low_err, result.mean_err]
291
+ puts "#{"─" * 60}"
292
+
293
+ print " [tuner] analyzing window #{window_num}..."
294
+ tuner_response = robot.run(tuner_prompt(result))
295
+ tuner_line = tuner_response.reply.lines.first&.chomp || "(no response)"
296
+ puts "\r [tuner] #{tuner_line}#{" " * 20}"
297
+ puts " Params: #{PredictorConfig.summary}"
298
+ puts
299
+
300
+ # ── Start next window ──────────────────────────────────────────
301
+ new_pred = EnsemblePredictor.predict(prices)
302
+ pending_pred = { prediction: new_pred, window_prices: [] }
303
+ puts " Next prediction → high=$#{new_pred[:high]} low=$#{new_pred[:low]}"
304
+ puts
305
+ end
306
+ end
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example 34: AgentSkills.io Integration
5
+ #
6
+ # Demonstrates the unified skills: param detecting AgentSkills folder format.
7
+ # Skills in ~/.prompts/skills/ are matched at runtime via embedding similarity
8
+ # before each run() call — only relevant skills are injected.
9
+ #
10
+ # Usage:
11
+ # mkdir -p ~/.prompts/skills/code_reviewer
12
+ # # (create SKILL.md as shown in the example header)
13
+ # ANTHROPIC_API_KEY=your_key ruby examples/34_agentskills.rb
14
+
15
+ require_relative "common"
16
+
17
+ require "logger"
18
+ log_file = File.join(__dir__, "34.log")
19
+ RobotLab.config.logger = Logger.new(log_file)
20
+ RubyLLM.configure { |c| c.logger = Logger.new(log_file) }
21
+
22
+ banner "RobotLab — AgentSkills.io Integration Demo"
23
+
24
+ # Check if the skill is installed
25
+ skill_path = File.expand_path("~/.prompts/skills/code_reviewer/SKILL.md")
26
+ unless File.exist?(skill_path)
27
+ puts "Demo skill not found at #{skill_path}"
28
+ puts "Create it with:"
29
+ puts " mkdir -p ~/.prompts/skills/code_reviewer"
30
+ puts " # Then add SKILL.md with name: code_reviewer"
31
+ exit 1
32
+ end
33
+
34
+ # Build a robot that lists code_reviewer as a candidate skill.
35
+ # At runtime, if the user message is semantically similar to
36
+ # "Review Ruby code for quality, style, and potential bugs",
37
+ # the skill's instructions are injected into the system prompt.
38
+ robot = RobotLab.build(
39
+ model: LLM[:default].model,
40
+ name: "assistant",
41
+ system_prompt: "You are a helpful Ruby programming assistant.",
42
+ skills: [:code_reviewer]
43
+ )
44
+
45
+ puts "Pending AgentSkills: #{robot.instance_variable_get(:@pending_agent_skills).map(&:name).inspect}"
46
+ puts
47
+
48
+ # Message semantically related to code review — skill should activate
49
+ code_question = <<~MSG
50
+ Please review this Ruby method for quality issues:
51
+
52
+ def process(data)
53
+ begin
54
+ result = data.map { |item| transform(item) }
55
+ save(result)
56
+ rescue => e
57
+ puts e.message
58
+ end
59
+ end
60
+ MSG
61
+
62
+ puts "Query: code review (skill should activate)"
63
+ result = robot.run(code_question)
64
+ puts result.reply
65
+ puts
66
+ hr
67
+
68
+ # Message unrelated to code review — skill should NOT activate
69
+ puts "Query: general question (skill should NOT activate)"
70
+ result = robot.run("What is the capital of France?")
71
+ puts result.reply
72
+ puts
data/examples/README.md CHANGED
@@ -226,7 +226,7 @@ Demonstrates `robot.search_history(query, limit:)` — semantic search over accu
226
226
 
227
227
  Demonstrates `memory.store_document(key, text)` and `memory.search_documents(query, limit:)` — a lightweight RAG store using `fastembed` (BAAI/bge-small-en-v1.5). Documents are embedded once; queries are compared by cosine similarity at search time. Includes the `RobotLab::DocumentStore` standalone API and a RAG pattern sketch showing how to pass retrieved context to a robot.
228
228
 
229
- **Requires:** `fastembed` gem (already a core dependency); downloads the ~23 MB ONNX model on first run (cached in `~/.cache/fastembed/`)
229
+ **Requires:** `robot_lab-document_store` gem (`gem "robot_lab-document_store"` in your Gemfile); downloads the ~23 MB ONNX model on first run (cached in `~/.cache/fastembed/`)
230
230
 
231
231
  ### 27 — Production Incident War Room
232
232
 
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+
5
+ # Fallback for when direnv has not activated examples/.envrc
6
+ ENV["ROBOT_LAB_TEMPLATE_PATH"] ||= File.join(__dir__, "prompts")
7
+
8
+ require_relative "../lib/robot_lab"
9
+
10
+ LlmConfig = Data.define(:provider, :model)
11
+
12
+ LLM = {
13
+ default: LlmConfig.new(provider: "openai", model: "gpt-5.4"),
14
+ local: LlmConfig.new(provider: "ollama", model: "llama3.2"),
15
+ anthropic: LlmConfig.new(provider: "anthropic", model: "claude-opus-4-7")
16
+ }.freeze
17
+
18
+ RubyLLM.configure do |c|
19
+ c.logger = Logger.new(File::NULL)
20
+ c.default_model = LLM[:default].model
21
+ end
22
+
23
+ RobotLab.configure do |c|
24
+ c.logger = Logger.new(File::NULL)
25
+ end
26
+
27
+ # ── Example Output Helpers ─────────────────────────────────────────────────────
28
+
29
+ module ExOut
30
+ WIDTH = 68
31
+ RESET = "\e[0m"
32
+ BOLD = "\e[1m"
33
+ DIM = "\e[2m"
34
+ CYAN = "\e[36m"
35
+ end
36
+
37
+ # Prints a bold top-level header. Extracts the example number from $0
38
+ # automatically so callers only supply the title.
39
+ def banner(title)
40
+ num = File.basename($0, ".rb")[/^\d+/]&.to_i
41
+ label = num ? "Example #{num}: #{title}" : title
42
+ puts
43
+ puts "#{ExOut::BOLD}#{"=" * ExOut::WIDTH}#{ExOut::RESET}"
44
+ puts "#{ExOut::BOLD} #{label}#{ExOut::RESET}"
45
+ puts "#{ExOut::BOLD}#{"=" * ExOut::WIDTH}#{ExOut::RESET}"
46
+ puts
47
+ end
48
+
49
+ # Prints a named section divider with a cyan rule.
50
+ def section(title)
51
+ puts
52
+ tail = "─" * [ExOut::WIDTH - title.length - 4, 2].max
53
+ puts "#{ExOut::BOLD}#{ExOut::CYAN}── #{title} #{tail}#{ExOut::RESET}"
54
+ puts
55
+ end
56
+
57
+ # Prints a plain dim horizontal rule.
58
+ def hr
59
+ puts "#{ExOut::DIM}#{"─" * ExOut::WIDTH}#{ExOut::RESET}"
60
+ end
61
+
62
+ # Prints a Rouge-highlighted Ruby code block with a framed border.
63
+ def show_code(ruby_string, label: "ruby")
64
+ require "rouge"
65
+ w = ExOut::WIDTH - 2
66
+ border = "#{ExOut::DIM} #{"─" * w}#{ExOut::RESET}"
67
+ output = Rouge::Formatters::Terminal256.new
68
+ .format(Rouge::Lexers::Ruby.new.lex(ruby_string))
69
+ output += "\n" unless output.end_with?("\n")
70
+ puts
71
+ puts "#{ExOut::DIM} #{label}#{ExOut::RESET}"
72
+ puts border
73
+ output.each_line { |l| print " #{l}" }
74
+ puts border
75
+ puts
76
+ end