robot_lab 0.3.0 → 0.5.0

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