robot_lab 0.0.12 → 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 +108 -0
- data/CLAUDE.md +139 -0
- data/README.md +91 -45
- data/Rakefile +109 -3
- data/agent2agent_review.md +192 -0
- data/agentf_improvements.md +253 -0
- data/agents.md +14 -0
- data/docs/api/messages/index.md +21 -0
- data/docs/examples/index.md +37 -2
- data/docs/getting-started/configuration.md +20 -7
- data/docs/guides/creating-networks.md +23 -0
- 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/18_rails/app/jobs/robot_run_job.rb +15 -75
- 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 +235 -0
- 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 +10 -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 +5 -5
- data/lib/robot_lab/network.rb +12 -7
- 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 -48
- 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 +155 -90
- data/.github/workflows/deploy-yard-docs.yml +0 -52
- data/docs/examples/rails-application.md +0 -419
- data/docs/guides/ractor-parallelism.md +0 -364
- data/docs/guides/rails-integration.md +0 -642
- 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/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 -92
- 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_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/railtie.rb +0 -42
- data/lib/robot_lab/rails_integration/turbo_stream_callbacks.rb +0 -72
|
@@ -13,19 +13,14 @@
|
|
|
13
13
|
# Usage:
|
|
14
14
|
# ruby examples/25_history_search.rb
|
|
15
15
|
|
|
16
|
-
ENV["ROBOT_LAB_TEMPLATE_PATH"] ||= File.join(__dir__, "prompts")
|
|
17
|
-
|
|
18
16
|
require "json"
|
|
19
|
-
require_relative "
|
|
17
|
+
require_relative "common"
|
|
20
18
|
|
|
21
19
|
CONVERSATION_TURNS = File.readlines(
|
|
22
20
|
File.join(__dir__, "25_history_search", "conversation.jsonl"), chomp: true
|
|
23
21
|
).map { |line| JSON.parse(line, symbolize_names: true) }.freeze
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
puts "Example 25: Chat History Search"
|
|
27
|
-
puts "=" * 60
|
|
28
|
-
puts
|
|
23
|
+
banner "Chat History Search"
|
|
29
24
|
|
|
30
25
|
# ---------------------------------------------------------------------------
|
|
31
26
|
# Minimal message stub — populates history without LLM calls
|
|
@@ -35,7 +30,7 @@ FakeMsg = Struct.new(:role, :content, :tool_calls)
|
|
|
35
30
|
# ---------------------------------------------------------------------------
|
|
36
31
|
# Build a robot and inject the conversation fixture
|
|
37
32
|
# ---------------------------------------------------------------------------
|
|
38
|
-
robot = RobotLab.build(name: "tech_lead", system_prompt: "You are a senior engineering advisor.")
|
|
33
|
+
robot = RobotLab.build(model: LLM[:default].model, name: "tech_lead", system_prompt: "You are a senior engineering advisor.")
|
|
39
34
|
|
|
40
35
|
messages = CONVERSATION_TURNS.map { |t| FakeMsg.new(t[:role], t[:content], nil) }
|
|
41
36
|
robot.instance_variable_get(:@chat).instance_variable_set(:@messages, messages)
|
|
@@ -88,7 +83,7 @@ end
|
|
|
88
83
|
# ---------------------------------------------------------------------------
|
|
89
84
|
# RAG pattern — retrieve the most relevant turns, then inject as context
|
|
90
85
|
# ---------------------------------------------------------------------------
|
|
91
|
-
|
|
86
|
+
section "RAG Pattern: Retrieve Context, Then Call LLM"
|
|
92
87
|
puts "(showing retrieved context — no actual LLM call)"
|
|
93
88
|
puts
|
|
94
89
|
|
|
@@ -111,9 +106,7 @@ puts
|
|
|
111
106
|
# ---------------------------------------------------------------------------
|
|
112
107
|
# When to use search_history
|
|
113
108
|
# ---------------------------------------------------------------------------
|
|
114
|
-
|
|
115
|
-
puts "When to use search_history"
|
|
116
|
-
puts "=" * 60
|
|
109
|
+
section "When to Use search_history"
|
|
117
110
|
puts <<~'TEXT'
|
|
118
111
|
|
|
119
112
|
Without search_history:
|
|
@@ -13,14 +13,10 @@
|
|
|
13
13
|
# ruby examples/26_document_store.rb
|
|
14
14
|
# (Downloads the ~23 MB ONNX model on first run; cached afterwards.)
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
require_relative "common"
|
|
17
|
+
require "robot_lab/document_store"
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
puts "=" * 60
|
|
21
|
-
puts "Example 26: Embedding-Based Document Store"
|
|
22
|
-
puts "=" * 60
|
|
23
|
-
puts
|
|
19
|
+
banner "Embedding-Based Document Store"
|
|
24
20
|
puts "Note: First run downloads the fastembed model (~23 MB, cached)."
|
|
25
21
|
puts
|
|
26
22
|
|
|
@@ -97,7 +93,7 @@ end
|
|
|
97
93
|
# ---------------------------------------------------------------------------
|
|
98
94
|
# Delete and verify
|
|
99
95
|
# ---------------------------------------------------------------------------
|
|
100
|
-
|
|
96
|
+
section "Delete :redis_caching_guide, Re-run Cache Query"
|
|
101
97
|
store.delete(:redis_caching_guide)
|
|
102
98
|
results = store.search("Redis evicting keys unexpectedly", limit: 2)
|
|
103
99
|
puts " Remaining keys: #{store.keys.inspect}"
|
|
@@ -107,7 +103,7 @@ puts
|
|
|
107
103
|
# ---------------------------------------------------------------------------
|
|
108
104
|
# Memory integration
|
|
109
105
|
# ---------------------------------------------------------------------------
|
|
110
|
-
|
|
106
|
+
section "Memory Integration"
|
|
111
107
|
memory = RobotLab::Memory.new(enable_cache: false)
|
|
112
108
|
|
|
113
109
|
DOCUMENTS.each { |key, text| memory.store_document(key, text) }
|
|
@@ -124,10 +120,7 @@ puts
|
|
|
124
120
|
# ---------------------------------------------------------------------------
|
|
125
121
|
# RAG pattern
|
|
126
122
|
# ---------------------------------------------------------------------------
|
|
127
|
-
|
|
128
|
-
puts "RAG Pattern: retrieve relevant docs, then generate with LLM"
|
|
129
|
-
puts "=" * 60
|
|
130
|
-
puts
|
|
123
|
+
section "RAG Pattern: Retrieve Relevant Docs, Then Generate with LLM"
|
|
131
124
|
|
|
132
125
|
rag_query = "Our Sidekiq jobs exhaust retries and land in the dead queue after a Stripe outage."
|
|
133
126
|
|
|
@@ -38,11 +38,9 @@
|
|
|
38
38
|
|
|
39
39
|
ENV["ROBOT_LAB_TEMPLATE_PATH"] ||= File.join(__dir__, "../prompts")
|
|
40
40
|
|
|
41
|
-
require_relative "
|
|
41
|
+
require_relative "../common"
|
|
42
42
|
require "fileutils"
|
|
43
43
|
|
|
44
|
-
RubyLLM.configure { |c| c.logger = Logger.new(File::NULL) }
|
|
45
|
-
|
|
46
44
|
OUTPUT_DIR = File.join(__dir__, "output")
|
|
47
45
|
FileUtils.mkdir_p(OUTPUT_DIR)
|
|
48
46
|
|
|
@@ -62,6 +60,7 @@ class SREScout < RobotLab::Robot
|
|
|
62
60
|
def initialize(name:, subsystem:, memory_key:, bus: nil)
|
|
63
61
|
super(
|
|
64
62
|
name: name,
|
|
63
|
+
model: LLM[:default].model,
|
|
65
64
|
system_prompt: "You are a senior SRE responding to a production outage. " \
|
|
66
65
|
"Diagnose one infrastructure layer in 2 sentences: " \
|
|
67
66
|
"first sentence is root cause, second is customer impact.",
|
|
@@ -112,7 +111,7 @@ class WarRoom < RobotLab::Robot
|
|
|
112
111
|
attr_reader :updates
|
|
113
112
|
|
|
114
113
|
def initialize(bus:)
|
|
115
|
-
super(name: "war_room", system_prompt: "SRE war-room coordinator.", bus: bus)
|
|
114
|
+
super(name: "war_room", model: LLM[:default].model, system_prompt: "SRE war-room coordinator.", bus: bus)
|
|
116
115
|
@updates = []
|
|
117
116
|
@delivery_mutex = Mutex.new # only for reading @updates outside Async
|
|
118
117
|
|
|
@@ -178,7 +177,7 @@ db_scout = SREScout.new(name: "db_scout", subsystem: "database", memory_key
|
|
|
178
177
|
net_scout = SREScout.new(name: "net_scout", subsystem: "network", memory_key: :net_finding, bus: bus)
|
|
179
178
|
app_scout = SREScout.new(name: "app_scout", subsystem: "application", memory_key: :app_finding, bus: bus)
|
|
180
179
|
war_room = WarRoom.new(bus: bus)
|
|
181
|
-
commander = IncidentCommander.new(name: "commander", system_prompt: "SRE incident commander.")
|
|
180
|
+
commander = IncidentCommander.new(name: "commander", model: LLM[:default].model, system_prompt: "SRE incident commander.")
|
|
182
181
|
|
|
183
182
|
# Build the investigation network
|
|
184
183
|
# poller_group: labels are registered on the network's shared BusPoller.
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
# == Key config
|
|
13
13
|
#
|
|
14
14
|
# robot = RobotLab.build(
|
|
15
|
+
# model: "gpt-5.4",
|
|
15
16
|
# mcp_discovery: true, # ← enables semantic filtering
|
|
16
17
|
# mcp: [ ... ] # ← candidate servers, each with :description
|
|
17
18
|
# )
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
# Usage:
|
|
30
31
|
# bundle exec ruby examples/28_mcp_discovery.rb
|
|
31
32
|
|
|
32
|
-
require_relative "
|
|
33
|
+
require_relative "common"
|
|
33
34
|
|
|
34
35
|
# Three representative MCP server configurations
|
|
35
36
|
SERVERS = [
|
|
@@ -59,10 +60,8 @@ def show_query(label, query)
|
|
|
59
60
|
puts
|
|
60
61
|
end
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
puts "Example 28: MCP Server Discovery"
|
|
63
|
+
banner "MCP Server Discovery"
|
|
64
64
|
puts " Semantic server selection via TF cosine similarity"
|
|
65
|
-
puts "=" * 60
|
|
66
65
|
puts
|
|
67
66
|
puts "Candidate servers:"
|
|
68
67
|
SERVERS.each do |s|
|
|
@@ -70,14 +69,12 @@ SERVERS.each do |s|
|
|
|
70
69
|
end
|
|
71
70
|
puts
|
|
72
71
|
|
|
73
|
-
|
|
74
|
-
puts "-" * 60
|
|
72
|
+
section "Discovery Queries"
|
|
75
73
|
show_query("File ops", "read my config file")
|
|
76
74
|
show_query("Package mgmt", "install imagemagick via homebrew")
|
|
77
75
|
show_query("Code review", "list open pull requests on my repo")
|
|
78
76
|
|
|
79
|
-
|
|
80
|
-
puts "-" * 60
|
|
77
|
+
section "Fallback Cases"
|
|
81
78
|
|
|
82
79
|
# No description → all servers returned
|
|
83
80
|
no_desc_servers = SERVERS.map { |s| s.except(:description) }
|
|
@@ -93,10 +90,10 @@ result = RobotLab::MCP::ServerDiscovery.select("install imagemagick", from: SERV
|
|
|
93
90
|
puts " High threshold : returns all (#{result.size} servers) — no match above 1.0"
|
|
94
91
|
|
|
95
92
|
puts
|
|
96
|
-
|
|
97
|
-
puts "-" * 60
|
|
93
|
+
section "mcp_discovery: true on a Robot"
|
|
98
94
|
puts <<~NOTE
|
|
99
95
|
RobotLab.build(
|
|
96
|
+
model: "gpt-5.4",
|
|
100
97
|
name: "assistant",
|
|
101
98
|
mcp_discovery: true,
|
|
102
99
|
mcp: [
|
|
@@ -109,4 +106,4 @@ puts <<~NOTE
|
|
|
109
106
|
# Only the :brew server is connected for this message:
|
|
110
107
|
robot.run("install imagemagick")
|
|
111
108
|
NOTE
|
|
112
|
-
|
|
109
|
+
hr
|
data/examples/29_ractor_tools.rb
CHANGED
|
@@ -26,9 +26,8 @@
|
|
|
26
26
|
# SHA-256 rounds (~320 ms on modern hardware) so the 4-6× speedup is
|
|
27
27
|
# clearly visible on a 6-core machine.
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
require_relative "../lib/robot_lab"
|
|
29
|
+
require_relative "common"
|
|
30
|
+
require "robot_lab/ractor"
|
|
32
31
|
require "digest"
|
|
33
32
|
|
|
34
33
|
# Always shut down the pool when the process exits.
|
|
@@ -115,10 +114,7 @@ end
|
|
|
115
114
|
# Demo
|
|
116
115
|
# =============================================================================
|
|
117
116
|
|
|
118
|
-
|
|
119
|
-
puts "Example 29: Ractor-Safe CPU Tools"
|
|
120
|
-
puts "=" * 62
|
|
121
|
-
puts
|
|
117
|
+
banner "Ractor-Safe CPU Tools"
|
|
122
118
|
|
|
123
119
|
DIVIDER = ("─" * 54).freeze
|
|
124
120
|
|
|
@@ -238,6 +234,5 @@ puts " #{DIVIDER}"
|
|
|
238
234
|
RobotLab.shutdown_ractor_pool
|
|
239
235
|
puts " Pool shut down cleanly (poison-pill × #{pool.size} workers)."
|
|
240
236
|
puts
|
|
241
|
-
|
|
237
|
+
hr
|
|
242
238
|
puts "Example 29 complete."
|
|
243
|
-
puts "=" * 62
|
|
@@ -30,14 +30,10 @@
|
|
|
30
30
|
# bundle exec ruby examples/30_ractor_network.rb # Parts 1 & 2
|
|
31
31
|
# ANTHROPIC_API_KEY=key ruby examples/30_ractor_network.rb # Parts 1, 2 & 3
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
require_relative "common"
|
|
34
|
+
require "robot_lab/ractor"
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
puts "=" * 62
|
|
38
|
-
puts "Example 30: Ractor Network Scheduler"
|
|
39
|
-
puts "=" * 62
|
|
40
|
-
puts
|
|
36
|
+
banner "Ractor Network Scheduler"
|
|
41
37
|
|
|
42
38
|
DIVIDER = ("─" * 54).freeze
|
|
43
39
|
|
|
@@ -73,8 +69,7 @@ LATENCIES = {
|
|
|
73
69
|
#
|
|
74
70
|
# Speedup ≈ 1.7×
|
|
75
71
|
|
|
76
|
-
|
|
77
|
-
puts
|
|
72
|
+
section "Part 1: Simulated Parallel Run (no API key)"
|
|
78
73
|
|
|
79
74
|
# SimulatedScheduler overrides execute_spec so that instead of
|
|
80
75
|
# constructing a real Robot and calling the LLM, it just sleeps for
|
|
@@ -140,14 +135,13 @@ puts
|
|
|
140
135
|
# Part 2: Network.new(parallel_mode: :ractor) API
|
|
141
136
|
# =============================================================================
|
|
142
137
|
|
|
143
|
-
|
|
144
|
-
puts
|
|
138
|
+
section "Part 2: Network.new(parallel_mode: :ractor)"
|
|
145
139
|
|
|
146
140
|
# When parallel_mode: :ractor is set on a Network, network.run(message:)
|
|
147
141
|
# routes through RactorNetworkScheduler instead of SimpleFlow::Pipeline.
|
|
148
142
|
# The default mode is :async (unchanged SimpleFlow behavior).
|
|
149
143
|
|
|
150
|
-
model =
|
|
144
|
+
model = LLM[:default].model
|
|
151
145
|
|
|
152
146
|
network = RobotLab::Network.new(name: "research_pipeline", parallel_mode: :ractor) do
|
|
153
147
|
task :headline_finder, RobotLab.build(name: "headline_finder",
|
|
@@ -197,19 +191,17 @@ puts
|
|
|
197
191
|
# =============================================================================
|
|
198
192
|
|
|
199
193
|
unless ENV["ANTHROPIC_API_KEY"]
|
|
200
|
-
|
|
194
|
+
section "Part 3: Live LLM Run"
|
|
201
195
|
puts " Set ANTHROPIC_API_KEY to run the real pipeline."
|
|
202
196
|
puts " Expected behavior: headline_finder, background_brief, and"
|
|
203
197
|
puts " fact_checker run in parallel; report_writer follows."
|
|
204
198
|
puts
|
|
205
|
-
|
|
199
|
+
hr
|
|
206
200
|
puts "Example 30 complete."
|
|
207
|
-
puts "=" * 62
|
|
208
201
|
exit 0
|
|
209
202
|
end
|
|
210
203
|
|
|
211
|
-
|
|
212
|
-
puts
|
|
204
|
+
section "Part 3: Live LLM Run (ANTHROPIC_API_KEY detected)"
|
|
213
205
|
|
|
214
206
|
puts " Running 4-robot research pipeline on:"
|
|
215
207
|
puts " \"#{topic}\""
|
|
@@ -251,6 +243,5 @@ rescue RobotLab::Error => e
|
|
|
251
243
|
end
|
|
252
244
|
|
|
253
245
|
puts
|
|
254
|
-
|
|
246
|
+
hr
|
|
255
247
|
puts "Example 30 complete."
|
|
256
|
-
puts "=" * 62
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Example 31: Product Launch Assessment — 6 Parallel Analysts, Cap of 4
|
|
5
|
+
#
|
|
6
|
+
# Six specialist robots evaluate a product launch simultaneously.
|
|
7
|
+
# max_concurrent_robots: 4 ensures at most 4 LLM API calls are in-flight
|
|
8
|
+
# at once. Robots 5 and 6 queue behind the Async::Semaphore and start as
|
|
9
|
+
# soon as any of the first 4 finishes — providing natural back-pressure
|
|
10
|
+
# without slowing the pipeline more than necessary.
|
|
11
|
+
#
|
|
12
|
+
# Architecture:
|
|
13
|
+
#
|
|
14
|
+
# ┌──────────────────────────────────────────────────────────────────────┐
|
|
15
|
+
# │ PARALLEL ANALYSIS PHASE (max_concurrent_robots: 4) │
|
|
16
|
+
# │ │
|
|
17
|
+
# │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
|
18
|
+
# │ │ Market │ │ Compet. │ │ Tech │ │ Risk │ slots 1-4 │
|
|
19
|
+
# │ │ Analyst │ │ Analyst │ │ Reviewer │ │ Assessor │ │
|
|
20
|
+
# │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
|
|
21
|
+
# │ │ │ │ │ │
|
|
22
|
+
# │ start start start start │
|
|
23
|
+
# │ │
|
|
24
|
+
# │ ┌──────────┐ ┌──────────┐ │
|
|
25
|
+
# │ │Financial │ │ Legal │ queued — start when a slot opens │
|
|
26
|
+
# │ │ Reviewer │ │ Reviewer │ │
|
|
27
|
+
# │ └────┬─────┘ └────┬─────┘ │
|
|
28
|
+
# │ │ │ │
|
|
29
|
+
# │ (deferred) (deferred) │
|
|
30
|
+
# │ │
|
|
31
|
+
# │ ┌─────────────────────────────────────────────────────────────┐ │
|
|
32
|
+
# │ │ SHARED MEMORY │ │
|
|
33
|
+
# │ │ :market :competitive :tech :risk :financial :legal │ │
|
|
34
|
+
# │ └─────────────────────────────────────────────────────────────┘ │
|
|
35
|
+
# │ │ │
|
|
36
|
+
# │ ▼ │
|
|
37
|
+
# │ ┌─────────────────────────────────────────────────────────────┐ │
|
|
38
|
+
# │ │ Launch Director │ │
|
|
39
|
+
# │ │ Blocks on reactive memory until all 6 findings arrive, │ │
|
|
40
|
+
# │ │ then issues a GO / NO-GO recommendation. │ │
|
|
41
|
+
# │ └─────────────────────────────────────────────────────────────┘ │
|
|
42
|
+
# └──────────────────────────────────────────────────────────────────────┘
|
|
43
|
+
#
|
|
44
|
+
# Key config:
|
|
45
|
+
# RunConfig.new(max_concurrent_robots: 4)
|
|
46
|
+
#
|
|
47
|
+
# Usage:
|
|
48
|
+
# ANTHROPIC_API_KEY=your_key ruby examples/31_launch_assessment.rb
|
|
49
|
+
|
|
50
|
+
require_relative "common"
|
|
51
|
+
|
|
52
|
+
# ── AnalystRobot ────────────────────────────────────────────────────────────────
|
|
53
|
+
#
|
|
54
|
+
# Runs its LLM call, writes the verdict to shared memory, and logs a timing line
|
|
55
|
+
# so you can see when the semaphore releases robots 5 and 6.
|
|
56
|
+
|
|
57
|
+
class AnalystRobot < RobotLab::Robot
|
|
58
|
+
attr_reader :memory_key
|
|
59
|
+
attr_writer :shared_memory
|
|
60
|
+
|
|
61
|
+
def initialize(name:, memory_key:, role:)
|
|
62
|
+
super(
|
|
63
|
+
name: name,
|
|
64
|
+
model: LLM[:default].model,
|
|
65
|
+
system_prompt: "You are a #{role}. " \
|
|
66
|
+
"Review the product brief in 2-3 crisp sentences from your area of expertise. " \
|
|
67
|
+
"Close with a one-word verdict: READY or NOT-READY."
|
|
68
|
+
)
|
|
69
|
+
@memory_key = memory_key
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def call(result)
|
|
73
|
+
brief = extract_brief(result)
|
|
74
|
+
started = Time.now
|
|
75
|
+
puts " [#{name}] started at +#{"%.1f" % (started - $run_start)}s"
|
|
76
|
+
|
|
77
|
+
verdict = run(brief).reply.strip
|
|
78
|
+
|
|
79
|
+
elapsed = "%.1f" % (Time.now - started)
|
|
80
|
+
puts " [#{name}] finished in #{elapsed}s — #{verdict.split.last(2).join(" ")}"
|
|
81
|
+
|
|
82
|
+
if @shared_memory
|
|
83
|
+
@shared_memory.current_writer = name
|
|
84
|
+
@shared_memory.set(@memory_key, verdict)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
result.with_context(name.to_sym, verdict).continue(verdict)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def extract_brief(result)
|
|
93
|
+
case result.value
|
|
94
|
+
when Hash then result.value[:message].to_s
|
|
95
|
+
when RobotLab::RobotResult then result.value.reply.to_s
|
|
96
|
+
else result.value.to_s
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# ── LaunchDirector ──────────────────────────────────────────────────────────────
|
|
102
|
+
#
|
|
103
|
+
# Waits for all 6 findings via reactive memory, then issues the final call.
|
|
104
|
+
# SimpleFlow guarantees the six analyst tasks are done before this task runs,
|
|
105
|
+
# so the memory.get is effectively a non-blocking read by the time we arrive here.
|
|
106
|
+
|
|
107
|
+
class LaunchDirector < RobotLab::Robot
|
|
108
|
+
attr_writer :shared_memory
|
|
109
|
+
|
|
110
|
+
FINDING_KEYS = %i[market competitive tech risk financial legal].freeze
|
|
111
|
+
|
|
112
|
+
def call(result)
|
|
113
|
+
puts " [#{name}] reading all findings from shared memory..."
|
|
114
|
+
findings = @shared_memory.get(*FINDING_KEYS, wait: 120)
|
|
115
|
+
|
|
116
|
+
if findings.values.any? { |v| v == :timeout }
|
|
117
|
+
timed_out = findings.select { |_, v| v == :timeout }.keys
|
|
118
|
+
puts " [#{name}] WARNING: timed out waiting for: #{timed_out.join(", ")}"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
prompt = <<~PROMPT
|
|
122
|
+
Six specialist analysts have reviewed our product launch readiness.
|
|
123
|
+
Based on their findings, issue a final GO or NO-GO recommendation
|
|
124
|
+
in 3-5 sentences. Be direct and specific about the key deciding factors.
|
|
125
|
+
|
|
126
|
+
Market analysis: #{findings[:market] || "(not received)"}
|
|
127
|
+
Competitive analysis: #{findings[:competitive] || "(not received)"}
|
|
128
|
+
Technical review: #{findings[:tech] || "(not received)"}
|
|
129
|
+
Risk assessment: #{findings[:risk] || "(not received)"}
|
|
130
|
+
Financial review: #{findings[:financial] || "(not received)"}
|
|
131
|
+
Legal review: #{findings[:legal] || "(not received)"}
|
|
132
|
+
|
|
133
|
+
Begin your response with "GO -" or "NO-GO -".
|
|
134
|
+
PROMPT
|
|
135
|
+
|
|
136
|
+
recommendation = run(prompt).reply.strip
|
|
137
|
+
@shared_memory.set(:recommendation, recommendation)
|
|
138
|
+
puts " [#{name}] recommendation ready"
|
|
139
|
+
|
|
140
|
+
result.with_context(:recommendation, recommendation).continue(recommendation)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# ── Product Brief ───────────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
PRODUCT_BRIEF = <<~BRIEF
|
|
147
|
+
Product: "Orion" — an AI-powered project management tool that auto-generates
|
|
148
|
+
sprint plans from Jira backlogs, detects scope creep in real-time, and integrates
|
|
149
|
+
with GitHub and Slack via webhooks. SaaS pricing: $25/seat/month, 14-day free trial.
|
|
150
|
+
Target: mid-size engineering teams (20-200 developers). Launch date: 6 weeks out.
|
|
151
|
+
Beta: 12 paying customers, 94% satisfaction, 0 critical bugs open. SOC 2 Type I
|
|
152
|
+
certification in progress, expected within 30 days.
|
|
153
|
+
BRIEF
|
|
154
|
+
|
|
155
|
+
# ── Build the six analysts ───────────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
ANALYSTS = [
|
|
158
|
+
{ name: "market_analyst", key: :market, role: "market opportunity analyst" },
|
|
159
|
+
{ name: "competitive_analyst", key: :competitive, role: "competitive intelligence analyst" },
|
|
160
|
+
{ name: "tech_reviewer", key: :tech, role: "technical readiness and quality reviewer" },
|
|
161
|
+
{ name: "risk_assessor", key: :risk, role: "product risk assessment specialist" },
|
|
162
|
+
{ name: "financial_reviewer", key: :financial, role: "financial viability and pricing analyst" },
|
|
163
|
+
{ name: "legal_reviewer", key: :legal, role: "legal, compliance, and IP reviewer" },
|
|
164
|
+
].freeze
|
|
165
|
+
|
|
166
|
+
analyst_robots = ANALYSTS.map do |spec|
|
|
167
|
+
AnalystRobot.new(name: spec[:name], memory_key: spec[:key], role: spec[:role])
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
director = LaunchDirector.new(
|
|
171
|
+
name: "launch_director",
|
|
172
|
+
model: LLM[:default].model,
|
|
173
|
+
system_prompt: "You are the VP of Product making the final launch call."
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# ── Network — note the concurrency cap ──────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
config = RobotLab::RunConfig.new(max_concurrent_robots: 4)
|
|
179
|
+
|
|
180
|
+
analyst_names = analyst_robots.map { |r| r.name.to_sym }
|
|
181
|
+
|
|
182
|
+
network = RobotLab.create_network(name: "launch_assessment", config: config) do
|
|
183
|
+
analyst_robots.each do |robot|
|
|
184
|
+
task robot.name.to_sym, robot, depends_on: :none
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
task :launch_director, director, depends_on: analyst_names
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Assign shared memory so each robot can write to it directly
|
|
191
|
+
shared_memory = network.memory
|
|
192
|
+
(analyst_robots + [director]).each { |r| r.shared_memory = shared_memory }
|
|
193
|
+
|
|
194
|
+
# Subscribe for a memory-level audit trail
|
|
195
|
+
analyst_robots.each do |robot|
|
|
196
|
+
network.memory.subscribe(robot.memory_key) do |change|
|
|
197
|
+
puts " [memory] :#{change.key} written by #{change.writer}"
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# ── Run ─────────────────────────────────────────────────────────────────────────
|
|
202
|
+
|
|
203
|
+
banner "Product Launch Assessment"
|
|
204
|
+
puts " 6 specialist analysts in parallel, max_concurrent_robots: 4"
|
|
205
|
+
puts
|
|
206
|
+
puts "Pipeline:"
|
|
207
|
+
puts network.visualize
|
|
208
|
+
puts
|
|
209
|
+
puts "Concurrency config: #{config.inspect}"
|
|
210
|
+
puts
|
|
211
|
+
puts "Product brief:"
|
|
212
|
+
puts PRODUCT_BRIEF.strip.gsub(/^/, " ")
|
|
213
|
+
puts
|
|
214
|
+
section "Running — Analysts 5 and 6 Queue Until a Semaphore Slot Opens"
|
|
215
|
+
|
|
216
|
+
$run_start = Time.now
|
|
217
|
+
result = network.run(message: PRODUCT_BRIEF)
|
|
218
|
+
elapsed = "%.1f" % (Time.now - $run_start)
|
|
219
|
+
|
|
220
|
+
puts
|
|
221
|
+
hr
|
|
222
|
+
puts "All analysts complete. Total wall time: #{elapsed}s"
|
|
223
|
+
hr
|
|
224
|
+
|
|
225
|
+
section "Launch Director Recommendation"
|
|
226
|
+
puts network.memory[:recommendation]
|
|
227
|
+
puts
|
|
228
|
+
section "Individual Analyst Verdicts"
|
|
229
|
+
analyst_robots.each do |robot|
|
|
230
|
+
label = robot.name.gsub("_", " ").upcase
|
|
231
|
+
finding = network.memory.get(robot.memory_key).to_s
|
|
232
|
+
puts "#{label}"
|
|
233
|
+
puts finding
|
|
234
|
+
puts
|
|
235
|
+
end
|