robot_lab 0.2.1 → 0.2.7
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/.envrc +1 -0
- data/.loki +60 -0
- data/.quality/reek_baseline.txt +43 -0
- data/.rubocop.yml +5 -167
- data/CHANGELOG.md +54 -0
- data/README.md +185 -64
- data/Rakefile +28 -12
- data/docs/api/core/index.md +41 -14
- data/docs/api/core/memory.md +247 -29
- data/docs/api/core/network.md +285 -33
- data/docs/api/core/result.md +120 -32
- data/docs/api/core/robot.md +636 -60
- data/docs/api/core/state.md +87 -197
- data/docs/api/core/tool.md +165 -20
- data/docs/api/errors.md +152 -14
- data/docs/api/hooks.md +469 -0
- data/docs/api/index.md +83 -7
- data/docs/api/mcp/client.md +129 -35
- data/docs/api/mcp/index.md +164 -23
- data/docs/api/mcp/server.md +27 -3
- data/docs/api/mcp/transports.md +94 -22
- data/docs/api/messages/index.md +26 -3
- data/docs/api/messages/text-message.md +33 -11
- data/docs/api/messages/tool-call-message.md +27 -4
- data/docs/api/messages/tool-result-message.md +23 -4
- data/docs/api/messages/user-message.md +45 -8
- data/docs/api/skills.md +519 -0
- data/docs/api/streaming/context.md +28 -5
- data/docs/api/streaming/index.md +57 -11
- data/docs/api/support.md +846 -0
- data/docs/architecture/core-concepts.md +118 -30
- data/docs/architecture/index.md +86 -11
- data/docs/architecture/message-flow.md +66 -29
- data/docs/architecture/network-orchestration.md +153 -38
- data/docs/architecture/robot-execution.md +173 -91
- data/docs/architecture/state-management.md +31 -12
- data/docs/concepts.md +176 -21
- data/docs/examples/basic-chat.md +72 -19
- data/docs/examples/index.md +117 -31
- data/docs/examples/mcp-server.md +154 -45
- data/docs/examples/multi-robot-network.md +91 -21
- data/docs/examples/tool-usage.md +104 -37
- data/docs/getting-started/configuration.md +299 -93
- data/docs/getting-started/installation.md +53 -41
- data/docs/getting-started/quick-start.md +51 -6
- data/docs/guides/building-robots.md +296 -50
- data/docs/guides/creating-networks.md +220 -31
- data/docs/guides/hooks.md +1084 -0
- data/docs/guides/index.md +5 -0
- data/docs/guides/knowledge.md +37 -6
- data/docs/guides/mcp-integration.md +211 -44
- data/docs/guides/memory.md +103 -12
- data/docs/guides/observability.md +166 -51
- data/docs/guides/streaming.md +184 -125
- data/docs/guides/using-tools.md +300 -11
- data/docs/index.md +37 -5
- data/examples/01_simple_robot.rb +2 -2
- data/examples/02_tools.rb +14 -4
- data/examples/03_network.rb +23 -9
- data/examples/04_mcp.rb +11 -4
- data/examples/05_streaming.rb +8 -5
- data/examples/06_prompt_templates.rb +13 -9
- data/examples/07_network_memory.rb +5 -5
- data/examples/08_llm_config.rb +20 -15
- data/examples/09_chaining.rb +4 -4
- data/examples/11_network_introspection.rb +4 -4
- data/examples/12_message_bus.rb +2 -2
- data/examples/13_spawn.rb +2 -2
- data/examples/14_rusty_circuit/README.md +1 -0
- data/examples/14_rusty_circuit/comic.rb +7 -3
- data/examples/14_rusty_circuit/display.rb +14 -0
- data/examples/14_rusty_circuit/heckler.rb +8 -6
- data/examples/14_rusty_circuit/open_mic.rb +17 -6
- data/examples/14_rusty_circuit/scout.rb +17 -10
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +14 -10
- data/examples/15_memory_network_and_bus/linux_writer.rb +2 -2
- data/examples/15_memory_network_and_bus/os_editor.rb +3 -1
- data/examples/15_memory_network_and_bus/os_writer.rb +4 -1
- data/examples/16_writers_room/writer.rb +22 -22
- data/examples/16_writers_room/writers_room.rb +2 -0
- data/examples/17_skills.rb +14 -13
- data/examples/18_rails/README.md +20 -1
- data/examples/18_rails/app/controllers/chat_controller.rb +5 -1
- data/examples/18_rails/app/jobs/robot_run_job.rb +11 -5
- data/examples/18_rails/app/robots/chat_robot.rb +11 -0
- data/examples/18_rails/config/initializers/robot_lab.rb +8 -0
- data/examples/19_token_tracking.rb +25 -9
- data/examples/20_circuit_breaker.rb +10 -7
- data/examples/21_learning_loop.rb +42 -16
- data/examples/22_context_compression.rb +23 -23
- data/examples/23_convergence.rb +24 -17
- data/examples/24_structured_delegation.rb +13 -8
- data/examples/25_history_search.rb +12 -8
- data/examples/27_incident_response/incident_response.rb +31 -13
- data/examples/28_mcp_discovery.rb +17 -13
- data/examples/29_ractor_tools.rb +4 -2
- data/examples/30_ractor_network.rb +22 -17
- data/examples/31_launch_assessment.rb +20 -9
- data/examples/32_newsletter_reader.rb +7 -2
- data/examples/33_stock_predictor.rb +34 -13
- data/examples/34_agentskills.rb +7 -3
- data/examples/35_hooks.rb +266 -0
- data/examples/README.md +203 -42
- data/examples/common.rb +79 -7
- data/examples/xyzzy.rb +97 -0
- data/lib/robot_lab/agent_skill.rb +5 -4
- data/lib/robot_lab/budget/ledger.rb +98 -0
- data/lib/robot_lab/capabilities.rb +84 -0
- data/lib/robot_lab/config/defaults.yml +10 -0
- data/lib/robot_lab/config.rb +14 -9
- data/lib/robot_lab/error.rb +34 -4
- data/lib/robot_lab/errors.rb +45 -0
- data/lib/robot_lab/hook.rb +79 -0
- data/lib/robot_lab/hook_context.rb +194 -0
- data/lib/robot_lab/hook_registry.rb +55 -0
- data/lib/robot_lab/hooks.rb +87 -0
- data/lib/robot_lab/mcp/connection_poller.rb +2 -2
- data/lib/robot_lab/names.rb +402 -0
- data/lib/robot_lab/narrator.rb +87 -0
- data/lib/robot_lab/network.rb +77 -20
- data/lib/robot_lab/robot/agent_skill_matching.rb +1 -3
- data/lib/robot_lab/robot/budget.rb +89 -0
- data/lib/robot_lab/robot/bus_messaging.rb +90 -18
- data/lib/robot_lab/robot/hooking.rb +56 -0
- data/lib/robot_lab/robot/mcp_management.rb +8 -2
- data/lib/robot_lab/robot/template_rendering.rb +18 -5
- data/lib/robot_lab/robot.rb +233 -89
- data/lib/robot_lab/run_config.rb +24 -5
- data/lib/robot_lab/runnable.rb +51 -0
- data/lib/robot_lab/sandbox/null.rb +13 -0
- data/lib/robot_lab/sandbox/seatbelt.rb +104 -0
- data/lib/robot_lab/sandbox.rb +52 -0
- data/lib/robot_lab/script_tool.rb +60 -2
- data/lib/robot_lab/task.rb +26 -20
- data/lib/robot_lab/tool.rb +52 -11
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab.rb +70 -15
- data/mkdocs.yml +7 -1
- metadata +41 -99
- data/examples/temp.md +0 -51
- data/site/404.html +0 -2300
- data/site/api/core/index.html +0 -2706
- data/site/api/core/memory/index.html +0 -3793
- data/site/api/core/network/index.html +0 -3500
- data/site/api/core/robot/index.html +0 -4566
- data/site/api/core/state/index.html +0 -3390
- data/site/api/core/tool/index.html +0 -3843
- data/site/api/index.html +0 -2635
- data/site/api/mcp/client/index.html +0 -3435
- data/site/api/mcp/index.html +0 -2783
- data/site/api/mcp/server/index.html +0 -3252
- data/site/api/mcp/transports/index.html +0 -3352
- data/site/api/messages/index.html +0 -2641
- data/site/api/messages/text-message/index.html +0 -3087
- data/site/api/messages/tool-call-message/index.html +0 -3159
- data/site/api/messages/tool-result-message/index.html +0 -3252
- data/site/api/messages/user-message/index.html +0 -3212
- data/site/api/streaming/context/index.html +0 -3282
- data/site/api/streaming/events/index.html +0 -3347
- data/site/api/streaming/index.html +0 -2738
- data/site/architecture/core-concepts/index.html +0 -3757
- data/site/architecture/index.html +0 -2797
- data/site/architecture/message-flow/index.html +0 -3238
- data/site/architecture/network-orchestration/index.html +0 -3433
- data/site/architecture/robot-execution/index.html +0 -3140
- data/site/architecture/state-management/index.html +0 -3498
- data/site/assets/css/custom.css +0 -56
- 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 +0 -16
- data/site/assets/javascripts/bundle.79ae519e.min.js.map +0 -7
- data/site/assets/javascripts/lunr/min/lunr.ar.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.da.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.de.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.du.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.el.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.es.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.fi.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.fr.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.he.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.hi.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.hu.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.hy.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.it.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.ja.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.jp.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.kn.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.ko.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.multi.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.nl.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.no.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.pt.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.ro.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.ru.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.sa.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.stemmer.support.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.sv.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.ta.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.te.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.th.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.tr.min.js +0 -18
- data/site/assets/javascripts/lunr/min/lunr.vi.min.js +0 -1
- data/site/assets/javascripts/lunr/min/lunr.zh.min.js +0 -1
- data/site/assets/javascripts/lunr/tinyseg.js +0 -206
- data/site/assets/javascripts/lunr/wordcut.js +0 -6708
- data/site/assets/javascripts/workers/search.2c215733.min.js +0 -42
- data/site/assets/javascripts/workers/search.2c215733.min.js.map +0 -7
- data/site/assets/stylesheets/main.484c7ddc.min.css +0 -1
- data/site/assets/stylesheets/main.484c7ddc.min.css.map +0 -1
- data/site/assets/stylesheets/palette.ab4e12ef.min.css +0 -1
- data/site/assets/stylesheets/palette.ab4e12ef.min.css.map +0 -1
- data/site/concepts/index.html +0 -3455
- data/site/examples/basic-chat/index.html +0 -2880
- data/site/examples/index.html +0 -2907
- data/site/examples/mcp-server/index.html +0 -3018
- data/site/examples/multi-robot-network/index.html +0 -3131
- data/site/examples/rails-application/index.html +0 -3329
- data/site/examples/tool-usage/index.html +0 -3085
- data/site/getting-started/configuration/index.html +0 -3745
- data/site/getting-started/index.html +0 -2572
- data/site/getting-started/installation/index.html +0 -2981
- data/site/getting-started/quick-start/index.html +0 -2942
- data/site/guides/building-robots/index.html +0 -4290
- data/site/guides/creating-networks/index.html +0 -3858
- data/site/guides/index.html +0 -2586
- data/site/guides/mcp-integration/index.html +0 -3581
- data/site/guides/memory/index.html +0 -3586
- data/site/guides/rails-integration/index.html +0 -4019
- data/site/guides/streaming/index.html +0 -3157
- data/site/guides/using-tools/index.html +0 -3802
- data/site/index.html +0 -2671
- data/site/search/search_index.json +0 -1
- data/site/sitemap.xml +0 -183
- data/site/sitemap.xml.gz +0 -0
- data/site/tags.json +0 -1
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Example 35: Hooks Architecture with robot_lab-xyzzy
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates the hook handler class pattern. Hooks are named classes that
|
|
7
|
+
# inherit from RobotLab::Hook and implement class methods for each lifecycle
|
|
8
|
+
# phase they care about. All hooks are Ractor-safe by default.
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# bundle exec ruby examples/35_hooks.rb
|
|
12
|
+
|
|
13
|
+
require_relative "common"
|
|
14
|
+
|
|
15
|
+
# RobotLab::Hook has no logger accessor — handlers own their own output.
|
|
16
|
+
# xyzzy.rb freezes its log destination into a constant at load time, so the
|
|
17
|
+
# redirect has to happen before the require.
|
|
18
|
+
XYZZY_LOG = File.join(__dir__, "xyzzy_hooks.log")
|
|
19
|
+
ENV["XYZZY_LOG_PATH"] = XYZZY_LOG
|
|
20
|
+
|
|
21
|
+
require_relative "xyzzy"
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# Handler classes used in this demo
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
class RuntimeDemoHook < RobotLab::Hook
|
|
28
|
+
self.namespace = :runtime_demo
|
|
29
|
+
|
|
30
|
+
def self.after_run(ctx)
|
|
31
|
+
ctx.local.reply_seen = ctx.response.reply
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class LocalDemoHook < RobotLab::Hook
|
|
36
|
+
self.namespace = :local_demo
|
|
37
|
+
|
|
38
|
+
def self.before_run(ctx)
|
|
39
|
+
ctx.local.note = "local robot hook state"
|
|
40
|
+
ctx.local.demo = "robot_run"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class PerfHook < RobotLab::Hook
|
|
45
|
+
self.namespace = :perf
|
|
46
|
+
@timings = []
|
|
47
|
+
@mutex = Mutex.new
|
|
48
|
+
|
|
49
|
+
class << self
|
|
50
|
+
def reset!
|
|
51
|
+
@mutex.synchronize { @timings = [] }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def timings
|
|
55
|
+
@mutex.synchronize { @timings.dup }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def around_run(ctx, &block)
|
|
59
|
+
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
60
|
+
result = block.call
|
|
61
|
+
elapsed = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0) * 1_000).round(3)
|
|
62
|
+
@mutex.synchronize { @timings << elapsed }
|
|
63
|
+
puts " [perf] #{ctx.request.inspect} — #{elapsed}ms"
|
|
64
|
+
result
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
class GenerationTracerHook < RobotLab::Hook
|
|
70
|
+
self.namespace = :tracer
|
|
71
|
+
|
|
72
|
+
def self.before_llm_generation(ctx)
|
|
73
|
+
puts " [tracer] llm_generation start request=#{ctx.request.inspect}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.after_llm_generation(ctx)
|
|
77
|
+
msg = ctx.generation_response
|
|
78
|
+
puts " [tracer] llm_generation done model=#{msg&.model_id} tokens=#{msg&.output_tokens}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
class GenCacheHook < RobotLab::Hook
|
|
83
|
+
self.namespace = :gen_cache
|
|
84
|
+
@cache = {}
|
|
85
|
+
@mutex = Mutex.new
|
|
86
|
+
@call_count = 0
|
|
87
|
+
|
|
88
|
+
class << self
|
|
89
|
+
def reset!
|
|
90
|
+
@mutex.synchronize do
|
|
91
|
+
@cache = {}
|
|
92
|
+
@call_count = 0
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def call_count
|
|
97
|
+
@mutex.synchronize { @call_count }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def around_llm_generation(ctx, &block)
|
|
101
|
+
cached = @mutex.synchronize { @cache[ctx.request] }
|
|
102
|
+
if cached
|
|
103
|
+
puts " [gen_cache] HIT — skipping LLM"
|
|
104
|
+
cached
|
|
105
|
+
else
|
|
106
|
+
puts " [gen_cache] MISS — calling LLM"
|
|
107
|
+
@mutex.synchronize { @call_count += 1 }
|
|
108
|
+
block.call.tap { |r| @mutex.synchronize { @cache[ctx.request] = r } }
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
class NetworkDemoHook < RobotLab::Hook
|
|
115
|
+
self.namespace = :network_demo
|
|
116
|
+
|
|
117
|
+
def self.before_task(ctx)
|
|
118
|
+
ctx.local.task_note = "network-scoped task hook"
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
HookDemoResponse = Data.define(:content, :tool_calls, :stop_reason) do
|
|
125
|
+
def initialize(content:, tool_calls: nil, stop_reason: "end_turn")
|
|
126
|
+
super
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
class HookDemoTool < RobotLab::Tool
|
|
131
|
+
description "Returns a deterministic hook demo value"
|
|
132
|
+
param :label, type: "string", desc: "The label to echo"
|
|
133
|
+
|
|
134
|
+
def execute(label:)
|
|
135
|
+
{ label: label, status: "handled by HookDemoTool" }
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
class HookDemo
|
|
140
|
+
def run
|
|
141
|
+
banner "Hooks Architecture with robot_lab-xyzzy"
|
|
142
|
+
|
|
143
|
+
explain_setup
|
|
144
|
+
run_robot
|
|
145
|
+
run_llm_loop
|
|
146
|
+
run_tool
|
|
147
|
+
run_network
|
|
148
|
+
run_error
|
|
149
|
+
|
|
150
|
+
hr
|
|
151
|
+
puts "Hook demo complete."
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private
|
|
155
|
+
|
|
156
|
+
def explain_setup
|
|
157
|
+
section "Extension Registration"
|
|
158
|
+
puts <<~TEXT
|
|
159
|
+
xyzzy.rb is a single-file hook extension loaded from examples/.
|
|
160
|
+
|
|
161
|
+
It is a RobotLab::Hook subclass registered under namespace :#{RobotLab::Xyzzy.namespace}.
|
|
162
|
+
stdout : [xyzzy] timestamp hook_name (one line per call)
|
|
163
|
+
logfile : full context snapshot → #{XYZZY_LOG}
|
|
164
|
+
TEXT
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def run_robot
|
|
168
|
+
section "Robot Run Hooks"
|
|
169
|
+
robot = hooked_robot("hook_demo_robot")
|
|
170
|
+
robot.on(LocalDemoHook)
|
|
171
|
+
|
|
172
|
+
result = robot.run("show the hook pipeline", hooks: RuntimeDemoHook)
|
|
173
|
+
|
|
174
|
+
puts "Robot reply: #{result.reply}"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def run_llm_loop
|
|
178
|
+
section "LLM Request/Response Loop"
|
|
179
|
+
puts <<~TEXT
|
|
180
|
+
Demonstrates before/around/after_llm_generation hooks.
|
|
181
|
+
GenCacheHook implements around_llm_generation: on a cache hit the block
|
|
182
|
+
(the real LLM call) is never invoked. PerfHook times each robot.run call.
|
|
183
|
+
TEXT
|
|
184
|
+
|
|
185
|
+
GenCacheHook.reset!
|
|
186
|
+
PerfHook.reset!
|
|
187
|
+
|
|
188
|
+
puts " provider=#{LLM[:default].provider} model=#{LLM[:default].model}\n\n"
|
|
189
|
+
|
|
190
|
+
# with_model alone would leave the provider unset, and an Ollama model is
|
|
191
|
+
# not in RubyLLM's registry — pass provider and model together.
|
|
192
|
+
robot = RobotLab.build(
|
|
193
|
+
name: "loop_demo_robot",
|
|
194
|
+
system_prompt: "You are a helpful assistant. Answer every question in one concise sentence.",
|
|
195
|
+
**llm_opts
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
robot.on(PerfHook)
|
|
199
|
+
robot.on(GenerationTracerHook)
|
|
200
|
+
robot.on(GenCacheHook)
|
|
201
|
+
|
|
202
|
+
queries = [
|
|
203
|
+
"what is the boiling point of water?",
|
|
204
|
+
"what is 6 * 7?",
|
|
205
|
+
"what is the boiling point of water?",
|
|
206
|
+
"what is 6 * 7?"
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
queries.each_with_index do |query, i|
|
|
210
|
+
puts "\n--- query #{i + 1}: #{query.inspect} ---"
|
|
211
|
+
result = robot.run(query)
|
|
212
|
+
puts " reply: #{result.reply}"
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
timings = PerfHook.timings
|
|
216
|
+
cache_hits = queries.size - GenCacheHook.call_count
|
|
217
|
+
avg_ms = (timings.sum / timings.size).round(3)
|
|
218
|
+
puts "\n--- summary ---"
|
|
219
|
+
puts " robot.run calls : #{queries.size}"
|
|
220
|
+
puts " LLM calls made : #{GenCacheHook.call_count} (#{cache_hits} served from gen_cache)"
|
|
221
|
+
puts " avg run time : #{avg_ms}ms"
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def run_tool
|
|
225
|
+
section "Tool Call Hooks"
|
|
226
|
+
tool = HookDemoTool.new
|
|
227
|
+
result = tool.call({ "label" => "tool hook payload" })
|
|
228
|
+
puts "Tool result: #{result.inspect}"
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def run_network
|
|
232
|
+
section "Network and Task Hooks"
|
|
233
|
+
network = RobotLab::Network.new(name: "hook_demo_network")
|
|
234
|
+
network.on(NetworkDemoHook)
|
|
235
|
+
network.task(:summarize, hooked_robot("network_worker"), depends_on: :none)
|
|
236
|
+
|
|
237
|
+
result = network.run(message: "summarize hook activity")
|
|
238
|
+
reply = result.respond_to?(:reply) ? result.reply : result&.value&.reply
|
|
239
|
+
puts "Network result: #{reply}"
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def run_error
|
|
243
|
+
section "Error Hook"
|
|
244
|
+
failing_robot = RobotLab.build(name: "failing_hook_robot", system_prompt: "raise", **llm_opts)
|
|
245
|
+
# Robot#chat is a public reader — no need to reach for @chat.
|
|
246
|
+
failing_robot.chat.define_singleton_method(:ask) do |_message = nil, **_kwargs|
|
|
247
|
+
raise "planned hook demo failure"
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
failing_robot.run("trigger the on_error hook")
|
|
251
|
+
rescue RuntimeError => e
|
|
252
|
+
puts "Caught expected error: #{e.message}"
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Stubs the chat so the hook pipeline is exercised deterministically with no
|
|
256
|
+
# LLM traffic. Robot#chat is public, so this needs no ivar surgery.
|
|
257
|
+
def hooked_robot(name)
|
|
258
|
+
RobotLab.build(name: name, system_prompt: "deterministic hook demo", **llm_opts).tap do |robot|
|
|
259
|
+
robot.chat.define_singleton_method(:ask) do |message = nil, **_kwargs, &_block|
|
|
260
|
+
HookDemoResponse.new(content: "deterministic response to #{message.inspect}")
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
HookDemo.new.run
|
data/examples/README.md
CHANGED
|
@@ -6,7 +6,71 @@ Working demonstrations of RobotLab features, from single-robot basics to multi-r
|
|
|
6
6
|
|
|
7
7
|
- Ruby >= 3.2
|
|
8
8
|
- `bundle install` (from the project root)
|
|
9
|
-
-
|
|
9
|
+
- [Ollama](https://ollama.com) running locally with the demo model pulled:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
ollama serve
|
|
13
|
+
ollama pull qwen3.6
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Every example runs against a **local** model — no API keys, no egress, no
|
|
17
|
+
per-token cost. The model, provider and endpoint are set in one place,
|
|
18
|
+
`examples/common.rb`:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
LLM = {
|
|
22
|
+
default: LlmConfig.new(provider: "ollama", model: "qwen3.6:latest"),
|
|
23
|
+
small: LlmConfig.new(provider: "ollama", model: "qwen2.5:7b"),
|
|
24
|
+
large: LlmConfig.new(provider: "ollama", model: "llama3.3:latest")
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Robots pick it up with the `llm_opts` helper:
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
robot = RobotLab.build(name: "helper", **llm_opts) # default model
|
|
32
|
+
cheap = RobotLab.build(name: "helper", **llm_opts(:small)) # smaller model
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`provider:` is not optional here. Ollama models are absent from RubyLLM's
|
|
36
|
+
model registry, so passing `model:` alone raises `RubyLLM::ModelNotFoundError`
|
|
37
|
+
— supplying `provider:` is what makes RubyLLM skip the registry lookup. Note
|
|
38
|
+
also that `RunConfig` has no `provider` field, so provider and model travel
|
|
39
|
+
together on the robot even when other settings come from a shared `RunConfig`.
|
|
40
|
+
|
|
41
|
+
`common.rb` also raises `request_timeout` to 900s. A 20B+ model on consumer
|
|
42
|
+
hardware routinely takes longer than robot_lab's 120s default on a long
|
|
43
|
+
answer, which otherwise surfaces mid-example as `Net::ReadTimeout`. Override
|
|
44
|
+
with `LLM_REQUEST_TIMEOUT`.
|
|
45
|
+
|
|
46
|
+
## Runtime: pick your model with `LLM_PROFILE`
|
|
47
|
+
|
|
48
|
+
Every example is a *sequence* of LLM calls, so wall-clock is dominated by
|
|
49
|
+
`calls × seconds-per-call`. On `qwen3.6:latest` expect roughly **60-70s per
|
|
50
|
+
call**. That is fine for the single-robot examples and painful for the
|
|
51
|
+
multi-robot ones:
|
|
52
|
+
|
|
53
|
+
| Example | LLM calls | `qwen3.6:latest` | `LLM_PROFILE=small` |
|
|
54
|
+
|---------|-----------|------------------|---------------------|
|
|
55
|
+
| 01, 02, 19, 20 | 1-6 | seconds to ~5 min | fast |
|
|
56
|
+
| 03, 06, 07, 24, 27, 31 | 4-8 | ~5-10 min | ~2 min |
|
|
57
|
+
| 13, 14, 15 | ~20-30 | **30-40 min** | ~5-10 min |
|
|
58
|
+
| 16 | unbounded (600s cap) | usually hits the cap | often completes |
|
|
59
|
+
|
|
60
|
+
`LLM_PROFILE` switches the model for any example without editing it:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
LLM_PROFILE=small bundle exec ruby examples/14_rusty_circuit/open_mic.rb
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Valid values are the keys of `LLM` in `common.rb`: `default`, `small`, `large`.
|
|
67
|
+
|
|
68
|
+
**Long silences are not hangs.** In example 14 the scout writes its notes to
|
|
69
|
+
`output/scout_notes.md` rather than STDOUT, and a scout turn is 1-4 sequential
|
|
70
|
+
calls — minutes of blank terminal. The `· Scout …` lines exist so you can tell
|
|
71
|
+
progress from a stall. Example 16 is the same idea at larger scale: writers
|
|
72
|
+
coordinate through shared memory, and much of the work shows up in
|
|
73
|
+
`output/room.log` rather than on screen.
|
|
10
74
|
|
|
11
75
|
## Running Examples
|
|
12
76
|
|
|
@@ -21,17 +85,36 @@ bundle exec rake examples:all
|
|
|
21
85
|
bundle exec ruby examples/01_simple_robot.rb
|
|
22
86
|
```
|
|
23
87
|
|
|
88
|
+
## Tools require `tools: :inherit` at run time
|
|
89
|
+
|
|
90
|
+
This trips up everyone once. `Robot#run` defaults to `tools: :none`, which
|
|
91
|
+
means *"send zero tools this turn"* — the robot still holds them in
|
|
92
|
+
`local_tools`, but the provider never sees them and the model answers from
|
|
93
|
+
memory instead of calling anything:
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
robot = RobotLab.build(name: "bot", **llm_opts, local_tools: [Calculator])
|
|
97
|
+
|
|
98
|
+
robot.run("What is 15 * 7?") # Calculator is NEVER offered
|
|
99
|
+
robot.run("What is 15 * 7?", tools: :inherit) # Calculator is offered
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The same applies to MCP (`mcp: :inherit`), to network tasks
|
|
103
|
+
(`task :name, robot, tools: :inherit`), and to anything that forwards keywords
|
|
104
|
+
into `run` — including `RobotLab::RailsIntegration::Job`. Examples 02, 04, 06,
|
|
105
|
+
14, 15, 16, 20, 33 and the Rails app all pass it explicitly.
|
|
106
|
+
|
|
24
107
|
## Directory Structure
|
|
25
108
|
|
|
26
109
|
```
|
|
27
110
|
examples/
|
|
28
|
-
|
|
29
|
-
|
|
111
|
+
common.rb # Shared LLM config (Ollama), llm_opts, output helpers
|
|
112
|
+
xyzzy.rb # Single-file hook extension used by 35
|
|
30
113
|
01_simple_robot.rb # Basic robot with template
|
|
31
114
|
02_tools.rb # Robot with custom tools
|
|
32
115
|
03_network.rb # Multi-robot network with routing
|
|
33
116
|
04_mcp.rb # MCP server integration (GitHub)
|
|
34
|
-
05_streaming.rb # Real-time streaming
|
|
117
|
+
05_streaming.rb # Real-time streaming callbacks
|
|
35
118
|
06_prompt_templates.rb # Template-based e-commerce support
|
|
36
119
|
07_network_memory.rb # Shared memory with concurrent robots
|
|
37
120
|
08_llm_config.rb # Configuration hierarchy demo
|
|
@@ -47,6 +130,20 @@ examples/
|
|
|
47
130
|
scout.rb # Talent scout with analyst spawning
|
|
48
131
|
display.rb # Terminal formatting (color, wrapping, file output)
|
|
49
132
|
prompts/ # Templates for comic, heckler, and scout
|
|
133
|
+
15_memory_network_and_bus/ # Network + memory + bus + spawn in one pipeline
|
|
134
|
+
editorial_pipeline.rb # Main entrypoint — three writers, editor, chief
|
|
135
|
+
16_writers_room/ # Self-organizing group: no orchestration at all
|
|
136
|
+
writers_room.rb # Main entrypoint — book and screenplay modes
|
|
137
|
+
17_skills.rb # Composable skills, flat and recursive
|
|
138
|
+
18_rails/ # Minimal Rails 8 demo app (full integration)
|
|
139
|
+
app/robots/chat_robot.rb # Robot factory with system prompt + TimeTool
|
|
140
|
+
app/tools/time_tool.rb # Custom RobotLab::Tool subclass
|
|
141
|
+
app/jobs/robot_run_job.rb # Background job with Turbo Stream callbacks
|
|
142
|
+
app/controllers/ # Chat controller (index + create)
|
|
143
|
+
app/views/ # Layout with CDN importmap, chat view with streaming
|
|
144
|
+
app/models/ # RobotLabThread, RobotLabResult
|
|
145
|
+
config/ # Minimal Rails 8 config (async adapters, no asset pipeline)
|
|
146
|
+
db/migrate/ # Migration from generator template
|
|
50
147
|
19_token_tracking.rb # Per-robot token & cost tracking
|
|
51
148
|
20_circuit_breaker.rb # Tool loop circuit breaker with max_tool_rounds
|
|
52
149
|
21_learning_loop.rb # Learning accumulation across runs with robot.learn
|
|
@@ -55,18 +152,17 @@ examples/
|
|
|
55
152
|
24_structured_delegation.rb # Structured delegation with duration and token tracking
|
|
56
153
|
25_history_search.rb # Semantic search over a robot's conversation history
|
|
57
154
|
26_document_store.rb # Embedding-based document store (RAG) via fastembed
|
|
155
|
+
27_incident_response/ # BusPoller, reactive memory, poller groups
|
|
156
|
+
incident_response.rb # Main entrypoint — wires up the war room
|
|
157
|
+
28_mcp_discovery.rb # Semantic MCP server selection before connecting
|
|
58
158
|
29_ractor_tools.rb # Ractor-safe tools: worker pool, freeze_deep, parallel batch
|
|
59
159
|
30_ractor_network.rb # Ractor network scheduler: dependency waves, parallel_mode
|
|
60
160
|
31_launch_assessment.rb # 6 parallel analysts, max_concurrent_robots: 4 semaphore cap
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
app/views/ # Layout with CDN importmap, chat view with streaming
|
|
67
|
-
app/models/ # RobotLabThread, RobotLabResult
|
|
68
|
-
config/ # Minimal Rails 8 config (async adapters, no asset pipeline)
|
|
69
|
-
db/migrate/ # Migration from generator template
|
|
161
|
+
32_newsletter_reader.rb # Utility script (no RobotLab) — RSS to Markdown
|
|
162
|
+
33_stock_generator.rb # Companion publisher for 33_stock_predictor (Redis)
|
|
163
|
+
33_stock_predictor.rb # Durable cross-session learning via robot_lab-durable
|
|
164
|
+
34_agentskills.rb # AgentSkills.io folder-format skills, matched per run
|
|
165
|
+
35_hooks.rb # Hook architecture demo using xyzzy.rb
|
|
70
166
|
prompts/ # Prompt templates (.md with YAML front matter)
|
|
71
167
|
```
|
|
72
168
|
|
|
@@ -76,43 +172,43 @@ examples/
|
|
|
76
172
|
|
|
77
173
|
Create and run a basic robot using a prompt template. Sends a single message and displays the response.
|
|
78
174
|
|
|
79
|
-
**Requires:**
|
|
175
|
+
**Requires:** Ollama
|
|
80
176
|
|
|
81
177
|
### 02 — Tools
|
|
82
178
|
|
|
83
179
|
Give a robot custom tools (`Calculator`, `FortuneCookie`) defined as `RubyLLM::Tool` subclasses. The LLM decides when to call each tool based on the user's request.
|
|
84
180
|
|
|
85
|
-
**Requires:**
|
|
181
|
+
**Requires:** Ollama
|
|
86
182
|
|
|
87
183
|
### 03 — Multi-Robot Network
|
|
88
184
|
|
|
89
185
|
Build a customer support network with a classifier robot that routes requests to billing, technical, or general specialists. Uses SimpleFlow's optional task activation for conditional routing.
|
|
90
186
|
|
|
91
|
-
**Requires:**
|
|
187
|
+
**Requires:** Ollama
|
|
92
188
|
|
|
93
189
|
### 04 — MCP Integration
|
|
94
190
|
|
|
95
191
|
Connect to the GitHub MCP server via stdio transport. Part 1 demonstrates direct `MCP::Client` usage (listing tools, calling `search_repositories`). Part 2 wraps the MCP server inside a robot for natural-language queries.
|
|
96
192
|
|
|
97
|
-
**Requires:**
|
|
193
|
+
**Requires:** Ollama, `GITHUB_PERSONAL_ACCESS_TOKEN`, `github-mcp-server` installed
|
|
98
194
|
|
|
99
195
|
### 05 — Streaming
|
|
100
196
|
|
|
101
|
-
Real-time streaming of robot responses
|
|
197
|
+
Real-time streaming of robot responses through four routes: the stored `on_content:` callback wired at build time, a per-call block passed to `run()`, both together (stored fires first, then the block), and `on_content` arriving through a `RunConfig`.
|
|
102
198
|
|
|
103
|
-
**Requires:**
|
|
199
|
+
**Requires:** Ollama (makes four live calls)
|
|
104
200
|
|
|
105
201
|
### 06 — Prompt Templates
|
|
106
202
|
|
|
107
203
|
Full e-commerce support system using prompt_manager templates with YAML front matter. A triage robot classifies customer requests and routes to order, product, or escalation specialists. Demonstrates build-time context (company info, policies) and run-time context (customer data, order history).
|
|
108
204
|
|
|
109
|
-
**Requires:**
|
|
205
|
+
**Requires:** Ollama
|
|
110
206
|
|
|
111
207
|
### 07 — Network Memory
|
|
112
208
|
|
|
113
209
|
Reactive shared memory with concurrent robots. Multiple analysis robots (sentiment, entity extraction, keywords) run in parallel and write to shared memory. A synthesizer robot waits for all results using blocking reads, then produces a combined analysis. Demonstrates subscriptions, notifications, and network broadcast.
|
|
114
210
|
|
|
115
|
-
**Requires:**
|
|
211
|
+
**Requires:** Ollama
|
|
116
212
|
|
|
117
213
|
### 08 — LLM Configuration
|
|
118
214
|
|
|
@@ -154,7 +250,7 @@ Bidirectional robot communication via TypedBus. A comedy critic (Alice) tasks a
|
|
|
154
250
|
|
|
155
251
|
Demonstrates: Robot subclasses, prompt templates, auto-ack `on_message`, `reply()` convenience, temperature ramping, convergence patterns.
|
|
156
252
|
|
|
157
|
-
**Requires:**
|
|
253
|
+
**Requires:** Ollama
|
|
158
254
|
|
|
159
255
|
### 13 — Spawning Robots
|
|
160
256
|
|
|
@@ -162,7 +258,7 @@ Dynamic specialist creation at runtime. A dispatcher robot receives questions, a
|
|
|
162
258
|
|
|
163
259
|
Demonstrates: `spawn` for dynamic robot creation, lazy bus creation, `on_message` for reply handling, LLM-driven delegation.
|
|
164
260
|
|
|
165
|
-
**Requires:**
|
|
261
|
+
**Requires:** Ollama
|
|
166
262
|
|
|
167
263
|
### 14 — The Rusty Circuit (Open Mic Night)
|
|
168
264
|
|
|
@@ -170,27 +266,51 @@ A comedy club where three robots interact through a shared message bus. A comedi
|
|
|
170
266
|
|
|
171
267
|
Terminal output is color-formatted: comic bits in cyan (left-aligned), heckler reactions in yellow (right-indented), tool annotations dimmed. Scout notes go to `scout_notes.md` instead of STDOUT. The final verdict appears in green on both STDOUT and the scout file.
|
|
172
268
|
|
|
173
|
-
Demonstrates: Robot subclasses, self-modification via tool side effects, dynamic spawning (`spawn`), shared `:room` channel + personal channels,
|
|
269
|
+
Demonstrates: Robot subclasses, self-modification via tool side effects, dynamic spawning (`spawn`), shared `:room` channel + personal channels, `enqueue_delivery` for serializing async deliveries, `[SILENCE]` opt-out pattern, style reinvention via user-prompt injection.
|
|
270
|
+
|
|
271
|
+
**Requires:** Ollama. The most expensive example in the suite — ~30 LLM calls, 30+ minutes on `qwen3.6`. Use `LLM_PROFILE=small` to watch it in under ten.
|
|
272
|
+
|
|
273
|
+
### 15 — Memory, Network, Bus & Spawn Together
|
|
274
|
+
|
|
275
|
+
An editorial pipeline where three writers advocate for macOS, Windows, and Linux/BSD as a home AI research lab platform. The network runs the writers in parallel and hands their drafts to an editor through shared memory; the Linux writer spawns three distro specialists mid-pipeline; an editor-in-chief outside the pipeline reviews the combined article over the bus and can demand revisions.
|
|
276
|
+
|
|
277
|
+
Demonstrates: all four coordination mechanisms in one program, plus the direct `shared_memory` reference pattern that parallel pipeline steps need (`extract_run_context` deletes from a shared hash, so only the first parallel step would otherwise see `network_memory`).
|
|
278
|
+
|
|
279
|
+
**Requires:** Ollama
|
|
174
280
|
|
|
175
|
-
|
|
281
|
+
### 16 — The Writers' Room (Self-Organizing Group)
|
|
282
|
+
|
|
283
|
+
A team of identical writer robots produces a 10-chapter novella with no orchestration, no pipeline, and no assigned roles. Each writer subscribes to a `:room` broadcast channel and a personal channel, and has tools to read/write shared memory, broadcast, DM, spawn more writers, and mark the work complete. The script seeds the room with an assignment and waits.
|
|
284
|
+
|
|
285
|
+
`--screenplay-from output/memory.json` re-runs the room in screenplay mode, adapting a finished book into a 4-act TV movie pilot at scene granularity.
|
|
286
|
+
|
|
287
|
+
Demonstrates: emergent coordination, `clear_messages(keep_system: true)` as a per-message conversation reset, dynamic team growth, memory as the single source of truth.
|
|
288
|
+
|
|
289
|
+
**Requires:** Ollama (long-running — `--timeout` defaults to 600s)
|
|
290
|
+
|
|
291
|
+
### 17 — Composable Skills
|
|
292
|
+
|
|
293
|
+
Skills are ordinary templates whose bodies are prepended to the main template. An SRE incident responder is composed from `runbook_protocol` and `structured_output` (flat skills) plus `sre_compliance`, which recursively expands to `pii_redactor` and `audit_trail`. The assembled system prompt is printed line-by-line so the depth-first expansion order is visible, and a skill-less robot is built alongside it for size comparison.
|
|
294
|
+
|
|
295
|
+
**Requires:** Ollama
|
|
176
296
|
|
|
177
297
|
### 19 — Token & Cost Tracking
|
|
178
298
|
|
|
179
|
-
Track token usage across runs using `result.input_tokens` / `result.output_tokens` for per-run counts and `robot.total_input_tokens` / `robot.total_output_tokens` for running totals. Demonstrates `reset_token_totals` to start a fresh batch
|
|
299
|
+
Track token usage across runs using `result.input_tokens` / `result.output_tokens` for per-run counts and `robot.total_input_tokens` / `robot.total_output_tokens` for running totals. Demonstrates `reset_token_totals` to start a fresh batch. Local inference is free, so cost is reported as `$0.00000 (local)`; set `RATE_INPUT_CPM` / `RATE_OUTPUT_CPM` to price the same traffic against a hosted provider.
|
|
180
300
|
|
|
181
|
-
**Requires:**
|
|
301
|
+
**Requires:** Ollama
|
|
182
302
|
|
|
183
303
|
### 20 — Tool Loop Circuit Breaker
|
|
184
304
|
|
|
185
305
|
Guards against runaway tool call loops using `max_tool_rounds:`. A step processor tool is designed to always return "more steps remain", which would loop indefinitely without a guard. The circuit breaker fires after the configured limit and raises `RobotLab::ToolLoopError`. Shows how to rescue the error gracefully and confirms the robot is fully reusable after a breaker trip.
|
|
186
306
|
|
|
187
|
-
**Requires:**
|
|
307
|
+
**Requires:** Ollama
|
|
188
308
|
|
|
189
309
|
### 21 — Learning Accumulation Loop
|
|
190
310
|
|
|
191
311
|
Builds up cross-run observations with `robot.learn(text)`. A code reviewer accumulates one key insight after each review. On subsequent runs, learnings are automatically prepended to the user message as a "LEARNINGS FROM PREVIOUS RUNS:" block. Demonstrates bidirectional substring deduplication (broader learnings replace narrower ones), the `robot.learnings` accessor, and how learnings survive a robot rebuild via the shared `Memory` object.
|
|
192
312
|
|
|
193
|
-
**Requires:**
|
|
313
|
+
**Requires:** Ollama
|
|
194
314
|
|
|
195
315
|
### 22 — Context Window Compression
|
|
196
316
|
|
|
@@ -198,23 +318,17 @@ Demonstrates `robot.compress_history()` for reducing token usage in long convers
|
|
|
198
318
|
|
|
199
319
|
**Requires:** `gem 'classifier', '~> 2.3'` in your Gemfile (no LLM calls in the demo itself)
|
|
200
320
|
|
|
201
|
-
### 24 — Structured Delegation
|
|
202
|
-
|
|
203
|
-
A manager robot delegates sub-tasks to a summarizer and an analyst. Each `delegate()` call returns a `RobotResult` annotated with `delegated_by`, `duration`, and token counts. Includes a comparison table of when to use delegation vs. bus messaging vs. pipelines.
|
|
204
|
-
|
|
205
|
-
**Requires:** LLM API key
|
|
206
|
-
|
|
207
321
|
### 23 — Debate Convergence Detection
|
|
208
322
|
|
|
209
|
-
Demonstrates `RobotLab::Convergence` for detecting when two independent agents have reached the same conclusion. Scores pairs of texts from identical → semantically similar → partially related → unrelated, showing how the similarity metric varies. Includes the
|
|
323
|
+
Demonstrates `RobotLab::Convergence` for detecting when two independent agents have reached the same conclusion. Scores pairs of texts from identical → semantically similar → partially related → unrelated, showing how the similarity metric varies. Includes the fast-path pattern: a gate robot compares two verifiers' replies and only activates the expensive reconciler — via SimpleFlow optional-task activation — when they disagree.
|
|
210
324
|
|
|
211
325
|
**Requires:** `gem 'classifier', '~> 2.3'` in your Gemfile (no LLM calls in the demo itself)
|
|
212
326
|
|
|
213
327
|
### 24 — Structured Delegation
|
|
214
328
|
|
|
215
|
-
Demonstrates `robot.delegate(to:, task:)` for synchronous and asynchronous inter-robot delegation. The manager robot delegates document analysis to a summarizer and an analyst. Shows synchronous (sequential, blocking) and asynchronous (parallel fan-out, `DelegationFuture`) modes with wall-time comparison.
|
|
329
|
+
Demonstrates `robot.delegate(to:, task:)` for synchronous and asynchronous inter-robot delegation. The manager robot delegates document analysis to a summarizer and an analyst. Shows synchronous (sequential, blocking) and asynchronous (parallel fan-out, `DelegationFuture`) modes with wall-time comparison. Each result carries `delegated_by`, `duration`, and token counts.
|
|
216
330
|
|
|
217
|
-
**Requires:**
|
|
331
|
+
**Requires:** Ollama
|
|
218
332
|
|
|
219
333
|
### 25 — Chat History Search
|
|
220
334
|
|
|
@@ -248,7 +362,7 @@ bundle exec ruby examples/27_incident_response/incident_response.rb
|
|
|
248
362
|
bundle exec rake examples:run[27]
|
|
249
363
|
```
|
|
250
364
|
|
|
251
|
-
**Requires:**
|
|
365
|
+
**Requires:** Ollama
|
|
252
366
|
|
|
253
367
|
### 28 — MCP Server Discovery
|
|
254
368
|
|
|
@@ -296,7 +410,7 @@ and the `pipeline.step_dependencies` dependency graph inspection.
|
|
|
296
410
|
|
|
297
411
|
**Part 3** — Live LLM run (enabled automatically when `ANTHROPIC_API_KEY` is set).
|
|
298
412
|
|
|
299
|
-
**Requires:** None for Parts 1 & 2.
|
|
413
|
+
**Requires:** None for Parts 1 & 2. `RUN_LIVE=1` plus Ollama for Part 3 (expected to fail — ruby_llm is not Ractor-safe yet).
|
|
300
414
|
|
|
301
415
|
### 31 — Product Launch Assessment (Concurrency Cap)
|
|
302
416
|
|
|
@@ -304,7 +418,37 @@ Six specialist robots evaluate a product launch simultaneously: market, competit
|
|
|
304
418
|
|
|
305
419
|
Demonstrates: `max_concurrent_robots:` on `RunConfig`, `Async::Semaphore` back-pressure via `simple_flow`, six parallel `depends_on: :none` tasks, shared memory writes and blocking reads.
|
|
306
420
|
|
|
307
|
-
**Requires:**
|
|
421
|
+
**Requires:** Ollama
|
|
422
|
+
|
|
423
|
+
### 32 — Newsletter Reader
|
|
424
|
+
|
|
425
|
+
A plain utility script that fetches unprocessed issues from Ruby newsletter RSS feeds and saves them as Markdown. **It uses no part of RobotLab** — it lives here as a content feeder for other experiments, not as a capability demo. Set `CLIPPINGS_DIR` to choose the output folder.
|
|
426
|
+
|
|
427
|
+
**Requires:** `html2markdown` on `PATH`
|
|
428
|
+
|
|
429
|
+
### 33 — Durable Cross-Session Learning
|
|
430
|
+
|
|
431
|
+
`33_stock_generator.rb` publishes synthetic XYZZY prices to a Redis channel using geometric Brownian motion. `33_stock_predictor.rb` consumes them, predicts each window's high/low with an SMA + EMA ensemble, and after every window asks a tuner robot to adjust the predictor's parameters.
|
|
432
|
+
|
|
433
|
+
The tuner uses `robot_lab-durable`. Note the API: `learn:` / `learn_domain:` are **not** constructor parameters (`Robot#initialize` takes a fixed keyword list with no `**rest`, so passing them raises `ArgumentError`). Call `robot.setup_durable_learning(domain:)` after `build`, which seeds `robot.learnings` from `~/.robot_lab/durable/<domain>.yml` and appends the `RecallKnowledge` / `RecordKnowledge` tools. Core `Robot#run` does not invoke the reflector, so the caller drives `robot.run_reflector` to promote learnings back to the store.
|
|
434
|
+
|
|
435
|
+
Run the two scripts in separate terminals.
|
|
436
|
+
|
|
437
|
+
**Requires:** Ollama, Redis on localhost:6379, `robot_lab-durable`
|
|
438
|
+
|
|
439
|
+
### 34 — AgentSkills.io Integration
|
|
440
|
+
|
|
441
|
+
Skills declared as `skills: [:code_reviewer]` are resolved from `~/.prompts/skills/<name>/SKILL.md` and matched per-run by embedding similarity — a code-review question activates the skill, an unrelated question does not. Run the two queries and compare.
|
|
442
|
+
|
|
443
|
+
**Requires:** Ollama, `robot_lab-document_store` gem, a `SKILL.md` at `~/.prompts/skills/code_reviewer/`
|
|
444
|
+
|
|
445
|
+
### 35 — Hooks Architecture
|
|
446
|
+
|
|
447
|
+
Loads `xyzzy.rb`, a single-file `RobotLab::Hook` subclass that registers for every hook and logs each callback with the context it receives. Demonstrates robot run, LLM generation, tool call, network run, task, and error hooks. Stubs `robot.chat.ask` for the deterministic sections; the LLM-generation section makes four real calls to show `around_llm_generation` serving two of them from a cache.
|
|
448
|
+
|
|
449
|
+
`RobotLab::Hook` has no logger accessor — handlers own their output. `xyzzy.rb` freezes its log destination from `XYZZY_LOG_PATH` at load time, so the env var must be set before the `require`.
|
|
450
|
+
|
|
451
|
+
**Requires:** Ollama (four calls in the LLM-loop section)
|
|
308
452
|
|
|
309
453
|
### 18 — Rails Integration Demo
|
|
310
454
|
|
|
@@ -319,13 +463,15 @@ A minimal, hand-built Rails 8 app that exercises every piece of RobotLab's Rails
|
|
|
319
463
|
|
|
320
464
|
**No Redis, no Solid Queue, no asset pipeline.** Uses `:async` adapters for both ActiveJob and ActionCable. Turbo JS loaded via importmap from CDN (`@hotwired/turbo-rails`).
|
|
321
465
|
|
|
466
|
+
**Two API details worth copying:** the job's superclass is `RobotLab::RailsIntegration::Job` (there is no `RobotLab::Job` alias), and the controller enqueues with `tools: :inherit` so that keyword reaches `robot.run` — otherwise `TimeTool` is never offered and the model guesses the time.
|
|
467
|
+
|
|
322
468
|
```bash
|
|
323
469
|
cd examples/18_rails
|
|
324
470
|
bin/setup # bundle install + db:create + db:migrate
|
|
325
471
|
bin/dev # starts Puma on http://localhost:3000
|
|
326
472
|
```
|
|
327
473
|
|
|
328
|
-
**Requires:**
|
|
474
|
+
**Requires:** Ollama, Ruby 3.2+
|
|
329
475
|
|
|
330
476
|
## Prompt Templates
|
|
331
477
|
|
|
@@ -370,3 +516,18 @@ Front matter keys like `model`, `temperature`, `top_p`, `max_tokens` are applied
|
|
|
370
516
|
| `open_mic_comic.md` | 14 | Observational comedian with self-modification |
|
|
371
517
|
| `open_mic_heckler.md` | 14 | Tough audience heckler (can stay silent or counter-joke) |
|
|
372
518
|
| `open_mic_scout.md` | 14 | Talent scout with analyst recruitment |
|
|
519
|
+
| `os_advocate.md` | 15 | Operating-system advocacy writer |
|
|
520
|
+
| `os_editor.md` | 15 | Synthesizes the three advocacy drafts |
|
|
521
|
+
| `os_chief.md` | 15 | Editor-in-chief (APPROVED / REVISE) |
|
|
522
|
+
| `writer.md` | 16 | Self-organizing novella writer |
|
|
523
|
+
| `screenplay_writer.md` | 16 | Self-organizing screenplay writer |
|
|
524
|
+
| `incident_responder.md` | 17 | SRE on-call analyst (main template) |
|
|
525
|
+
| `runbook_protocol.md` | 17 | Flat skill: 5-step incident protocol |
|
|
526
|
+
| `structured_output.md` | 17 | Flat skill: JSON response format |
|
|
527
|
+
| `sre_compliance.md` | 17 | Recursive skill: bundles the two leaves below |
|
|
528
|
+
| `pii_redactor.md` | 17 | Leaf skill: redact PII |
|
|
529
|
+
| `audit_trail.md` | 17 | Leaf skill: audit metadata |
|
|
530
|
+
|
|
531
|
+
Templates matching `*_test.md` are fixtures for the test suite, not examples.
|
|
532
|
+
|
|
533
|
+
Templates for 14, 15 and 16 live in those examples' own `prompts/` directories; each entrypoint sets `ROBOT_LAB_TEMPLATE_PATH` before requiring `common.rb`.
|