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.
- checksums.yaml +4 -4
- data/.architecture/AGENTS.md +32 -0
- data/.architecture/config.yml +8 -0
- data/.architecture/members.yml +60 -0
- data/.architecture/reviews/feature-free-will.md +490 -0
- data/.architecture/reviews/overall-codebase.md +427 -0
- data/.claude/settings.local.json +57 -0
- data/.codex/config.toml +2 -0
- data/.irbrc +2 -2
- data/.rubocop.yml +172 -0
- data/CHANGELOG.md +72 -0
- data/CLAUDE.md +139 -0
- data/README.md +91 -95
- data/Rakefile +109 -3
- data/agent2agent_review.md +192 -0
- data/agentf_improvements.md +253 -0
- data/agents.md +14 -0
- data/docs/examples/index.md +37 -2
- data/docs/getting-started/configuration.md +20 -7
- data/docs/guides/index.md +16 -16
- data/docs/guides/knowledge.md +7 -1
- data/docs/guides/observability.md +132 -0
- data/docs/index.md +30 -3
- data/docs/superpowers/plans/2026-05-06-agentskills.md +1303 -0
- data/docs/superpowers/specs/2026-05-06-agentskills-design.md +247 -0
- data/examples/.envrc +1 -0
- data/examples/01_simple_robot.rb +5 -9
- data/examples/02_tools.rb +5 -9
- data/examples/03_network.rb +8 -9
- data/examples/04_mcp.rb +21 -29
- data/examples/05_streaming.rb +12 -18
- data/examples/06_prompt_templates.rb +11 -19
- data/examples/07_network_memory.rb +16 -31
- data/examples/08_llm_config.rb +10 -22
- data/examples/09_chaining.rb +16 -27
- data/examples/10_memory.rb +12 -28
- data/examples/11_network_introspection.rb +15 -29
- data/examples/12_message_bus.rb +5 -12
- data/examples/13_spawn.rb +5 -10
- data/examples/14_rusty_circuit/.envrc +1 -0
- data/examples/14_rusty_circuit/comic.rb +2 -0
- data/examples/14_rusty_circuit/heckler.rb +1 -1
- data/examples/14_rusty_circuit/open_mic.rb +1 -3
- data/examples/14_rusty_circuit/scout.rb +2 -0
- data/examples/15_memory_network_and_bus/.envrc +1 -0
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +6 -3
- data/examples/15_memory_network_and_bus/linux_writer.rb +1 -1
- data/examples/15_memory_network_and_bus/output/combined_article.md +6 -6
- data/examples/15_memory_network_and_bus/output/final_article.md +6 -8
- data/examples/15_memory_network_and_bus/output/linux_draft.md +4 -2
- data/examples/15_memory_network_and_bus/output/mac_draft.md +3 -3
- data/examples/15_memory_network_and_bus/output/memory.json +6 -6
- data/examples/15_memory_network_and_bus/output/revision_1.md +10 -11
- data/examples/15_memory_network_and_bus/output/revision_2.md +6 -8
- data/examples/15_memory_network_and_bus/output/windows_draft.md +3 -3
- data/examples/16_writers_room/.envrc +1 -0
- data/examples/16_writers_room/writers_room.rb +2 -4
- data/examples/17_skills.rb +8 -17
- data/examples/18_rails/Gemfile +1 -0
- data/examples/19_token_tracking.rb +9 -15
- data/examples/20_circuit_breaker.rb +10 -19
- data/examples/21_learning_loop.rb +11 -20
- data/examples/22_context_compression.rb +6 -13
- data/examples/23_convergence.rb +6 -17
- data/examples/24_structured_delegation.rb +11 -15
- data/examples/25_history_search.rb +5 -12
- data/examples/26_document_store.rb +6 -13
- data/examples/27_incident_response/incident_response.rb +4 -5
- data/examples/28_mcp_discovery.rb +8 -11
- data/examples/29_ractor_tools.rb +4 -9
- data/examples/30_ractor_network.rb +10 -19
- data/examples/31_launch_assessment.rb +10 -23
- data/examples/32_newsletter_reader.rb +188 -0
- data/examples/33_stock_generator.rb +80 -0
- data/examples/33_stock_predictor.rb +306 -0
- data/examples/34_agentskills.rb +72 -0
- data/examples/README.md +1 -1
- data/examples/common.rb +76 -0
- data/examples/ruboruby.md +423 -0
- data/examples/temp.md +51 -0
- data/lib/robot_lab/agent_skill.rb +63 -0
- data/lib/robot_lab/agent_skill_catalog.rb +74 -0
- data/lib/robot_lab/ask_user.rb +2 -2
- data/lib/robot_lab/bus_poller.rb +12 -5
- data/lib/robot_lab/config.rb +1 -12
- data/lib/robot_lab/delegation_future.rb +1 -1
- data/lib/robot_lab/doom_loop_detector.rb +98 -0
- data/lib/robot_lab/history_compressor.rb +4 -10
- data/lib/robot_lab/mcp/client.rb +1 -2
- data/lib/robot_lab/mcp/connection_poller.rb +3 -3
- data/lib/robot_lab/mcp/server.rb +1 -1
- data/lib/robot_lab/mcp/server_discovery.rb +0 -2
- data/lib/robot_lab/memory.rb +32 -27
- data/lib/robot_lab/memory_change.rb +2 -2
- data/lib/robot_lab/message.rb +4 -4
- data/lib/robot_lab/network.rb +11 -6
- data/lib/robot_lab/robot/agent_skill_matching.rb +99 -0
- data/lib/robot_lab/robot/bus_messaging.rb +9 -27
- data/lib/robot_lab/robot/history_search.rb +4 -1
- data/lib/robot_lab/robot/mcp_management.rb +5 -11
- data/lib/robot_lab/robot/template_rendering.rb +60 -40
- data/lib/robot_lab/robot.rb +323 -206
- data/lib/robot_lab/robot_result.rb +6 -5
- data/lib/robot_lab/run_config.rb +5 -11
- data/lib/robot_lab/script_tool.rb +76 -0
- data/lib/robot_lab/state_proxy.rb +7 -5
- data/lib/robot_lab/tool.rb +3 -3
- data/lib/robot_lab/tool_config.rb +1 -1
- data/lib/robot_lab/tool_manifest.rb +5 -7
- data/lib/robot_lab/user_message.rb +2 -2
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab/waiter.rb +1 -1
- data/lib/robot_lab.rb +41 -52
- data/logfile +8 -0
- data/mkdocs.yml +2 -3
- data/robot_concurrency.md +38 -0
- data/simple_acp_review.md +298 -0
- data/site/404.html +2300 -0
- data/site/api/core/index.html +2706 -0
- data/site/api/core/memory/index.html +3793 -0
- data/site/api/core/network/index.html +3500 -0
- data/site/api/core/robot/index.html +4566 -0
- data/site/api/core/state/index.html +3390 -0
- data/site/api/core/tool/index.html +3843 -0
- data/site/api/index.html +2635 -0
- data/site/api/mcp/client/index.html +3435 -0
- data/site/api/mcp/index.html +2783 -0
- data/site/api/mcp/server/index.html +3252 -0
- data/site/api/mcp/transports/index.html +3352 -0
- data/site/api/messages/index.html +2641 -0
- data/site/api/messages/text-message/index.html +3087 -0
- data/site/api/messages/tool-call-message/index.html +3159 -0
- data/site/api/messages/tool-result-message/index.html +3252 -0
- data/site/api/messages/user-message/index.html +3212 -0
- data/site/api/streaming/context/index.html +3282 -0
- data/site/api/streaming/events/index.html +3347 -0
- data/site/api/streaming/index.html +2738 -0
- data/site/architecture/core-concepts/index.html +3757 -0
- data/site/architecture/index.html +2797 -0
- data/site/architecture/message-flow/index.html +3238 -0
- data/site/architecture/network-orchestration/index.html +3433 -0
- data/site/architecture/robot-execution/index.html +3140 -0
- data/site/architecture/state-management/index.html +3498 -0
- data/site/assets/css/custom.css +56 -0
- data/site/assets/images/favicon.png +0 -0
- data/site/assets/images/robot_lab.jpg +0 -0
- data/site/assets/javascripts/bundle.79ae519e.min.js +16 -0
- data/site/assets/javascripts/bundle.79ae519e.min.js.map +7 -0
- data/site/assets/javascripts/lunr/min/lunr.ar.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.da.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.de.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.du.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.el.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.es.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.fi.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.fr.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.he.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.hi.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.hu.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.hy.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.it.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.ja.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.jp.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.kn.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.ko.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.multi.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.nl.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.no.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.pt.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.ro.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.ru.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.sa.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.stemmer.support.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.sv.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.ta.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.te.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.th.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.tr.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.vi.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.zh.min.js +1 -0
- data/site/assets/javascripts/lunr/tinyseg.js +206 -0
- data/site/assets/javascripts/lunr/wordcut.js +6708 -0
- data/site/assets/javascripts/workers/search.2c215733.min.js +42 -0
- data/site/assets/javascripts/workers/search.2c215733.min.js.map +7 -0
- data/site/assets/stylesheets/main.484c7ddc.min.css +1 -0
- data/site/assets/stylesheets/main.484c7ddc.min.css.map +1 -0
- data/site/assets/stylesheets/palette.ab4e12ef.min.css +1 -0
- data/site/assets/stylesheets/palette.ab4e12ef.min.css.map +1 -0
- data/site/concepts/index.html +3455 -0
- data/site/examples/basic-chat/index.html +2880 -0
- data/site/examples/index.html +2907 -0
- data/site/examples/mcp-server/index.html +3018 -0
- data/site/examples/multi-robot-network/index.html +3131 -0
- data/site/examples/rails-application/index.html +3329 -0
- data/site/examples/tool-usage/index.html +3085 -0
- data/site/getting-started/configuration/index.html +3745 -0
- data/site/getting-started/index.html +2572 -0
- data/site/getting-started/installation/index.html +2981 -0
- data/site/getting-started/quick-start/index.html +2942 -0
- data/site/guides/building-robots/index.html +4290 -0
- data/site/guides/creating-networks/index.html +3858 -0
- data/site/guides/index.html +2586 -0
- data/site/guides/mcp-integration/index.html +3581 -0
- data/site/guides/memory/index.html +3586 -0
- data/site/guides/rails-integration/index.html +4019 -0
- data/site/guides/streaming/index.html +3157 -0
- data/site/guides/using-tools/index.html +3802 -0
- data/site/index.html +2671 -0
- data/site/search/search_index.json +1 -0
- data/site/sitemap.xml +183 -0
- data/site/sitemap.xml.gz +0 -0
- data/site/tags.json +1 -0
- data/temp.md +6 -0
- data/tool_manifest_plan.md +155 -0
- metadata +154 -92
- data/docs/examples/rails-application.md +0 -419
- data/docs/guides/ractor-parallelism.md +0 -364
- data/docs/guides/rails-integration.md +0 -681
- data/docs/superpowers/plans/2026-04-14-ractor-integration.md +0 -1538
- data/docs/superpowers/specs/2026-04-14-ractor-integration-design.md +0 -258
- data/lib/generators/robot_lab/install_generator.rb +0 -90
- data/lib/generators/robot_lab/job_generator.rb +0 -40
- data/lib/generators/robot_lab/robot_generator.rb +0 -55
- data/lib/generators/robot_lab/templates/initializer.rb.tt +0 -42
- data/lib/generators/robot_lab/templates/job.rb.tt +0 -21
- data/lib/generators/robot_lab/templates/migration.rb.tt +0 -32
- data/lib/generators/robot_lab/templates/result_model.rb.tt +0 -52
- data/lib/generators/robot_lab/templates/robot.rb.tt +0 -31
- data/lib/generators/robot_lab/templates/robot_job.rb.tt +0 -18
- data/lib/generators/robot_lab/templates/robot_test.rb.tt +0 -34
- data/lib/generators/robot_lab/templates/routing_robot.rb.tt +0 -59
- data/lib/generators/robot_lab/templates/thread_model.rb.tt +0 -40
- data/lib/robot_lab/document_store.rb +0 -155
- data/lib/robot_lab/ractor_boundary.rb +0 -42
- data/lib/robot_lab/ractor_job.rb +0 -37
- data/lib/robot_lab/ractor_memory_proxy.rb +0 -85
- data/lib/robot_lab/ractor_network_scheduler.rb +0 -154
- data/lib/robot_lab/ractor_worker_pool.rb +0 -117
- data/lib/robot_lab/rails_integration/engine.rb +0 -29
- data/lib/robot_lab/rails_integration/job.rb +0 -158
- data/lib/robot_lab/rails_integration/railtie.rb +0 -51
- 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:** `
|
|
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
|
|
data/examples/common.rb
ADDED
|
@@ -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
|