robot_lab 0.2.8 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -0
  3. data/README.md +16 -18
  4. data/lib/robot_lab/ask_user.rb +3 -3
  5. data/lib/robot_lab/config/defaults.yml +5 -5
  6. data/lib/robot_lab/config.rb +2 -2
  7. data/lib/robot_lab/mcp/transports/streamable_http.rb +2 -4
  8. data/lib/robot_lab/memory.rb +16 -7
  9. data/lib/robot_lab/robot/hooking.rb +26 -0
  10. data/lib/robot_lab/robot/result_building.rb +119 -0
  11. data/lib/robot_lab/robot.rb +37 -120
  12. data/lib/robot_lab/run_config.rb +52 -20
  13. data/lib/robot_lab/tool.rb +7 -12
  14. data/lib/robot_lab/version.rb +1 -1
  15. data/lib/robot_lab.rb +3 -3
  16. metadata +15 -274
  17. data/.architecture/AGENTS.md +0 -32
  18. data/.architecture/config.yml +0 -8
  19. data/.architecture/members.yml +0 -60
  20. data/.architecture/reviews/feature-free-will.md +0 -490
  21. data/.architecture/reviews/overall-codebase.md +0 -427
  22. data/.claude/settings.local.json +0 -57
  23. data/.codex/config.toml +0 -2
  24. data/.envrc +0 -6
  25. data/.github/workflows/deploy-github-pages.yml +0 -52
  26. data/.irbrc +0 -6
  27. data/.loki +0 -65
  28. data/.quality/reek_baseline.txt +0 -43
  29. data/.rubocop.yml +0 -10
  30. data/Archspec.rb +0 -44
  31. data/CLAUDE.md +0 -139
  32. data/COMMITS.md +0 -196
  33. data/Rakefile +0 -193
  34. data/_typos.toml +0 -21
  35. data/agent2agent_review.md +0 -192
  36. data/agentf_improvements.md +0 -253
  37. data/agents.md +0 -14
  38. data/docs/api/core/index.md +0 -147
  39. data/docs/api/core/memory.md +0 -578
  40. data/docs/api/core/network.md +0 -553
  41. data/docs/api/core/result.md +0 -211
  42. data/docs/api/core/robot.md +0 -1667
  43. data/docs/api/core/state.md +0 -145
  44. data/docs/api/core/tool.md +0 -520
  45. data/docs/api/errors.md +0 -323
  46. data/docs/api/hooks.md +0 -469
  47. data/docs/api/index.md +0 -137
  48. data/docs/api/mcp/client.md +0 -387
  49. data/docs/api/mcp/index.md +0 -267
  50. data/docs/api/mcp/server.md +0 -252
  51. data/docs/api/mcp/transports.md +0 -339
  52. data/docs/api/messages/index.md +0 -126
  53. data/docs/api/messages/text-message.md +0 -170
  54. data/docs/api/messages/tool-call-message.md +0 -206
  55. data/docs/api/messages/tool-result-message.md +0 -242
  56. data/docs/api/messages/user-message.md +0 -232
  57. data/docs/api/skills.md +0 -423
  58. data/docs/api/streaming/context.md +0 -280
  59. data/docs/api/streaming/events.md +0 -185
  60. data/docs/api/streaming/index.md +0 -156
  61. data/docs/api/support.md +0 -846
  62. data/docs/architecture/core-concepts.md +0 -575
  63. data/docs/architecture/index.md +0 -252
  64. data/docs/architecture/message-flow.md +0 -366
  65. data/docs/architecture/network-orchestration.md +0 -478
  66. data/docs/architecture/robot-execution.md +0 -396
  67. data/docs/architecture/state-management.md +0 -410
  68. data/docs/assets/css/custom.css +0 -56
  69. data/docs/assets/images/robot_lab.jpg +0 -0
  70. data/docs/concepts.md +0 -641
  71. data/docs/examples/basic-chat.md +0 -258
  72. data/docs/examples/index.md +0 -425
  73. data/docs/examples/mcp-server.md +0 -403
  74. data/docs/examples/multi-robot-network.md +0 -476
  75. data/docs/examples/tool-usage.md +0 -468
  76. data/docs/getting-started/configuration.md +0 -645
  77. data/docs/getting-started/index.md +0 -56
  78. data/docs/getting-started/installation.md +0 -200
  79. data/docs/getting-started/quick-start.md +0 -293
  80. data/docs/guides/building-robots.md +0 -1187
  81. data/docs/guides/creating-networks.md +0 -736
  82. data/docs/guides/hooks.md +0 -1206
  83. data/docs/guides/index.md +0 -78
  84. data/docs/guides/knowledge.md +0 -219
  85. data/docs/guides/mcp-integration.md +0 -740
  86. data/docs/guides/memory.md +0 -458
  87. data/docs/guides/observability.md +0 -733
  88. data/docs/guides/streaming.md +0 -323
  89. data/docs/guides/using-tools.md +0 -780
  90. data/docs/index.md +0 -280
  91. data/docs/superpowers/plans/2026-05-06-agentskills.md +0 -1303
  92. data/docs/superpowers/specs/2026-05-06-agentskills-design.md +0 -247
  93. data/examples/.envrc +0 -1
  94. data/examples/01_simple_robot.rb +0 -31
  95. data/examples/02_tools.rb +0 -109
  96. data/examples/03_network.rb +0 -118
  97. data/examples/04_mcp.rb +0 -215
  98. data/examples/05_streaming.rb +0 -137
  99. data/examples/06_prompt_templates.rb +0 -325
  100. data/examples/07_network_memory.rb +0 -313
  101. data/examples/08_llm_config.rb +0 -162
  102. data/examples/09_chaining.rb +0 -251
  103. data/examples/10_memory.rb +0 -315
  104. data/examples/11_network_introspection.rb +0 -239
  105. data/examples/12_message_bus.rb +0 -67
  106. data/examples/13_spawn.rb +0 -85
  107. data/examples/14_rusty_circuit/.envrc +0 -1
  108. data/examples/14_rusty_circuit/.gitignore +0 -1
  109. data/examples/14_rusty_circuit/README.md +0 -1
  110. data/examples/14_rusty_circuit/comic.rb +0 -149
  111. data/examples/14_rusty_circuit/display.rb +0 -217
  112. data/examples/14_rusty_circuit/heckler.rb +0 -65
  113. data/examples/14_rusty_circuit/open_mic.rb +0 -132
  114. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +0 -20
  115. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +0 -23
  116. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +0 -20
  117. data/examples/14_rusty_circuit/scout.rb +0 -165
  118. data/examples/14_rusty_circuit/show.log +0 -234
  119. data/examples/15_memory_network_and_bus/.envrc +0 -1
  120. data/examples/15_memory_network_and_bus/editor_in_chief.rb +0 -24
  121. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +0 -213
  122. data/examples/15_memory_network_and_bus/linux_writer.rb +0 -80
  123. data/examples/15_memory_network_and_bus/os_editor.rb +0 -48
  124. data/examples/15_memory_network_and_bus/os_writer.rb +0 -49
  125. data/examples/15_memory_network_and_bus/output/combined_article.md +0 -13
  126. data/examples/15_memory_network_and_bus/output/final_article.md +0 -13
  127. data/examples/15_memory_network_and_bus/output/linux_draft.md +0 -7
  128. data/examples/15_memory_network_and_bus/output/mac_draft.md +0 -7
  129. data/examples/15_memory_network_and_bus/output/memory.json +0 -13
  130. data/examples/15_memory_network_and_bus/output/revision_1.md +0 -18
  131. data/examples/15_memory_network_and_bus/output/revision_2.md +0 -13
  132. data/examples/15_memory_network_and_bus/output/windows_draft.md +0 -7
  133. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +0 -13
  134. data/examples/15_memory_network_and_bus/prompts/os_chief.md +0 -13
  135. data/examples/15_memory_network_and_bus/prompts/os_editor.md +0 -13
  136. data/examples/16_writers_room/.envrc +0 -1
  137. data/examples/16_writers_room/display.rb +0 -158
  138. data/examples/16_writers_room/output/.gitignore +0 -4
  139. data/examples/16_writers_room/output/README.md +0 -69
  140. data/examples/16_writers_room/output/opus_001.md +0 -263
  141. data/examples/16_writers_room/output/opus_001_notes.log +0 -470
  142. data/examples/16_writers_room/output/opus_002.md +0 -245
  143. data/examples/16_writers_room/output/opus_002_notes.log +0 -546
  144. data/examples/16_writers_room/output/opus_002_screenplay.md +0 -7989
  145. data/examples/16_writers_room/output/opus_002_screenplay_notes.md +0 -993
  146. data/examples/16_writers_room/prompts/screenplay_writer.md +0 -66
  147. data/examples/16_writers_room/prompts/writer.md +0 -37
  148. data/examples/16_writers_room/room.rb +0 -186
  149. data/examples/16_writers_room/tools.rb +0 -173
  150. data/examples/16_writers_room/writer.rb +0 -121
  151. data/examples/16_writers_room/writers_room.rb +0 -256
  152. data/examples/17_skills.rb +0 -234
  153. data/examples/18_rails/.envrc +0 -3
  154. data/examples/18_rails/.gitignore +0 -5
  155. data/examples/18_rails/Gemfile +0 -11
  156. data/examples/18_rails/README.md +0 -67
  157. data/examples/18_rails/Rakefile +0 -4
  158. data/examples/18_rails/app/controllers/application_controller.rb +0 -4
  159. data/examples/18_rails/app/controllers/chat_controller.rb +0 -50
  160. data/examples/18_rails/app/jobs/application_job.rb +0 -4
  161. data/examples/18_rails/app/jobs/robot_run_job.rb +0 -25
  162. data/examples/18_rails/app/models/application_record.rb +0 -5
  163. data/examples/18_rails/app/models/robot_lab_result.rb +0 -36
  164. data/examples/18_rails/app/models/robot_lab_thread.rb +0 -23
  165. data/examples/18_rails/app/robots/chat_robot.rb +0 -25
  166. data/examples/18_rails/app/tools/time_tool.rb +0 -9
  167. data/examples/18_rails/app/views/chat/_user_message.html.erb +0 -1
  168. data/examples/18_rails/app/views/chat/index.html.erb +0 -67
  169. data/examples/18_rails/app/views/layouts/application.html.erb +0 -49
  170. data/examples/18_rails/bin/dev +0 -7
  171. data/examples/18_rails/bin/rails +0 -6
  172. data/examples/18_rails/bin/setup +0 -15
  173. data/examples/18_rails/config/application.rb +0 -33
  174. data/examples/18_rails/config/cable.yml +0 -2
  175. data/examples/18_rails/config/database.yml +0 -5
  176. data/examples/18_rails/config/environment.rb +0 -4
  177. data/examples/18_rails/config/initializers/robot_lab.rb +0 -11
  178. data/examples/18_rails/config/routes.rb +0 -6
  179. data/examples/18_rails/config.ru +0 -4
  180. data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +0 -32
  181. data/examples/19_token_tracking.rb +0 -138
  182. data/examples/20_circuit_breaker.rb +0 -147
  183. data/examples/21_learning_loop.rb +0 -181
  184. data/examples/22_context_compression.rb +0 -172
  185. data/examples/23_convergence.rb +0 -133
  186. data/examples/24_structured_delegation.rb +0 -151
  187. data/examples/25_history_search/conversation.jsonl +0 -30
  188. data/examples/25_history_search.rb +0 -133
  189. data/examples/26_document_store/api_versioning_adr.md +0 -52
  190. data/examples/26_document_store/incident_postmortem.md +0 -46
  191. data/examples/26_document_store/postgres_runbook.md +0 -49
  192. data/examples/26_document_store/redis_caching_guide.md +0 -48
  193. data/examples/26_document_store/sidekiq_guide.md +0 -51
  194. data/examples/26_document_store.rb +0 -140
  195. data/examples/27_incident_response/incident_response.rb +0 -261
  196. data/examples/28_mcp_discovery.rb +0 -113
  197. data/examples/29_ractor_tools.rb +0 -240
  198. data/examples/30_ractor_network.rb +0 -252
  199. data/examples/31_launch_assessment.rb +0 -246
  200. data/examples/32_newsletter_reader.rb +0 -193
  201. data/examples/33_stock_generator.rb +0 -80
  202. data/examples/33_stock_predictor.rb +0 -327
  203. data/examples/34_agentskills.rb +0 -76
  204. data/examples/35_hooks.rb +0 -266
  205. data/examples/README.md +0 -533
  206. data/examples/common.rb +0 -148
  207. data/examples/prompts/assistant.md +0 -5
  208. data/examples/prompts/audit_trail.md +0 -8
  209. data/examples/prompts/billing.md +0 -10
  210. data/examples/prompts/classifier.md +0 -7
  211. data/examples/prompts/comedian.md +0 -6
  212. data/examples/prompts/comedy_critic.md +0 -10
  213. data/examples/prompts/configurable.md +0 -9
  214. data/examples/prompts/dispatcher.md +0 -12
  215. data/examples/prompts/entity_extractor.md +0 -14
  216. data/examples/prompts/escalation.md +0 -42
  217. data/examples/prompts/frontmatter_mcp_test.md +0 -9
  218. data/examples/prompts/frontmatter_named_test.md +0 -5
  219. data/examples/prompts/frontmatter_tools_test.md +0 -6
  220. data/examples/prompts/general.md +0 -7
  221. data/examples/prompts/github_assistant.md +0 -14
  222. data/examples/prompts/helper.md +0 -4
  223. data/examples/prompts/incident_responder.md +0 -18
  224. data/examples/prompts/keyword_extractor.md +0 -11
  225. data/examples/prompts/llm_config_demo.md +0 -20
  226. data/examples/prompts/order_support.md +0 -35
  227. data/examples/prompts/os_advocate.md +0 -13
  228. data/examples/prompts/os_chief.md +0 -13
  229. data/examples/prompts/os_editor.md +0 -13
  230. data/examples/prompts/parameterized_main_test.md +0 -6
  231. data/examples/prompts/pii_redactor.md +0 -7
  232. data/examples/prompts/product_support.md +0 -37
  233. data/examples/prompts/runbook_protocol.md +0 -12
  234. data/examples/prompts/sentiment_analyzer.md +0 -12
  235. data/examples/prompts/skill_a_test.md +0 -4
  236. data/examples/prompts/skill_b_test.md +0 -4
  237. data/examples/prompts/skill_config_test.md +0 -5
  238. data/examples/prompts/skill_cycle_a_test.md +0 -6
  239. data/examples/prompts/skill_cycle_b_test.md +0 -6
  240. data/examples/prompts/skill_description_test.md +0 -4
  241. data/examples/prompts/skill_leaf_test.md +0 -4
  242. data/examples/prompts/skill_nested_test.md +0 -6
  243. data/examples/prompts/skill_refs_main_test.md +0 -6
  244. data/examples/prompts/skill_self_ref_test.md +0 -6
  245. data/examples/prompts/skill_with_mcp_test.md +0 -9
  246. data/examples/prompts/skill_with_params_test.md +0 -6
  247. data/examples/prompts/skill_with_robot_name_test.md +0 -5
  248. data/examples/prompts/skill_with_tools_test.md +0 -6
  249. data/examples/prompts/sre_compliance.md +0 -10
  250. data/examples/prompts/structured_output.md +0 -7
  251. data/examples/prompts/synthesizer.md +0 -17
  252. data/examples/prompts/technical.md +0 -10
  253. data/examples/prompts/template_with_skills_test.md +0 -6
  254. data/examples/prompts/triage.md +0 -22
  255. data/examples/ruboruby.md +0 -423
  256. data/examples/xyzzy.rb +0 -97
  257. data/logfile +0 -8
  258. data/mkdocs.yml +0 -209
  259. data/robot_concurrency.md +0 -38
  260. data/simple_acp_review.md +0 -298
  261. data/temp.md +0 -6
  262. data/tool_manifest_plan.md +0 -155
@@ -1,80 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Example 33: XYZZY Stock Price Generator
5
- #
6
- # Publishes fake streaming prices for ticker XYZZY to a Redis channel
7
- # using Geometric Brownian Motion with occasional volatility regime shifts.
8
- #
9
- # Prerequisites:
10
- # gem install redis
11
- # Redis server running on localhost:6379
12
- #
13
- # Usage:
14
- # ruby examples/33_stock_generator.rb
15
-
16
- require "redis"
17
- require "json"
18
- require "time"
19
-
20
- CHANNEL = "stock:xyzzy"
21
- START_PRICE = 100.0
22
- BASE_VOL = 0.008 # baseline volatility per tick (~0.8%)
23
- DRIFT = 0.0001 # slight upward drift per tick
24
-
25
- # Box-Muller transform — standard normal sample
26
- def randn
27
- Math.sqrt(-2.0 * Math.log(rand)) * Math.cos(2.0 * Math::PI * rand)
28
- end
29
-
30
- # Occasionally shift the volatility regime to create interesting price dynamics
31
- def current_volatility(tick)
32
- case tick % 60
33
- when 0..10 then BASE_VOL * 2.0 # high volatility burst
34
- when 30..35 then BASE_VOL * 0.4 # low volatility squeeze
35
- else BASE_VOL
36
- end
37
- end
38
-
39
- # ── Main ──────────────────────────────────────────────────────────────────────
40
-
41
- redis = Redis.new
42
- price = START_PRICE
43
- tick = 0
44
-
45
- trap("INT") { puts "\nGenerator stopped."; exit }
46
-
47
- puts "=" * 50
48
- puts "XYZZY Stock Generator"
49
- puts "=" * 50
50
- puts "Channel : #{CHANNEL}"
51
- puts "Interval: 5 seconds per tick"
52
- puts "Model : Geometric Brownian Motion"
53
- puts "Press Ctrl-C to stop."
54
- puts "-" * 50
55
-
56
- loop do
57
- tick += 1
58
-
59
- vol = current_volatility(tick)
60
- price = (price * Math.exp((DRIFT - 0.5 * vol**2) + vol * randn)).round(2)
61
- price = [price, 1.0].max # floor at $1.00
62
-
63
- regime = case vol
64
- when BASE_VOL * 2.0 then " [HIGH VOL]"
65
- when BASE_VOL * 0.4 then " [low vol]"
66
- else ""
67
- end
68
-
69
- payload = JSON.generate(
70
- ticker: "XYZZY",
71
- price: price,
72
- tick: tick,
73
- timestamp: Time.now.iso8601
74
- )
75
-
76
- redis.publish(CHANNEL, payload)
77
- puts "Tick %5d $%8.2f vol=%.3f%s" % [tick, price, vol, regime]
78
-
79
- sleep 5
80
- end
@@ -1,327 +0,0 @@
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
- # Ollama running with the model from common.rb pulled
18
- #
19
- # Usage:
20
- # ruby examples/33_stock_predictor.rb
21
-
22
- require_relative "common"
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
- require_ollama!
208
-
209
- redis = Redis.new
210
- prices = []
211
-
212
- robot = RobotLab.build(
213
- **llm_opts,
214
- name: "predictor_tuner",
215
- system_prompt: <<~PROMPT,
216
- You are a quantitative analyst tuning an ensemble stock price range
217
- predictor for ticker XYZZY. Each prediction covers the high and low
218
- price over the next #{WINDOW_SIZE} ticks.
219
-
220
- The ensemble combines a Simple Moving Average (SMA) band and an
221
- Exponential Moving Average (EMA) band. Adjustable parameters:
222
-
223
- sma_window (3-30 int) — lookback period for SMA
224
- sma_std_multiplier (0.5-4.0) — band width relative to SMA stddev
225
- ema_alpha (0.05-0.5) — EMA smoothing (higher = more reactive)
226
- ema_vol_multiplier (0.5-4.0) — band width relative to EMA volatility
227
- sma_weight (0.0-1.0) — SMA share in ensemble (EMA = 1 - weight)
228
-
229
- Workflow per window:
230
- 1. Call RecallKnowledge to check past findings before acting.
231
- 2. If the error is clearly too high/low in one direction, adjust the
232
- relevant band multiplier via AdjustParameters.
233
- 3. Make at most two adjustments per window to isolate cause and effect.
234
- 4. If you observe a reliable pattern, call RecordKnowledge to preserve it.
235
- 5. When uncertain, do nothing rather than guess.
236
- PROMPT
237
- local_tools: [AdjustParameters]
238
- )
239
-
240
- # Durable learning is enabled AFTER construction. Robot#initialize takes a
241
- # fixed keyword list with no **rest, so `learn:`/`learn_domain:` constructor
242
- # params raise ArgumentError; the Durable::Learning mixin exposes
243
- # setup_durable_learning instead. It seeds @learnings from
244
- # ~/.robot_lab/durable/<domain>.yml and appends the RecallKnowledge and
245
- # RecordKnowledge tools to local_tools.
246
- robot.setup_durable_learning(domain: "xyzzy stock prediction")
247
-
248
- puts "Durable domain : xyzzy stock prediction"
249
- puts "Tools : #{robot.local_tools.map(&:name).join(', ')}"
250
- puts "Seeded learnings: #{robot.learnings.size}"
251
- puts "-" * 60
252
-
253
- warmed_up = false
254
- pending_pred = nil # { prediction: {high:, low:}, window_prices: [] }
255
- window_num = 0
256
-
257
- trap("INT") { puts "\nPredictor stopped."; exit }
258
-
259
- puts "Connecting to Redis and subscribing to #{CHANNEL}..."
260
-
261
- redis.subscribe(CHANNEL) do |on|
262
- on.message do |_channel, payload|
263
- data = JSON.parse(payload, symbolize_names: true)
264
- tick = data[:tick]
265
- price = data[:price].to_f
266
-
267
- EMAPredictor.update(price)
268
- prices << price
269
-
270
- # ── Warmup phase ──────────────────────────────────────────────
271
- unless warmed_up
272
- if prices.size < PredictorConfig.sma_window
273
- puts "Tick %5d $%8.2f [warming up %d/%d]" % [tick, price, prices.size, PredictorConfig.sma_window]
274
- next
275
- end
276
-
277
- warmed_up = true
278
- pred = EnsemblePredictor.predict(prices)
279
- pending_pred = { prediction: pred, window_prices: [] }
280
-
281
- puts "Tick %5d $%8.2f [warmup done]" % [tick, price]
282
- puts " First prediction → high=$#{pred[:high]} low=$#{pred[:low]}"
283
- next
284
- end
285
-
286
- # ── Accumulate current window ──────────────────────────────────
287
- pending_pred[:window_prices] << price
288
- progress = pending_pred[:window_prices].size
289
- pred = pending_pred[:prediction]
290
-
291
- puts "Tick %5d $%8.2f [%2d/#{WINDOW_SIZE}] (pred high=$#{pred[:high]} low=$#{pred[:low]})" %
292
- [tick, price, progress]
293
-
294
- next unless progress >= WINDOW_SIZE
295
-
296
- # ── Window closed — evaluate ───────────────────────────────────
297
- window_num += 1
298
- result = evaluate_window(window_num, pred, pending_pred[:window_prices])
299
-
300
- puts "\n#{"─" * 60}"
301
- puts " Window #{result.window_num} result:"
302
- puts " Predicted high=$%-8.2f low=$%-.2f" % [result.predicted_high, result.predicted_low]
303
- puts " Actual high=$%-8.2f low=$%-.2f" % [result.actual_high, result.actual_low]
304
- puts " Error high=%-8.2f low=%-8.2f mean=%.2f" % [result.high_err, result.low_err, result.mean_err]
305
- puts "#{"─" * 60}"
306
-
307
- print " [tuner] analyzing window #{window_num}..."
308
- # tools: :inherit is required — run() defaults to :none, which sends the
309
- # provider an empty tool list, so AdjustParameters/RecallKnowledge/
310
- # RecordKnowledge would never be callable.
311
- tuner_response = robot.run(tuner_prompt(result), tools: :inherit)
312
- tuner_line = tuner_response.reply.lines.first&.chomp || "(no response)"
313
- puts "\r [tuner] #{tuner_line}#{" " * 20}"
314
- puts " Params: #{PredictorConfig.summary}"
315
-
316
- # Promote this session's learnings into the durable YAML store. Core
317
- # Robot#run does not invoke the reflector, so the caller drives it.
318
- robot.run_reflector
319
- puts
320
-
321
- # ── Start next window ──────────────────────────────────────────
322
- new_pred = EnsemblePredictor.predict(prices)
323
- pending_pred = { prediction: new_pred, window_prices: [] }
324
- puts " Next prediction → high=$#{new_pred[:high]} low=$#{new_pred[:low]}"
325
- puts
326
- end
327
- end
@@ -1,76 +0,0 @@
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
- # ruby examples/34_agentskills.rb
14
-
15
- require_relative "common"
16
- require "robot_lab/document_store"
17
-
18
- require "logger"
19
- log_file = File.join(__dir__, "34.log")
20
- RobotLab.config.logger = Logger.new(log_file)
21
- RubyLLM.configure { |c| c.logger = Logger.new(log_file) }
22
-
23
- banner "RobotLab — AgentSkills.io Integration Demo"
24
-
25
- # Check if the skill is installed
26
- skill_path = File.expand_path("~/.prompts/skills/code_reviewer/SKILL.md")
27
- unless File.exist?(skill_path)
28
- puts "Demo skill not found at #{skill_path}"
29
- puts "Create it with:"
30
- puts " mkdir -p ~/.prompts/skills/code_reviewer"
31
- puts " # Then add SKILL.md with name: code_reviewer"
32
- exit 1
33
- end
34
-
35
- # Build a robot that lists code_reviewer as a candidate skill.
36
- # At runtime, if the user message is semantically similar to
37
- # "Review Ruby code for quality, style, and potential bugs",
38
- # the skill's instructions are injected into the system prompt.
39
- robot = RobotLab.build(
40
- **llm_opts,
41
- name: "assistant",
42
- system_prompt: "You are a helpful Ruby programming assistant.",
43
- skills: [:code_reviewer]
44
- )
45
-
46
- puts "Declared skills: #{robot.skills.inspect}"
47
- puts "(Folder-format skills are matched per-run by embedding similarity, so"
48
- puts " whether one is injected depends on the message — watch the two queries"
49
- puts " below.)"
50
- puts
51
-
52
- # Message semantically related to code review — skill should activate
53
- code_question = <<~MSG
54
- Please review this Ruby method for quality issues:
55
-
56
- def process(data)
57
- begin
58
- result = data.map { |item| transform(item) }
59
- save(result)
60
- rescue => e
61
- puts e.message
62
- end
63
- end
64
- MSG
65
-
66
- puts "Query: code review (skill should activate)"
67
- result = robot.run(code_question)
68
- puts result.reply
69
- puts
70
- hr
71
-
72
- # Message unrelated to code review — skill should NOT activate
73
- puts "Query: general question (skill should NOT activate)"
74
- result = robot.run("What is the capital of France?")
75
- puts result.reply
76
- puts