robot_lab 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +1 -1
- data/lib/robot_lab/mcp/transports/streamable_http.rb +1 -1
- data/lib/robot_lab/version.rb +1 -1
- metadata +3 -253
- data/.architecture/AGENTS.md +0 -32
- data/.architecture/config.yml +0 -8
- data/.architecture/members.yml +0 -60
- data/.architecture/reviews/feature-free-will.md +0 -490
- data/.architecture/reviews/overall-codebase.md +0 -427
- data/.claude/settings.local.json +0 -57
- data/.codex/config.toml +0 -2
- data/.envrc +0 -6
- data/.github/workflows/deploy-github-pages.yml +0 -52
- data/.irbrc +0 -6
- data/.loki +0 -65
- data/.quality/reek_baseline.txt +0 -43
- data/.rubocop.yml +0 -10
- data/Archspec.rb +0 -44
- data/CLAUDE.md +0 -141
- data/COMMITS.md +0 -196
- data/Rakefile +0 -193
- data/_typos.toml +0 -21
- data/agent2agent_review.md +0 -192
- data/agentf_improvements.md +0 -253
- data/agents.md +0 -14
- data/docs/api/core/index.md +0 -147
- data/docs/api/core/memory.md +0 -578
- data/docs/api/core/network.md +0 -553
- data/docs/api/core/result.md +0 -211
- data/docs/api/core/robot.md +0 -1667
- data/docs/api/core/state.md +0 -145
- data/docs/api/core/tool.md +0 -520
- data/docs/api/errors.md +0 -323
- data/docs/api/hooks.md +0 -469
- data/docs/api/index.md +0 -137
- data/docs/api/mcp/client.md +0 -387
- data/docs/api/mcp/index.md +0 -267
- data/docs/api/mcp/server.md +0 -252
- data/docs/api/mcp/transports.md +0 -339
- data/docs/api/messages/index.md +0 -126
- data/docs/api/messages/text-message.md +0 -170
- data/docs/api/messages/tool-call-message.md +0 -206
- data/docs/api/messages/tool-result-message.md +0 -242
- data/docs/api/messages/user-message.md +0 -232
- data/docs/api/skills.md +0 -423
- data/docs/api/streaming/context.md +0 -280
- data/docs/api/streaming/events.md +0 -185
- data/docs/api/streaming/index.md +0 -156
- data/docs/api/support.md +0 -846
- data/docs/architecture/core-concepts.md +0 -575
- data/docs/architecture/index.md +0 -252
- data/docs/architecture/message-flow.md +0 -366
- data/docs/architecture/network-orchestration.md +0 -478
- data/docs/architecture/robot-execution.md +0 -396
- data/docs/architecture/state-management.md +0 -410
- data/docs/assets/css/custom.css +0 -56
- data/docs/assets/images/robot_lab.jpg +0 -0
- data/docs/concepts.md +0 -641
- data/docs/examples/basic-chat.md +0 -258
- data/docs/examples/index.md +0 -425
- data/docs/examples/mcp-server.md +0 -403
- data/docs/examples/multi-robot-network.md +0 -476
- data/docs/examples/tool-usage.md +0 -468
- data/docs/getting-started/configuration.md +0 -645
- data/docs/getting-started/index.md +0 -56
- data/docs/getting-started/installation.md +0 -197
- data/docs/getting-started/quick-start.md +0 -293
- data/docs/guides/building-robots.md +0 -1187
- data/docs/guides/creating-networks.md +0 -736
- data/docs/guides/hooks.md +0 -1206
- data/docs/guides/index.md +0 -78
- data/docs/guides/knowledge.md +0 -219
- data/docs/guides/mcp-integration.md +0 -740
- data/docs/guides/memory.md +0 -458
- data/docs/guides/observability.md +0 -733
- data/docs/guides/streaming.md +0 -323
- data/docs/guides/using-tools.md +0 -780
- data/docs/index.md +0 -280
- data/docs/superpowers/plans/2026-05-06-agentskills.md +0 -1303
- data/docs/superpowers/specs/2026-05-06-agentskills-design.md +0 -247
- data/examples/.envrc +0 -3
- data/examples/01_simple_robot.rb +0 -31
- data/examples/02_tools.rb +0 -109
- data/examples/03_network.rb +0 -118
- data/examples/04_mcp.rb +0 -217
- data/examples/05_streaming.rb +0 -137
- data/examples/06_prompt_templates.rb +0 -325
- data/examples/07_network_memory.rb +0 -313
- data/examples/08_llm_config.rb +0 -162
- data/examples/09_chaining.rb +0 -251
- data/examples/10_memory.rb +0 -315
- data/examples/11_network_introspection.rb +0 -239
- data/examples/12_message_bus.rb +0 -67
- data/examples/13_spawn.rb +0 -85
- data/examples/14_rusty_circuit/.envrc +0 -1
- data/examples/14_rusty_circuit/.gitignore +0 -1
- data/examples/14_rusty_circuit/README.md +0 -1
- data/examples/14_rusty_circuit/comic.rb +0 -149
- data/examples/14_rusty_circuit/display.rb +0 -217
- data/examples/14_rusty_circuit/heckler.rb +0 -65
- data/examples/14_rusty_circuit/open_mic.rb +0 -132
- data/examples/14_rusty_circuit/prompts/open_mic_comic.md +0 -20
- data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +0 -23
- data/examples/14_rusty_circuit/prompts/open_mic_scout.md +0 -20
- data/examples/14_rusty_circuit/scout.rb +0 -165
- data/examples/14_rusty_circuit/show.log +0 -234
- data/examples/15_memory_network_and_bus/.envrc +0 -1
- data/examples/15_memory_network_and_bus/README.md +0 -66
- data/examples/15_memory_network_and_bus/editor_in_chief.rb +0 -24
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +0 -213
- data/examples/15_memory_network_and_bus/linux_writer.rb +0 -80
- data/examples/15_memory_network_and_bus/os_editor.rb +0 -48
- data/examples/15_memory_network_and_bus/os_writer.rb +0 -49
- data/examples/15_memory_network_and_bus/output/combined_article.md +0 -11
- data/examples/15_memory_network_and_bus/output/final_article.md +0 -8
- data/examples/15_memory_network_and_bus/output/linux_draft.md +0 -7
- data/examples/15_memory_network_and_bus/output/mac_draft.md +0 -7
- data/examples/15_memory_network_and_bus/output/memory.json +0 -13
- data/examples/15_memory_network_and_bus/output/revision_1.md +0 -29
- data/examples/15_memory_network_and_bus/output/revision_2.md +0 -50
- data/examples/15_memory_network_and_bus/output/revision_3.md +0 -8
- data/examples/15_memory_network_and_bus/output/windows_draft.md +0 -7
- data/examples/15_memory_network_and_bus/prompts/os_advocate.md +0 -13
- data/examples/15_memory_network_and_bus/prompts/os_chief.md +0 -13
- data/examples/15_memory_network_and_bus/prompts/os_editor.md +0 -13
- data/examples/16_writers_room/.envrc +0 -1
- data/examples/16_writers_room/display.rb +0 -158
- data/examples/16_writers_room/output/.gitignore +0 -4
- data/examples/16_writers_room/output/README.md +0 -69
- data/examples/16_writers_room/output/opus_001.md +0 -263
- data/examples/16_writers_room/output/opus_001_notes.log +0 -470
- data/examples/16_writers_room/output/opus_002.md +0 -245
- data/examples/16_writers_room/output/opus_002_notes.log +0 -546
- data/examples/16_writers_room/output/opus_002_screenplay.md +0 -7989
- data/examples/16_writers_room/output/opus_002_screenplay_notes.md +0 -993
- data/examples/16_writers_room/prompts/screenplay_writer.md +0 -66
- data/examples/16_writers_room/prompts/writer.md +0 -37
- data/examples/16_writers_room/room.rb +0 -186
- data/examples/16_writers_room/tools.rb +0 -173
- data/examples/16_writers_room/writer.rb +0 -121
- data/examples/16_writers_room/writers_room.rb +0 -256
- data/examples/17_skills.rb +0 -234
- data/examples/18_rails/.envrc +0 -3
- data/examples/18_rails/.gitignore +0 -5
- data/examples/18_rails/Gemfile +0 -11
- data/examples/18_rails/README.md +0 -67
- data/examples/18_rails/Rakefile +0 -4
- data/examples/18_rails/app/controllers/application_controller.rb +0 -4
- data/examples/18_rails/app/controllers/chat_controller.rb +0 -50
- data/examples/18_rails/app/jobs/application_job.rb +0 -4
- data/examples/18_rails/app/jobs/robot_run_job.rb +0 -25
- data/examples/18_rails/app/models/application_record.rb +0 -5
- data/examples/18_rails/app/models/robot_lab_result.rb +0 -36
- data/examples/18_rails/app/models/robot_lab_thread.rb +0 -23
- data/examples/18_rails/app/robots/chat_robot.rb +0 -25
- data/examples/18_rails/app/tools/time_tool.rb +0 -9
- data/examples/18_rails/app/views/chat/_user_message.html.erb +0 -1
- data/examples/18_rails/app/views/chat/index.html.erb +0 -67
- data/examples/18_rails/app/views/layouts/application.html.erb +0 -49
- data/examples/18_rails/bin/dev +0 -7
- data/examples/18_rails/bin/rails +0 -6
- data/examples/18_rails/bin/setup +0 -15
- data/examples/18_rails/config/application.rb +0 -33
- data/examples/18_rails/config/cable.yml +0 -2
- data/examples/18_rails/config/database.yml +0 -5
- data/examples/18_rails/config/environment.rb +0 -4
- data/examples/18_rails/config/initializers/robot_lab.rb +0 -11
- data/examples/18_rails/config/routes.rb +0 -6
- data/examples/18_rails/config.ru +0 -4
- data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +0 -32
- data/examples/19_token_tracking.rb +0 -138
- data/examples/20_circuit_breaker.rb +0 -147
- data/examples/21_learning_loop.rb +0 -181
- data/examples/22_context_compression.rb +0 -172
- data/examples/23_convergence.rb +0 -133
- data/examples/24_structured_delegation.rb +0 -151
- data/examples/25_history_search/conversation.jsonl +0 -30
- data/examples/25_history_search.rb +0 -133
- data/examples/26_document_store/api_versioning_adr.md +0 -52
- data/examples/26_document_store/incident_postmortem.md +0 -46
- data/examples/26_document_store/postgres_runbook.md +0 -49
- data/examples/26_document_store/redis_caching_guide.md +0 -48
- data/examples/26_document_store/sidekiq_guide.md +0 -51
- data/examples/26_document_store.rb +0 -140
- data/examples/27_incident_response/README.md +0 -65
- data/examples/27_incident_response/incident_response.rb +0 -261
- data/examples/28_mcp_discovery.rb +0 -113
- data/examples/29_ractor_tools.rb +0 -240
- data/examples/30_ractor_network.rb +0 -252
- data/examples/31_launch_assessment.rb +0 -246
- data/examples/32_newsletter_reader.rb +0 -193
- data/examples/33_stock_generator.rb +0 -80
- data/examples/33_stock_predictor.rb +0 -327
- data/examples/34_agentskills.rb +0 -76
- data/examples/35_hooks.rb +0 -266
- data/examples/README.md +0 -550
- data/examples/common.rb +0 -180
- data/examples/prompts/assistant.md +0 -5
- data/examples/prompts/audit_trail.md +0 -8
- data/examples/prompts/billing.md +0 -10
- data/examples/prompts/classifier.md +0 -7
- data/examples/prompts/comedian.md +0 -6
- data/examples/prompts/comedy_critic.md +0 -10
- data/examples/prompts/configurable.md +0 -9
- data/examples/prompts/dispatcher.md +0 -12
- data/examples/prompts/entity_extractor.md +0 -14
- data/examples/prompts/escalation.md +0 -42
- data/examples/prompts/frontmatter_mcp_test.md +0 -9
- data/examples/prompts/frontmatter_named_test.md +0 -5
- data/examples/prompts/frontmatter_tools_test.md +0 -6
- data/examples/prompts/general.md +0 -7
- data/examples/prompts/github_assistant.md +0 -14
- data/examples/prompts/helper.md +0 -4
- data/examples/prompts/incident_responder.md +0 -18
- data/examples/prompts/keyword_extractor.md +0 -11
- data/examples/prompts/llm_config_demo.md +0 -20
- data/examples/prompts/order_support.md +0 -35
- data/examples/prompts/os_advocate.md +0 -13
- data/examples/prompts/os_chief.md +0 -13
- data/examples/prompts/os_editor.md +0 -13
- data/examples/prompts/parameterized_main_test.md +0 -6
- data/examples/prompts/pii_redactor.md +0 -7
- data/examples/prompts/product_support.md +0 -37
- data/examples/prompts/runbook_protocol.md +0 -12
- data/examples/prompts/sentiment_analyzer.md +0 -12
- data/examples/prompts/skill_a_test.md +0 -4
- data/examples/prompts/skill_b_test.md +0 -4
- data/examples/prompts/skill_config_test.md +0 -5
- data/examples/prompts/skill_cycle_a_test.md +0 -6
- data/examples/prompts/skill_cycle_b_test.md +0 -6
- data/examples/prompts/skill_description_test.md +0 -4
- data/examples/prompts/skill_leaf_test.md +0 -4
- data/examples/prompts/skill_nested_test.md +0 -6
- data/examples/prompts/skill_refs_main_test.md +0 -6
- data/examples/prompts/skill_self_ref_test.md +0 -6
- data/examples/prompts/skill_with_mcp_test.md +0 -9
- data/examples/prompts/skill_with_params_test.md +0 -6
- data/examples/prompts/skill_with_robot_name_test.md +0 -5
- data/examples/prompts/skill_with_tools_test.md +0 -6
- data/examples/prompts/sre_compliance.md +0 -10
- data/examples/prompts/structured_output.md +0 -7
- data/examples/prompts/synthesizer.md +0 -17
- data/examples/prompts/technical.md +0 -10
- data/examples/prompts/template_with_skills_test.md +0 -6
- data/examples/prompts/triage.md +0 -22
- data/examples/ruboruby.md +0 -423
- data/examples/run_all.rb +0 -60
- data/examples/xyzzy.rb +0 -97
- data/logfile +0 -8
- data/mkdocs.yml +0 -209
- data/robot_concurrency.md +0 -38
- data/simple_acp_review.md +0 -298
- data/temp.md +0 -6
- data/tool_manifest_plan.md +0 -155
data/docs/examples/basic-chat.md
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
# Basic Chat
|
|
2
|
-
|
|
3
|
-
A simple conversational robot example.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
This example demonstrates the minimal setup for a conversational robot that can respond to user messages using `robot.run("message")`.
|
|
8
|
-
|
|
9
|
-
The closest runnable file in this repository is
|
|
10
|
-
[`examples/01_simple_robot.rb`](https://github.com/MadBomber/robot_lab/blob/main/examples/01_simple_robot.rb).
|
|
11
|
-
The snippets below wrap that same API in a small REPL.
|
|
12
|
-
|
|
13
|
-
## Complete Example
|
|
14
|
-
|
|
15
|
-
```ruby
|
|
16
|
-
#!/usr/bin/env ruby
|
|
17
|
-
|
|
18
|
-
require "bundler/setup"
|
|
19
|
-
require "robot_lab"
|
|
20
|
-
|
|
21
|
-
# Build a simple assistant
|
|
22
|
-
assistant = RobotLab.build(
|
|
23
|
-
name: "assistant",
|
|
24
|
-
description: "A helpful conversational assistant",
|
|
25
|
-
system_prompt: <<~PROMPT,
|
|
26
|
-
You are a helpful, friendly assistant. You provide clear,
|
|
27
|
-
concise answers to questions. Be conversational but informative.
|
|
28
|
-
PROMPT
|
|
29
|
-
model: "claude-sonnet-4"
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
# Simple REPL
|
|
33
|
-
puts "Chat with the assistant (type 'quit' to exit)"
|
|
34
|
-
puts "-" * 50
|
|
35
|
-
|
|
36
|
-
loop do
|
|
37
|
-
print "\nYou: "
|
|
38
|
-
input = gets&.chomp
|
|
39
|
-
|
|
40
|
-
break if input.nil? || input.downcase == "quit"
|
|
41
|
-
next if input.empty?
|
|
42
|
-
|
|
43
|
-
# Run the robot with the user's message
|
|
44
|
-
result = assistant.run(input)
|
|
45
|
-
|
|
46
|
-
# Display response
|
|
47
|
-
puts "\nAssistant: #{result.last_text_content}"
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
puts "\nGoodbye!"
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## With Streaming
|
|
54
|
-
|
|
55
|
-
Pass a block to `run` to receive each `RubyLLM::Chunk` as it arrives.
|
|
56
|
-
|
|
57
|
-
> [!WARNING]
|
|
58
|
-
> The yielded object is a `RubyLLM::Chunk`. Read its text with `chunk.content`.
|
|
59
|
-
> There is no `chunk.text` method — a guard like `if chunk.respond_to?(:text)`
|
|
60
|
-
> silently prints nothing.
|
|
61
|
-
|
|
62
|
-
```ruby
|
|
63
|
-
#!/usr/bin/env ruby
|
|
64
|
-
|
|
65
|
-
require "bundler/setup"
|
|
66
|
-
require "robot_lab"
|
|
67
|
-
|
|
68
|
-
assistant = RobotLab.build(
|
|
69
|
-
name: "assistant",
|
|
70
|
-
system_prompt: "You are a helpful assistant.",
|
|
71
|
-
model: "claude-sonnet-4"
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
puts "Chat with streaming (type 'quit' to exit)"
|
|
75
|
-
puts "-" * 50
|
|
76
|
-
|
|
77
|
-
loop do
|
|
78
|
-
print "\nYou: "
|
|
79
|
-
input = gets&.chomp
|
|
80
|
-
|
|
81
|
-
break if input.nil? || input.downcase == "quit"
|
|
82
|
-
next if input.empty?
|
|
83
|
-
|
|
84
|
-
print "\nAssistant: "
|
|
85
|
-
assistant.run(input) { |chunk| print chunk.content }
|
|
86
|
-
puts
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
puts "\nGoodbye!"
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
You can also wire streaming once at build time with the `on_content:` callback,
|
|
93
|
-
which fires on every `run`:
|
|
94
|
-
|
|
95
|
-
```ruby
|
|
96
|
-
assistant = RobotLab.build(
|
|
97
|
-
name: "assistant",
|
|
98
|
-
system_prompt: "You are a helpful assistant.",
|
|
99
|
-
on_content: ->(chunk) { print chunk.content }
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
assistant.run("Tell me a one-sentence fact about Ruby.")
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
When both are supplied, the stored `on_content` callback fires first, then the
|
|
106
|
-
block. See [`examples/05_streaming.rb`](https://github.com/MadBomber/robot_lab/blob/main/examples/05_streaming.rb)
|
|
107
|
-
for all four variations (stored callback, per-call block, both, and via `RunConfig`).
|
|
108
|
-
|
|
109
|
-
## With Template
|
|
110
|
-
|
|
111
|
-
Templates are `.md` files with YAML front matter, resolved from the configured
|
|
112
|
-
prompts directory (`ROBOT_LAB_TEMPLATE_PATH`). Parameters declared `null` in the
|
|
113
|
-
front matter are required and are supplied via `context:`.
|
|
114
|
-
|
|
115
|
-
```ruby
|
|
116
|
-
#!/usr/bin/env ruby
|
|
117
|
-
|
|
118
|
-
require "bundler/setup"
|
|
119
|
-
require "robot_lab"
|
|
120
|
-
|
|
121
|
-
# Template file: prompts/support.md
|
|
122
|
-
# ---
|
|
123
|
-
# description: Support assistant
|
|
124
|
-
# parameters:
|
|
125
|
-
# company_name: null
|
|
126
|
-
# tone: friendly
|
|
127
|
-
# ---
|
|
128
|
-
# You are a <%= tone %> support assistant for <%= company_name %>.
|
|
129
|
-
|
|
130
|
-
assistant = RobotLab.build(
|
|
131
|
-
name: "assistant",
|
|
132
|
-
template: :support,
|
|
133
|
-
context: { company_name: "Acme Corp", tone: "friendly" },
|
|
134
|
-
model: "claude-sonnet-4"
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
puts "Chat with template-based assistant (type 'quit' to exit)"
|
|
138
|
-
puts "-" * 50
|
|
139
|
-
|
|
140
|
-
loop do
|
|
141
|
-
print "\nYou: "
|
|
142
|
-
input = gets&.chomp
|
|
143
|
-
|
|
144
|
-
break if input.nil? || input.downcase == "quit"
|
|
145
|
-
next if input.empty?
|
|
146
|
-
|
|
147
|
-
result = assistant.run(input)
|
|
148
|
-
puts "\nAssistant: #{result.last_text_content}"
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
puts "\nGoodbye!"
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
A full template-driven network lives in
|
|
155
|
-
[`examples/06_prompt_templates.rb`](https://github.com/MadBomber/robot_lab/blob/main/examples/06_prompt_templates.rb).
|
|
156
|
-
|
|
157
|
-
## With Memory
|
|
158
|
-
|
|
159
|
-
```ruby
|
|
160
|
-
#!/usr/bin/env ruby
|
|
161
|
-
|
|
162
|
-
require "bundler/setup"
|
|
163
|
-
require "robot_lab"
|
|
164
|
-
|
|
165
|
-
assistant = RobotLab.build(
|
|
166
|
-
name: "assistant",
|
|
167
|
-
system_prompt: "You are a helpful assistant. Use the user's name when you know it.",
|
|
168
|
-
model: "claude-sonnet-4"
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
puts "Chat with memory (type 'quit' to exit)"
|
|
172
|
-
puts "-" * 50
|
|
173
|
-
|
|
174
|
-
# Store user info in the robot's inherent memory
|
|
175
|
-
assistant.memory[:user_name] = "Alice"
|
|
176
|
-
|
|
177
|
-
loop do
|
|
178
|
-
print "\nYou: "
|
|
179
|
-
input = gets&.chomp
|
|
180
|
-
|
|
181
|
-
break if input.nil? || input.downcase == "quit"
|
|
182
|
-
next if input.empty?
|
|
183
|
-
|
|
184
|
-
# The robot's persistent @chat maintains conversation history automatically
|
|
185
|
-
result = assistant.run(input)
|
|
186
|
-
puts "\nAssistant: #{result.last_text_content}"
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
puts "\nGoodbye!"
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
The full Memory API — subscriptions, `StateProxy`, blocking reads, clone and
|
|
193
|
-
reset — is demonstrated in
|
|
194
|
-
[`examples/10_memory.rb`](https://github.com/MadBomber/robot_lab/blob/main/examples/10_memory.rb).
|
|
195
|
-
|
|
196
|
-
## Bare Robot with Chaining
|
|
197
|
-
|
|
198
|
-
```ruby
|
|
199
|
-
#!/usr/bin/env ruby
|
|
200
|
-
|
|
201
|
-
require "bundler/setup"
|
|
202
|
-
require "robot_lab"
|
|
203
|
-
|
|
204
|
-
# Build a bare robot with no template or prompt
|
|
205
|
-
robot = RobotLab.build(name: "bot")
|
|
206
|
-
|
|
207
|
-
# Configure via chaining
|
|
208
|
-
result = robot
|
|
209
|
-
.with_model("claude-sonnet-4")
|
|
210
|
-
.with_temperature(0.7)
|
|
211
|
-
.with_instructions("You are a pirate. Respond in pirate speak.")
|
|
212
|
-
.run("What is the weather like today?")
|
|
213
|
-
|
|
214
|
-
puts result.last_text_content
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
> [!NOTE]
|
|
218
|
-
> Only the `with_*` methods that `RubyLLM::Chat` exposes are delegated:
|
|
219
|
-
> `with_context`, `with_headers`, `with_instructions`, `with_model`, `with_params`,
|
|
220
|
-
> `with_schema`, `with_temperature`, `with_thinking`, `with_tool`, `with_tools`
|
|
221
|
-
> (plus RobotLab's own `with_template` and `with_bus`).
|
|
222
|
-
> There is no `with_max_tokens` / `with_top_p` / `with_top_k` — use a constructor
|
|
223
|
-
> kwarg (`max_tokens: 2000`) or `with_params(max_tokens: 2000, top_p: 0.3)`.
|
|
224
|
-
|
|
225
|
-
`examples/09_chaining.rb` walks through chaining and reconfiguration without
|
|
226
|
-
making any LLM calls.
|
|
227
|
-
|
|
228
|
-
## Running
|
|
229
|
-
|
|
230
|
-
```bash
|
|
231
|
-
# Set API key
|
|
232
|
-
export ANTHROPIC_API_KEY="your-key"
|
|
233
|
-
|
|
234
|
-
# Simplest runnable robot
|
|
235
|
-
ruby examples/01_simple_robot.rb
|
|
236
|
-
|
|
237
|
-
# Streaming
|
|
238
|
-
ruby examples/05_streaming.rb
|
|
239
|
-
|
|
240
|
-
# with_* chaining and reconfiguration (no LLM calls)
|
|
241
|
-
ruby examples/09_chaining.rb
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
## Key Concepts
|
|
245
|
-
|
|
246
|
-
1. **Robot Building**: Use `RobotLab.build(name:, system_prompt:)` or `RobotLab.build(name:, template:)` to create a robot
|
|
247
|
-
2. **Execution**: Call `robot.run("message")` to send a message and get a response
|
|
248
|
-
3. **Response**: Access the text via `result.last_text_content` (aliased as `result.reply`)
|
|
249
|
-
4. **Streaming**: Pass a block to `robot.run("message") { |chunk| print chunk.content }`, or set `on_content:` at build time
|
|
250
|
-
5. **Memory**: Access inherent memory via `robot.memory[:key]`
|
|
251
|
-
6. **Chaining**: Configure with the delegated `with_*` methods, which return `self`
|
|
252
|
-
7. **Conversation History**: The persistent `@chat` maintains history across multiple `run` calls
|
|
253
|
-
|
|
254
|
-
## See Also
|
|
255
|
-
|
|
256
|
-
- [Building Robots Guide](../guides/building-robots.md)
|
|
257
|
-
- [Streaming Guide](../guides/streaming.md)
|
|
258
|
-
- [Robot API Reference](../api/core/robot.md)
|
data/docs/examples/index.md
DELETED
|
@@ -1,425 +0,0 @@
|
|
|
1
|
-
# Examples
|
|
2
|
-
|
|
3
|
-
Complete working examples demonstrating RobotLab features.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
These examples show how to use RobotLab for common scenarios, from simple chatbots to complex multi-robot systems.
|
|
8
|
-
|
|
9
|
-
## Examples
|
|
10
|
-
|
|
11
|
-
| Example | Description |
|
|
12
|
-
|---------|-------------|
|
|
13
|
-
| [Basic Chat](basic-chat.md) | Simple conversational robot |
|
|
14
|
-
| [Multi-Robot Network](multi-robot-network.md) | Customer service with routing |
|
|
15
|
-
| [Tool Usage](tool-usage.md) | External API integration |
|
|
16
|
-
| [MCP Server](mcp-server.md) | Connecting a robot to external MCP servers |
|
|
17
|
-
| [Message Bus](#message-bus) | Bidirectional robot communication with convergence |
|
|
18
|
-
| [Spawning Robots](#spawning-robots) | Dynamic specialist creation at runtime |
|
|
19
|
-
|
|
20
|
-
> **Rails example** — see [robot_lab-rails](https://github.com/MadBomber/robot_lab-rails/blob/main/docs/examples/rails-application.md) for a full Rails integration example.
|
|
21
|
-
|
|
22
|
-
## Quick Links
|
|
23
|
-
|
|
24
|
-
### Simple Examples
|
|
25
|
-
|
|
26
|
-
- [Hello World Robot](#hello-world)
|
|
27
|
-
- [Robot with Tools](#robot-with-tools)
|
|
28
|
-
- [Network with Routing](#network-with-routing)
|
|
29
|
-
|
|
30
|
-
### Advanced Examples
|
|
31
|
-
|
|
32
|
-
- [Streaming Responses](basic-chat.md#with-streaming)
|
|
33
|
-
- [Persistent Conversations](basic-chat.md#with-memory)
|
|
34
|
-
- [MCP Integration](mcp-server.md)
|
|
35
|
-
- [Message Bus Communication](#message-bus)
|
|
36
|
-
- [Spawning Robots](#spawning-robots)
|
|
37
|
-
|
|
38
|
-
## Hello World
|
|
39
|
-
|
|
40
|
-
```ruby
|
|
41
|
-
require "robot_lab"
|
|
42
|
-
|
|
43
|
-
# Configuration is handled automatically via MywayConfig.
|
|
44
|
-
# Set API keys via environment variables:
|
|
45
|
-
# ROBOT_LAB_RUBY_LLM__ANTHROPIC_API_KEY=sk-ant-...
|
|
46
|
-
# Or via config files (~/.config/robot_lab/robot_lab.yml — the filename
|
|
47
|
-
# repeats the app name; ~/.config/robot_lab/config.yml is never read)
|
|
48
|
-
|
|
49
|
-
robot = RobotLab.build(
|
|
50
|
-
name: "greeter",
|
|
51
|
-
system_prompt: "You are a friendly greeter. Say hello warmly."
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
result = robot.run("Hi there!")
|
|
55
|
-
|
|
56
|
-
puts result.last_text_content
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
## Robot with Tools
|
|
60
|
-
|
|
61
|
-
Give the LLM a fixed set of operations rather than an expression evaluator —
|
|
62
|
-
never `eval` a string the model produced.
|
|
63
|
-
|
|
64
|
-
```ruby
|
|
65
|
-
class CalculatorTool < RubyLLM::Tool
|
|
66
|
-
description "Performs basic arithmetic operations"
|
|
67
|
-
|
|
68
|
-
param :operation, type: "string", desc: "add, subtract, multiply, or divide"
|
|
69
|
-
param :a, type: "number", desc: "First operand"
|
|
70
|
-
param :b, type: "number", desc: "Second operand"
|
|
71
|
-
|
|
72
|
-
def execute(operation:, a:, b:)
|
|
73
|
-
case operation
|
|
74
|
-
when "add" then a + b
|
|
75
|
-
when "subtract" then a - b
|
|
76
|
-
when "multiply" then a * b
|
|
77
|
-
when "divide" then a.to_f / b
|
|
78
|
-
else "Unknown operation: #{operation}"
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
robot = RobotLab.build(
|
|
84
|
-
name: "calculator",
|
|
85
|
-
system_prompt: "You help with calculations.",
|
|
86
|
-
local_tools: [CalculatorTool]
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
# tools: :inherit is REQUIRED — run() defaults to tools: :none
|
|
90
|
-
result = robot.run("What's 25 * 4?", tools: :inherit)
|
|
91
|
-
puts result.last_text_content
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
> [!WARNING]
|
|
95
|
-
> `Robot#run` defaults to `tools: :none` and `mcp: :none`. A plain
|
|
96
|
-
> `robot.run("...")` sends the LLM **no tools at all**, even when `local_tools:`
|
|
97
|
-
> were attached at build time. Pass `tools: :inherit` on the call that should be
|
|
98
|
-
> able to use them.
|
|
99
|
-
>
|
|
100
|
-
> For a **standalone** robot like this one, do not pass `tools: :inherit` at
|
|
101
|
-
> *build* time — there the parent level is the global default (`:none`), so it
|
|
102
|
-
> produces an allowlist that matches nothing. Leave `tools:` unset in the
|
|
103
|
-
> constructor. (Inside a network the opposite holds: build-time `:inherit` is how a
|
|
104
|
-
> robot opts into the allowlist on the network's `config:`. See
|
|
105
|
-
> [Configuration](../getting-started/configuration.md#hierarchical-mcp-and-tools).)
|
|
106
|
-
|
|
107
|
-
See [`examples/02_tools.rb`](https://github.com/MadBomber/robot_lab/blob/main/examples/02_tools.rb)
|
|
108
|
-
for a second tool definition (`FortuneCookie`) alongside the calculator.
|
|
109
|
-
|
|
110
|
-
## Network with Routing
|
|
111
|
-
|
|
112
|
-
Routing is not configured declaratively — a robot performs it. Subclass
|
|
113
|
-
`RobotLab::Robot`, override `#call`, and activate one of the `depends_on: :optional`
|
|
114
|
-
tasks based on what the LLM returned.
|
|
115
|
-
|
|
116
|
-
```ruby
|
|
117
|
-
class ClassifierRobot < RobotLab::Robot
|
|
118
|
-
def call(result)
|
|
119
|
-
run_context = extract_run_context(result)
|
|
120
|
-
message = run_context.delete(:message)
|
|
121
|
-
robot_result = run(message, **run_context)
|
|
122
|
-
|
|
123
|
-
new_result = result
|
|
124
|
-
.with_context(@name.to_sym, robot_result)
|
|
125
|
-
.continue(robot_result)
|
|
126
|
-
|
|
127
|
-
case robot_result.reply.to_s.strip.downcase
|
|
128
|
-
when /billing/ then new_result.activate(:billing)
|
|
129
|
-
when /technical/ then new_result.activate(:technical)
|
|
130
|
-
else new_result.activate(:general)
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
classifier = ClassifierRobot.new(
|
|
136
|
-
name: "classifier",
|
|
137
|
-
system_prompt: "Classify the request as BILLING, TECHNICAL, or GENERAL. Respond with only the category."
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
billing = RobotLab.build(name: "billing", system_prompt: "You handle billing questions.")
|
|
141
|
-
tech = RobotLab.build(name: "technical", system_prompt: "You handle technical issues.")
|
|
142
|
-
general = RobotLab.build(name: "general", system_prompt: "You handle everything else.")
|
|
143
|
-
|
|
144
|
-
network = RobotLab.create_network(name: "support") do
|
|
145
|
-
task :classifier, classifier, depends_on: :none
|
|
146
|
-
task :billing, billing, depends_on: :optional
|
|
147
|
-
task :technical, tech, depends_on: :optional
|
|
148
|
-
task :general, general, depends_on: :optional
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
result = network.run(message: "I was charged twice for my subscription")
|
|
152
|
-
|
|
153
|
-
# Access individual robot results via context
|
|
154
|
-
puts result.context[:classifier].last_text_content
|
|
155
|
-
puts result.value.last_text_content # the specialist that was activated
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
> [!WARNING]
|
|
159
|
-
> `result.context` is keyed by the **robot's** name (`with_context(@name.to_sym, ...)`),
|
|
160
|
-
> not the task name, and `activate(:name)` takes a **task** name. Keep the two
|
|
161
|
-
> identical — activating a task name that was never declared **raises
|
|
162
|
-
> `ArgumentError` and aborts the run** (`Step :classifier attempted to activate
|
|
163
|
-
> unknown step :billing`). The same happens for a task that was declared without
|
|
164
|
-
> `depends_on: :optional`.
|
|
165
|
-
|
|
166
|
-
Full version: [`examples/03_network.rb`](https://github.com/MadBomber/robot_lab/blob/main/examples/03_network.rb).
|
|
167
|
-
|
|
168
|
-
## Chaining Configuration
|
|
169
|
-
|
|
170
|
-
Robots support `with_*` methods that return `self` for chaining:
|
|
171
|
-
|
|
172
|
-
```ruby
|
|
173
|
-
robot = RobotLab.build(name: "assistant")
|
|
174
|
-
.with_instructions("You are a helpful coding assistant.")
|
|
175
|
-
.with_temperature(0.3)
|
|
176
|
-
.with_model("gpt-4o")
|
|
177
|
-
|
|
178
|
-
result = robot.run("Explain Ruby blocks.")
|
|
179
|
-
puts result.last_text_content
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
> [!NOTE]
|
|
183
|
-
> The chainable set is exactly what `RubyLLM::Chat` exposes — `with_context`,
|
|
184
|
-
> `with_headers`, `with_instructions`, `with_model`, `with_params`, `with_schema`,
|
|
185
|
-
> `with_temperature`, `with_thinking`, `with_tool`, `with_tools` — plus RobotLab's
|
|
186
|
-
> `with_template` and `with_bus`. There is no `with_max_tokens`, `with_top_p`, or
|
|
187
|
-
> `with_top_k`; use a constructor kwarg or `with_params(max_tokens: 2000)`.
|
|
188
|
-
|
|
189
|
-
## Using Templates
|
|
190
|
-
|
|
191
|
-
Templates are `.md` files with optional YAML front matter, managed by prompt_manager.
|
|
192
|
-
The body is rendered with **ERB** — interpolate with `<%= var %>`.
|
|
193
|
-
|
|
194
|
-
```ruby
|
|
195
|
-
# Template file: prompts/support.md
|
|
196
|
-
# ---
|
|
197
|
-
# model: claude-sonnet-4
|
|
198
|
-
# temperature: 0.5
|
|
199
|
-
# parameters:
|
|
200
|
-
# company_name: null
|
|
201
|
-
# ---
|
|
202
|
-
# You are a support assistant for <%= company_name %>.
|
|
203
|
-
|
|
204
|
-
robot = RobotLab.build(
|
|
205
|
-
name: "support",
|
|
206
|
-
template: :support,
|
|
207
|
-
context: { company_name: "Acme Corp" }
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
result = robot.run("How do I reset my password?")
|
|
211
|
-
puts result.last_text_content
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
> [!NOTE]
|
|
215
|
-
> `{{ var }}` is **not** interpolated — it passes through to the LLM verbatim.
|
|
216
|
-
> Of the LLM keys accepted in front matter, only `model:` and `temperature:` are
|
|
217
|
-
> actually applied to the chat. `top_p`, `top_k`, `max_tokens`, `presence_penalty`,
|
|
218
|
-
> `frequency_penalty`, and `stop` are parsed and then silently dropped; supply
|
|
219
|
-
> those as constructor kwargs or via a `RunConfig` instead.
|
|
220
|
-
|
|
221
|
-
## Running Examples
|
|
222
|
-
|
|
223
|
-
1. Install dependencies:
|
|
224
|
-
```bash
|
|
225
|
-
bundle install
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
2. Set API key:
|
|
229
|
-
```bash
|
|
230
|
-
export ANTHROPIC_API_KEY="your-key"
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
3. Run example:
|
|
234
|
-
```bash
|
|
235
|
-
ruby examples/01_simple_robot.rb
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
Or use the provided rake tasks:
|
|
239
|
-
|
|
240
|
-
```bash
|
|
241
|
-
bundle exec rake examples:all # Run all examples
|
|
242
|
-
bundle exec rake examples:run[1] # Run specific example by number
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
## Shared Example Setup (`examples/common.rb`)
|
|
246
|
-
|
|
247
|
-
Most numbered examples (`01_*.rb` through `35_*.rb`) pull in a shared setup file:
|
|
248
|
-
|
|
249
|
-
```ruby
|
|
250
|
-
require_relative "common"
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
The line sits below each example's header comment rather than at the very top of
|
|
254
|
-
the file, and three examples do without it entirely —
|
|
255
|
-
`32_newsletter_reader.rb`, `33_stock_generator.rb`, and `33_stock_predictor.rb`.
|
|
256
|
-
|
|
257
|
-
`common.rb` handles the shared boilerplate so individual examples stay focused:
|
|
258
|
-
|
|
259
|
-
- **`LLM` hash** — frozen lookup of provider/model pairs accessible as `LLM[:default]`, `LLM[:local]`, `LLM[:anthropic]`. Each entry is a `LlmConfig = Data.define(:provider, :model)` value, so you access the model string as `LLM[:default].model`.
|
|
260
|
-
- **`RubyLLM.configure`** — sets a null logger and `LLM[:default].model` as the `default_model`.
|
|
261
|
-
- **`RobotLab.configure`** — sets a null logger.
|
|
262
|
-
- **Output helpers** — `banner(title)`, `section(title)`, `hr`, and `show_code(ruby_string, label:)` (Rouge-highlighted) for consistent terminal formatting.
|
|
263
|
-
|
|
264
|
-
## Template Path via direnv
|
|
265
|
-
|
|
266
|
-
Examples that bundle their own `prompts/` directory ship with a `.envrc` file:
|
|
267
|
-
|
|
268
|
-
```
|
|
269
|
-
examples/.envrc
|
|
270
|
-
examples/14_rusty_circuit/.envrc
|
|
271
|
-
examples/15_memory_network_and_bus/.envrc
|
|
272
|
-
examples/16_writers_room/.envrc
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
Each sets `ROBOT_LAB_TEMPLATE_PATH` to the local `prompts/` directory when [direnv](https://direnv.net/) is active. `common.rb` also sets this variable as a fallback if `direnv` has not loaded the `.envrc`:
|
|
276
|
-
|
|
277
|
-
```ruby
|
|
278
|
-
ENV["ROBOT_LAB_TEMPLATE_PATH"] ||= File.join(__dir__, "prompts")
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
This means examples work correctly whether you run them from the project root with rake tasks or directly from inside the example's own directory.
|
|
282
|
-
|
|
283
|
-
## Message Bus
|
|
284
|
-
|
|
285
|
-
Robots can communicate bidirectionally via a message bus, enabling convergence loops and negotiation patterns. This example demonstrates a comedy critic tasking a comedian to generate jokes until one passes:
|
|
286
|
-
|
|
287
|
-
```ruby
|
|
288
|
-
ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
|
|
289
|
-
require "robot_lab"
|
|
290
|
-
|
|
291
|
-
MAX_ATTEMPTS = 5
|
|
292
|
-
|
|
293
|
-
class Comedian < RobotLab::Robot
|
|
294
|
-
TEMP_START = 0.2
|
|
295
|
-
TEMP_STEP = 0.2
|
|
296
|
-
|
|
297
|
-
def initialize(bus:)
|
|
298
|
-
super(name: "bob", template: :comedian, bus: bus, temperature: TEMP_START)
|
|
299
|
-
@attempts = 0
|
|
300
|
-
on_message do |message|
|
|
301
|
-
@attempts += 1
|
|
302
|
-
temp = [TEMP_START + TEMP_STEP * (@attempts - 1), 1.0].min
|
|
303
|
-
with_temperature(temp)
|
|
304
|
-
joke = run(message.content.to_s).reply.strip
|
|
305
|
-
send_reply(to: message.from.to_sym, content: joke, in_reply_to: message.key)
|
|
306
|
-
end
|
|
307
|
-
end
|
|
308
|
-
|
|
309
|
-
attr_reader :attempts
|
|
310
|
-
end
|
|
311
|
-
|
|
312
|
-
class ComedyCritic < RobotLab::Robot
|
|
313
|
-
def initialize(bus:)
|
|
314
|
-
super(name: "alice", template: :comedy_critic, bus: bus)
|
|
315
|
-
@accepted = false
|
|
316
|
-
@rounds = 0
|
|
317
|
-
on_message do |message|
|
|
318
|
-
@rounds += 1
|
|
319
|
-
verdict = run("Evaluate this joke:\n\n#{message.content}").reply.strip
|
|
320
|
-
@accepted = verdict.start_with?("FUNNY")
|
|
321
|
-
# The @rounds guard is what terminates the loop — without it the critic
|
|
322
|
-
# keeps sending Bob back forever.
|
|
323
|
-
send_message(to: :bob, content: "Not funny enough. Try again.") unless @accepted || @rounds >= MAX_ATTEMPTS
|
|
324
|
-
end
|
|
325
|
-
end
|
|
326
|
-
|
|
327
|
-
attr_reader :accepted
|
|
328
|
-
end
|
|
329
|
-
|
|
330
|
-
bus = TypedBus::MessageBus.new
|
|
331
|
-
bob = Comedian.new(bus: bus)
|
|
332
|
-
alice = ComedyCritic.new(bus: bus)
|
|
333
|
-
|
|
334
|
-
alice.send_message(to: :bob, content: "Tell me a funny robot joke.")
|
|
335
|
-
puts "Attempts: #{bob.attempts} / #{MAX_ATTEMPTS}"
|
|
336
|
-
puts "Accepted: #{alice.accepted}"
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
Key patterns demonstrated:
|
|
340
|
-
|
|
341
|
-
- **Robot subclasses** with templates for prompt management
|
|
342
|
-
- **Auto-ack** via 1-arg `on_message` blocks
|
|
343
|
-
- **`send_reply(to:, content:, in_reply_to:)`** for correlated responses
|
|
344
|
-
- **Temperature ramping** (0.2 → 1.0) for increasing creativity
|
|
345
|
-
- **Convergence loop** that terminates when the critic approves *or* `MAX_ATTEMPTS` is reached
|
|
346
|
-
|
|
347
|
-
> [!WARNING]
|
|
348
|
-
> `MAX_ATTEMPTS` only bounds the loop because the critic checks it before sending
|
|
349
|
-
> Bob back. Declaring the constant without testing it leaves the two robots
|
|
350
|
-
> messaging each other indefinitely.
|
|
351
|
-
|
|
352
|
-
Run: `bundle exec ruby examples/12_message_bus.rb`
|
|
353
|
-
|
|
354
|
-
## Spawning Robots
|
|
355
|
-
|
|
356
|
-
Robots can create new specialist robots at runtime using `spawn`. A dispatcher receives questions, decides what kind of specialist is needed, and spawns one on the fly. The bus is created lazily — no explicit setup required:
|
|
357
|
-
|
|
358
|
-
```ruby
|
|
359
|
-
ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
|
|
360
|
-
require "robot_lab"
|
|
361
|
-
|
|
362
|
-
QUESTIONS = [
|
|
363
|
-
"Why did the Roman Empire fall?",
|
|
364
|
-
"Write a haiku about recursion.",
|
|
365
|
-
"What is the square root of 144?",
|
|
366
|
-
].freeze
|
|
367
|
-
|
|
368
|
-
class Dispatcher < RobotLab::Robot
|
|
369
|
-
attr_reader :spawned
|
|
370
|
-
|
|
371
|
-
def initialize(bus: nil)
|
|
372
|
-
super(name: "dispatcher", template: :dispatcher, bus: bus)
|
|
373
|
-
@spawned = {}
|
|
374
|
-
@pending = {}
|
|
375
|
-
|
|
376
|
-
on_message do |message|
|
|
377
|
-
puts " Dispatcher <- :#{message.from} replied"
|
|
378
|
-
puts " | #{message.content.to_s.lines.first&.strip}"
|
|
379
|
-
@pending.delete(message.from)
|
|
380
|
-
end
|
|
381
|
-
end
|
|
382
|
-
|
|
383
|
-
def dispatch(question)
|
|
384
|
-
plan = run(question).last_text_content.strip
|
|
385
|
-
role, instruction = plan.split("\n", 2)
|
|
386
|
-
role = role.strip.downcase.gsub(/\s+/, "_")
|
|
387
|
-
instruction = instruction&.strip || "You are a helpful #{role}."
|
|
388
|
-
|
|
389
|
-
specialist = @spawned[role] ||= spawn(
|
|
390
|
-
name: role,
|
|
391
|
-
system_prompt: instruction
|
|
392
|
-
)
|
|
393
|
-
|
|
394
|
-
@pending[role] = question
|
|
395
|
-
|
|
396
|
-
specialist.send_message(to: :dispatcher, content:
|
|
397
|
-
specialist.run(question).last_text_content.strip
|
|
398
|
-
)
|
|
399
|
-
end
|
|
400
|
-
end
|
|
401
|
-
|
|
402
|
-
dispatcher = Dispatcher.new
|
|
403
|
-
|
|
404
|
-
QUESTIONS.each_with_index do |question, i|
|
|
405
|
-
puts "\nQuestion #{i + 1}: #{question}"
|
|
406
|
-
dispatcher.dispatch(question)
|
|
407
|
-
end
|
|
408
|
-
|
|
409
|
-
puts "\nSpecialists spawned: #{dispatcher.spawned.keys.join(', ')}"
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
Key patterns demonstrated:
|
|
413
|
-
|
|
414
|
-
- **`spawn`** for dynamic robot creation (bus created lazily)
|
|
415
|
-
- **`on_message`** for reply handling
|
|
416
|
-
- **LLM-driven delegation** — the dispatcher asks its LLM what specialist to create
|
|
417
|
-
- **Specialist reuse** — spawned robots are cached and reused across questions
|
|
418
|
-
|
|
419
|
-
Run: `bundle exec ruby examples/13_spawn.rb`
|
|
420
|
-
|
|
421
|
-
## See Also
|
|
422
|
-
|
|
423
|
-
- [Getting Started](../getting-started/index.md)
|
|
424
|
-
- [Guides](../guides/index.md)
|
|
425
|
-
- [API Reference](../api/index.md)
|