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/concepts.md
DELETED
|
@@ -1,641 +0,0 @@
|
|
|
1
|
-
# Core Concepts
|
|
2
|
-
|
|
3
|
-
Understanding the fundamental concepts in RobotLab will help you build effective AI applications.
|
|
4
|
-
|
|
5
|
-
## Robot
|
|
6
|
-
|
|
7
|
-
A **Robot** is an LLM-powered agent that inherits from `RubyLLM::Agent`. Each robot wraps a persistent chat session created at initialization and provides template-based prompts, tools, memory, and MCP integration. Robots are created using keyword arguments via the `RobotLab.build` factory method.
|
|
8
|
-
|
|
9
|
-
Each robot has:
|
|
10
|
-
|
|
11
|
-
- **Name**: An identifier. Nothing is auto-generated — if you omit `name:` the robot is literally named `"robot"`. The value is load-bearing: a robot still named `"robot"` is treated as "unnamed", which is what lets a template's `robot_name:` front matter key take effect. Give every robot an explicit name.
|
|
12
|
-
- **Template**: A `.md` file with YAML front matter managed by prompt_manager, referenced by symbol
|
|
13
|
-
- **System Prompt**: Inline instructions (can be used alone or combined with a template)
|
|
14
|
-
- **Model**: The LLM model to use (defaults to `RobotLab.config.ruby_llm.model`)
|
|
15
|
-
- **Provider**: Optional LLM provider for local models (Ollama, GPUStack, etc.)
|
|
16
|
-
- **Skills**: Composable template behaviors prepended before the main template
|
|
17
|
-
- **Local Tools**: `RubyLLM::Tool` subclasses or `RobotLab::Tool` instances (with automatic error handling)
|
|
18
|
-
- **Streaming**: Real-time content via stored `on_content` callback or per-call block
|
|
19
|
-
- **Memory**: Persistent key-value store across runs
|
|
20
|
-
|
|
21
|
-
```ruby
|
|
22
|
-
# Robot with template (references prompts/support.md)
|
|
23
|
-
robot = RobotLab.build(
|
|
24
|
-
name: "support_agent",
|
|
25
|
-
template: :support,
|
|
26
|
-
context: { tone: "friendly", department: "billing" },
|
|
27
|
-
local_tools: [OrderLookup, RefundProcessor],
|
|
28
|
-
model: "claude-sonnet-4"
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
# Attached tools are only sent when the run asks for them
|
|
32
|
-
robot.run("Where is order 4471?", tools: :inherit)
|
|
33
|
-
|
|
34
|
-
# Robot with inline system prompt
|
|
35
|
-
robot = RobotLab.build(
|
|
36
|
-
name: "helper",
|
|
37
|
-
system_prompt: "You are a friendly customer support agent."
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
# Bare robot configured via chaining
|
|
41
|
-
robot = RobotLab.build(name: "bot")
|
|
42
|
-
robot.with_instructions("Be concise.").with_temperature(0.3).run("Hello")
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
The primary method is `robot.run("message")`, which takes a positional string argument and returns a `RobotResult`:
|
|
46
|
-
|
|
47
|
-
```ruby
|
|
48
|
-
result = robot.run("What is 2 + 2?")
|
|
49
|
-
puts result.last_text_content # => "4"
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
Standalone robots persist their conversation history and memory across runs:
|
|
53
|
-
|
|
54
|
-
```ruby
|
|
55
|
-
robot.run("My name is Alice.")
|
|
56
|
-
result = robot.run("What is my name?")
|
|
57
|
-
puts result.last_text_content # => "Your name is Alice."
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Configuration
|
|
61
|
-
|
|
62
|
-
RobotLab uses `MywayConfig` for configuration. Values are loaded automatically from multiple sources in priority order (lowest to highest):
|
|
63
|
-
|
|
64
|
-
1. Bundled defaults (`lib/robot_lab/config/defaults.yml`)
|
|
65
|
-
2. Environment-specific overrides (development, test, production)
|
|
66
|
-
3. XDG user config (`~/.config/robot_lab/robot_lab.yml`)
|
|
67
|
-
4. Project config (`./config/robot_lab.yml`)
|
|
68
|
-
5. Environment variables (`ROBOT_LAB_*` prefix)
|
|
69
|
-
6. Constructor parameters
|
|
70
|
-
|
|
71
|
-
```ruby
|
|
72
|
-
# Access configuration values
|
|
73
|
-
RobotLab.config.ruby_llm.model #=> "claude-sonnet-4"
|
|
74
|
-
RobotLab.config.ruby_llm.request_timeout #=> 120
|
|
75
|
-
|
|
76
|
-
# Set API keys via environment variables
|
|
77
|
-
# ROBOT_LAB_RUBY_LLM__ANTHROPIC_API_KEY=sk-ant-...
|
|
78
|
-
# ROBOT_LAB_RUBY_LLM__OPENAI_API_KEY=sk-...
|
|
79
|
-
|
|
80
|
-
# A configure block also exists, for runtime-only attributes such as the logger
|
|
81
|
-
RobotLab.configure do |c|
|
|
82
|
-
c.logger = Logger.new(File::NULL)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
# Reload configuration
|
|
86
|
-
RobotLab.reload_config!
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
> [!IMPORTANT]
|
|
90
|
-
> Two easy mistakes. First, the user config file is
|
|
91
|
-
> `~/.config/robot_lab/**robot_lab**.yml` — the filename repeats the app name, and
|
|
92
|
-
> `config.yml` is never read. Second, the `defaults:` wrapper used inside the gem's
|
|
93
|
-
> bundled `defaults.yml` is silently ignored in your own files; write keys flat, or
|
|
94
|
-
> under a section named for the current environment. The user file honours a
|
|
95
|
-
> `development:` / `test:` / `production:` section; `./config/robot_lab.yml` must be
|
|
96
|
-
> flat outside Rails but **must** be environment-sectioned under Rails. See
|
|
97
|
-
> [Configuration](getting-started/configuration.md) for the full matrix.
|
|
98
|
-
|
|
99
|
-
## Network
|
|
100
|
-
|
|
101
|
-
A **Network** is a collection of robots orchestrated using [SimpleFlow](https://github.com/MadBomber/simple_flow) pipelines. Networks provide:
|
|
102
|
-
|
|
103
|
-
- **Task-Based Orchestration**: Define tasks with dependencies and routing
|
|
104
|
-
- **Parallel Execution**: Tasks with the same dependencies run concurrently
|
|
105
|
-
- **Optional Task Activation**: Dynamic routing based on robot output
|
|
106
|
-
- **Per-Task Configuration**: Each task can have its own context, tools, and MCP servers
|
|
107
|
-
- **Shared Memory**: All robots in a network share a reactive memory instance
|
|
108
|
-
|
|
109
|
-
```ruby
|
|
110
|
-
network = RobotLab.create_network(name: "customer_service") do
|
|
111
|
-
task :classifier, classifier_robot, depends_on: :none
|
|
112
|
-
task :billing, billing_robot,
|
|
113
|
-
context: { department: "billing" },
|
|
114
|
-
depends_on: :optional
|
|
115
|
-
task :technical, technical_robot,
|
|
116
|
-
context: { department: "technical" },
|
|
117
|
-
depends_on: :optional
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
result = network.run(message: "I was charged twice for my subscription.")
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
## Task
|
|
124
|
-
|
|
125
|
-
A **Task** wraps a robot for use in a network pipeline with per-task configuration:
|
|
126
|
-
|
|
127
|
-
- **Context**: Task-specific context deep-merged with network run params
|
|
128
|
-
- **MCP**: MCP servers available to this task (`:none`, `:inherit`, or a name array)
|
|
129
|
-
- **Tools**: Tools available to this task (`:none`, `:inherit`, or a name array)
|
|
130
|
-
- **Memory**: Task-specific memory
|
|
131
|
-
- **Dependencies**: `:none`, `[:task1, :task2]`, or `:optional`
|
|
132
|
-
- **Config**: a `RunConfig` — but see the caveat below
|
|
133
|
-
- **Poller group**: `poller_group:` (defaults to `:default`)
|
|
134
|
-
|
|
135
|
-
```ruby
|
|
136
|
-
# The robot must already have the tools ATTACHED...
|
|
137
|
-
billing_robot = RobotLab.build(
|
|
138
|
-
name: "billing",
|
|
139
|
-
system_prompt: "You handle billing.",
|
|
140
|
-
local_tools: [RefundTool, InvoiceTool, AuditTool] # attached as CLASSES
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
task :billing, billing_robot,
|
|
144
|
-
context: { department: "billing", escalation_level: 2 },
|
|
145
|
-
tools: [RefundTool, InvoiceTool], # ...and this SELECTS from them
|
|
146
|
-
depends_on: :optional
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
> [!WARNING]
|
|
150
|
-
> An explicit `tools:` array is a **name allowlist** over tools the robot already
|
|
151
|
-
> has attached — it is not a way to attach new tools, and it is not a
|
|
152
|
-
> local-vs-MCP switch. Attach tools with `local_tools:` when building the robot,
|
|
153
|
-
> then filter here.
|
|
154
|
-
>
|
|
155
|
-
> **The allowlist entries must match the form the tool was attached in.** Matching
|
|
156
|
-
> is a string comparison against each attached tool's `name`, and `Class#name`
|
|
157
|
-
> differs from `RubyLLM::Tool#name`:
|
|
158
|
-
>
|
|
159
|
-
> | Attached as | Matching allowlist entry | Does not match |
|
|
160
|
-
> |---|---|---|
|
|
161
|
-
> | `local_tools: [RefundTool]` (class) | `[RefundTool]` or `%w[RefundTool]` | `%w[refund]` |
|
|
162
|
-
> | `local_tools: [RefundTool.new]` (instance) | `%w[refund]` | `[RefundTool]` |
|
|
163
|
-
>
|
|
164
|
-
> A task-level `config:` is merged into the network config, so like a
|
|
165
|
-
> network-level config it propagates **only `mcp` and `tools`** — not `model`,
|
|
166
|
-
> `temperature`, or callbacks.
|
|
167
|
-
|
|
168
|
-
## SimpleFlow::Result
|
|
169
|
-
|
|
170
|
-
Networks use `SimpleFlow::Result` for data flow between tasks:
|
|
171
|
-
|
|
172
|
-
```ruby
|
|
173
|
-
result.value # Current task's output (RobotResult)
|
|
174
|
-
result.context # Accumulated context, keyed by ROBOT name
|
|
175
|
-
result.continue? # Whether the pipeline is still continuing
|
|
176
|
-
result.errors # Accumulated errors
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
> [!NOTE]
|
|
180
|
-
> There is no `halted?` and no `continued?` — both raise `NoMethodError`. The
|
|
181
|
-
> predicate is `continue?`. Note also that `result.context` is keyed by each
|
|
182
|
-
> robot's `name:`, not by its task name.
|
|
183
|
-
|
|
184
|
-
### Result Methods
|
|
185
|
-
|
|
186
|
-
The complete public API is `activate`, `activated_steps`, `context`, `continue`,
|
|
187
|
-
`continue?`, `errors`, `halt`, `value`, `with_context`, and `with_error`.
|
|
188
|
-
|
|
189
|
-
| Method | Purpose |
|
|
190
|
-
|--------|---------|
|
|
191
|
-
| `continue(value)` | Continue to next tasks |
|
|
192
|
-
| `halt(value)` | Stop pipeline execution |
|
|
193
|
-
| `with_context(key, val)` | Add data to context |
|
|
194
|
-
| `with_error(key, message)` | Record an error |
|
|
195
|
-
| `activate(task_name)` | Enable optional task |
|
|
196
|
-
| `activated_steps` | Tasks activated so far |
|
|
197
|
-
|
|
198
|
-
## Tool
|
|
199
|
-
|
|
200
|
-
**Tools** give robots the ability to interact with external systems. `RobotLab::Tool` extends `RubyLLM::Tool` with graceful error handling — if `execute` raises a `StandardError`, the error is caught and returned as a plain-text string (`"Error (tool_name): message"`) so the LLM can reason about it. Critical tools can opt out with `self.raise_on_error = true`.
|
|
201
|
-
|
|
202
|
-
There are two patterns for defining tools:
|
|
203
|
-
|
|
204
|
-
### RubyLLM::Tool Subclass (Preferred)
|
|
205
|
-
|
|
206
|
-
```ruby
|
|
207
|
-
class Calculator < RubyLLM::Tool
|
|
208
|
-
description "Performs basic arithmetic operations"
|
|
209
|
-
|
|
210
|
-
param :operation, type: "string", desc: "The operation (add, subtract, multiply, divide)"
|
|
211
|
-
param :a, type: "number", desc: "First operand"
|
|
212
|
-
param :b, type: "number", desc: "Second operand"
|
|
213
|
-
|
|
214
|
-
def execute(operation:, a:, b:)
|
|
215
|
-
case operation
|
|
216
|
-
when "add" then a + b
|
|
217
|
-
when "subtract" then a - b
|
|
218
|
-
when "multiply" then a * b
|
|
219
|
-
when "divide" then a.to_f / b
|
|
220
|
-
else "Unknown operation: #{operation}"
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
robot = RobotLab.build(
|
|
226
|
-
name: "math_bot",
|
|
227
|
-
system_prompt: "You can do math.",
|
|
228
|
-
local_tools: [Calculator]
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
# tools: :inherit is required -- run() sends no tools by default
|
|
232
|
-
robot.run("What is 17 * 23?", tools: :inherit)
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
> [!WARNING]
|
|
236
|
-
> `Robot#run` defaults to `mcp: :none, tools: :none`, and an explicit `:none`
|
|
237
|
-
> means "send zero tools this turn". Attaching tools with `local_tools:` does
|
|
238
|
-
> **not** by itself make them available — a plain `robot.run("...")` sends the
|
|
239
|
-
> model no tools at all. Pass `tools: :inherit` on the run (and
|
|
240
|
-
> `mcp: :inherit, tools: :inherit` for MCP servers).
|
|
241
|
-
>
|
|
242
|
-
> For a **standalone** robot, do not pass `tools: :inherit` to `RobotLab.build`.
|
|
243
|
-
> Build-time `:inherit` resolves against the parent level, which for a standalone
|
|
244
|
-
> robot is the global `:none` — that produces an allowlist matching nothing and
|
|
245
|
-
> suppresses the tools even when the run asks for them. Leave `tools:` unset.
|
|
246
|
-
>
|
|
247
|
-
> Inside a **network** it means the opposite: build-time `tools: :inherit` is
|
|
248
|
-
> exactly how a robot opts into the allowlist carried by the network's `config:`.
|
|
249
|
-
> With `RunConfig.new(tools: %w[RefundTool])` on the network and `tools: :inherit`
|
|
250
|
-
> on both the robot and its task, the robot sends only `refund`; leaving the
|
|
251
|
-
> robot's `tools:` unset sends everything it has attached instead.
|
|
252
|
-
|
|
253
|
-
### RobotLab::Tool.create Factory
|
|
254
|
-
|
|
255
|
-
```ruby
|
|
256
|
-
tool = RobotLab::Tool.create(
|
|
257
|
-
name: "get_weather",
|
|
258
|
-
description: "Get current weather for a location",
|
|
259
|
-
parameters: {
|
|
260
|
-
type: "object",
|
|
261
|
-
properties: {
|
|
262
|
-
location: { type: "string", description: "City name" }
|
|
263
|
-
},
|
|
264
|
-
required: ["location"]
|
|
265
|
-
}
|
|
266
|
-
) { |args| WeatherService.current(args[:location]) }
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
## RobotResult
|
|
270
|
-
|
|
271
|
-
`RobotResult` captures the output of a single `robot.run(...)` call:
|
|
272
|
-
|
|
273
|
-
```ruby
|
|
274
|
-
result = robot.run("Hello!")
|
|
275
|
-
|
|
276
|
-
result.last_text_content # => "Hi there!" (String or nil)
|
|
277
|
-
result.reply # => alias for last_text_content
|
|
278
|
-
result.output # => [TextMessage] built from the final response text
|
|
279
|
-
result.tool_calls # => [] (see note below -- effectively always empty)
|
|
280
|
-
result.robot_name # => "assistant"
|
|
281
|
-
result.stop_reason # => nil (always -- see note below)
|
|
282
|
-
result.has_tool_calls? # => false
|
|
283
|
-
result.checksum # => "a1b2c3d4..." (for dedup)
|
|
284
|
-
result.duration # => Float or nil (elapsed seconds, set in pipeline execution)
|
|
285
|
-
result.raw # => raw LLM response object
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
> [!NOTE]
|
|
289
|
-
> `result.tool_calls` and `result.has_tool_calls?` read the **final** assistant
|
|
290
|
-
> message. By the time `run` returns, ruby_llm's tool loop has already completed
|
|
291
|
-
> and that message carries no tool calls — so in practice `tool_calls` is always
|
|
292
|
-
> empty and `has_tool_calls?` is always `false`. To observe tool activity, use
|
|
293
|
-
> the `on_tool_call` / `on_tool_result` callbacks instead. Likewise
|
|
294
|
-
> `result.output` is a single-element array built from the final response text,
|
|
295
|
-
> not a transcript of the whole turn.
|
|
296
|
-
>
|
|
297
|
-
> `result.stop_reason` is likewise **always `nil`**: `RubyLLM::Message` does not
|
|
298
|
-
> define `stop_reason`, and `RobotResult` only populates the field when the
|
|
299
|
-
> response responds to it. It is dropped from `result.export` for the same reason.
|
|
300
|
-
> Because `stopped?` is derived from the absence of tool calls, it is
|
|
301
|
-
> correspondingly always `true`. Do not branch on `"end_turn"` / `"tool_use"`.
|
|
302
|
-
|
|
303
|
-
## Memory
|
|
304
|
-
|
|
305
|
-
**Memory** is a reactive key-value store that provides persistent storage across robot executions. Standalone robots use their own inherent memory; robots in a network share the network's memory.
|
|
306
|
-
|
|
307
|
-
```ruby
|
|
308
|
-
# Standalone robot with inherent memory
|
|
309
|
-
robot = RobotLab.build(name: "assistant", system_prompt: "You are helpful.")
|
|
310
|
-
robot.run("My name is Alice")
|
|
311
|
-
robot.run("What's my name?") # Memory persists across runs
|
|
312
|
-
|
|
313
|
-
# Access robot's memory directly
|
|
314
|
-
robot.memory[:user_id] = 123
|
|
315
|
-
robot.memory.data[:category] = "billing"
|
|
316
|
-
robot.memory.data.category # => "billing" (method-style access)
|
|
317
|
-
|
|
318
|
-
# Runtime memory injection
|
|
319
|
-
robot.run("Help me", memory: { session_id: "abc123" })
|
|
320
|
-
|
|
321
|
-
# Reset the key-value store (does NOT clear chat history)
|
|
322
|
-
robot.reset_memory
|
|
323
|
-
|
|
324
|
-
# Clear chat history (does NOT touch the key-value store)
|
|
325
|
-
robot.clear_messages(keep_system: true)
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
> [!NOTE]
|
|
329
|
-
> `reset_memory` and `clear_messages` are independent. A robot's conversation
|
|
330
|
-
> history and its key-value memory are two separate stores; clearing one leaves
|
|
331
|
-
> the other intact.
|
|
332
|
-
|
|
333
|
-
### Reserved Memory Keys
|
|
334
|
-
|
|
335
|
-
| Key | Purpose |
|
|
336
|
-
|-----|---------|
|
|
337
|
-
| `:data` | Runtime data (StateProxy for method-style access) |
|
|
338
|
-
| `:results` | Accumulated robot results |
|
|
339
|
-
| `:messages` | Conversation history |
|
|
340
|
-
| `:session_id` | Session identifier for history persistence |
|
|
341
|
-
| `:cache` | Semantic cache (RubyLLM::SemanticCache when the optional gem is installed, else nil) |
|
|
342
|
-
|
|
343
|
-
### Reactive Memory in Networks
|
|
344
|
-
|
|
345
|
-
In a network, shared memory supports pub/sub semantics for inter-robot communication:
|
|
346
|
-
|
|
347
|
-
```ruby
|
|
348
|
-
# Robot A writes to shared memory
|
|
349
|
-
network.memory.set(:sentiment, { score: 0.8 })
|
|
350
|
-
|
|
351
|
-
# Robot B reads (blocking until available)
|
|
352
|
-
result = network.memory.get(:sentiment, wait: true)
|
|
353
|
-
result = network.memory.get(:sentiment, wait: 30) # timeout in seconds
|
|
354
|
-
|
|
355
|
-
# Multiple keys -- the timeout applies PER MISSING KEY, not to the call as a whole
|
|
356
|
-
results = network.memory.get(:sentiment, :entities, :keywords, wait: 60)
|
|
357
|
-
|
|
358
|
-
# Subscribe to changes
|
|
359
|
-
network.memory.subscribe(:status) do |change|
|
|
360
|
-
puts "#{change.key} changed by #{change.writer}: #{change.value}"
|
|
361
|
-
end
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
> [!WARNING]
|
|
365
|
-
> A blocking `get` that times out **raises `RobotLab::AwaitTimeout`** — it does
|
|
366
|
-
> not return `nil`. Wrap it in a `rescue` if a missing key is an acceptable
|
|
367
|
-
> outcome. With several keys, the timeout is applied to each missing key in turn,
|
|
368
|
-
> so `get(:a, :b, :c, wait: 60)` can block for up to 180 seconds.
|
|
369
|
-
|
|
370
|
-
## MCP (Model Context Protocol)
|
|
371
|
-
|
|
372
|
-
**MCP** allows robots to connect to external tool servers:
|
|
373
|
-
|
|
374
|
-
```ruby
|
|
375
|
-
robot = RobotLab.build(
|
|
376
|
-
name: "developer",
|
|
377
|
-
system_prompt: "You are a developer assistant.",
|
|
378
|
-
mcp: [
|
|
379
|
-
{ name: "filesystem", transport: { type: "stdio", command: "mcp-server-filesystem" } },
|
|
380
|
-
{ name: "github", transport: { type: "stdio", command: "mcp-server-github" } }
|
|
381
|
-
]
|
|
382
|
-
)
|
|
383
|
-
|
|
384
|
-
# Connect the servers AND expose their tools for this run
|
|
385
|
-
robot.run("List the Ruby files in ./lib", mcp: :inherit, tools: :inherit)
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
> [!IMPORTANT]
|
|
389
|
-
> `transport:` must be a **nested hash** — `transport: { type: "stdio", command: ..., args: [...] }`.
|
|
390
|
-
> A flat `transport: stdio` with sibling `command:`/`args:` keys raises
|
|
391
|
-
> `NoMethodError: undefined method 'transform_keys' for an instance of String`,
|
|
392
|
-
> and that exception is **swallowed** rather than raised: the robot builds
|
|
393
|
-
> successfully with zero tools. It is not silent, though — a line is logged at
|
|
394
|
-
> `WARN` through `RobotLab.config.logger` (`Robot 'dev' error connecting to MCP
|
|
395
|
-
> server 'fs': undefined method 'transform_keys' for an instance of String`) and
|
|
396
|
-
> the server name lands in `robot.failed_mcp_server_names`. An *invalid* transport
|
|
397
|
-
> type is swallowed the same way; only a direct `MCP::Server.new` raises
|
|
398
|
-
> `ArgumentError`. Valid transport types are `stdio`, `sse`, `ws`,
|
|
399
|
-
> `websocket`, `streamable-http`, and `http` (`streamable_http` with an
|
|
400
|
-
> underscore is invalid).
|
|
401
|
-
>
|
|
402
|
-
> `mcp:` also defaults to `:none` on `run`. `mcp: :inherit` triggers the
|
|
403
|
-
> connection attempt; `tools: :inherit` is additionally required for the MCP
|
|
404
|
-
> tools to reach the model. MCP connection failures are logged and recorded in
|
|
405
|
-
> `robot.failed_mcp_server_names` — they are not raised.
|
|
406
|
-
|
|
407
|
-
MCP configuration follows a hierarchical resolution: `runtime > robot > network > global config`. Values can be `:none`, `:inherit`, or explicit arrays.
|
|
408
|
-
|
|
409
|
-
## Execution Flow
|
|
410
|
-
|
|
411
|
-
```mermaid
|
|
412
|
-
sequenceDiagram
|
|
413
|
-
participant User
|
|
414
|
-
participant Network
|
|
415
|
-
participant Pipeline
|
|
416
|
-
participant Task
|
|
417
|
-
participant Robot
|
|
418
|
-
participant LLM
|
|
419
|
-
participant Tool
|
|
420
|
-
|
|
421
|
-
User->>Network: run(message: "...", **context)
|
|
422
|
-
Network->>Pipeline: call_parallel(initial_result)
|
|
423
|
-
Pipeline->>Task: call(result)
|
|
424
|
-
Task->>Robot: call(enhanced_result)
|
|
425
|
-
Robot->>Robot: extract_run_context(result)
|
|
426
|
-
Robot->>LLM: ask(message)
|
|
427
|
-
|
|
428
|
-
alt Tool Call
|
|
429
|
-
LLM-->>Robot: tool_call
|
|
430
|
-
Robot->>Tool: execute(params)
|
|
431
|
-
Tool-->>Robot: result
|
|
432
|
-
Robot->>LLM: continue with result
|
|
433
|
-
end
|
|
434
|
-
|
|
435
|
-
LLM-->>Robot: response
|
|
436
|
-
Robot-->>Task: RobotResult
|
|
437
|
-
Task-->>Pipeline: result.continue(robot_result)
|
|
438
|
-
|
|
439
|
-
alt Optional Task Activated
|
|
440
|
-
Pipeline->>Task: call activated task
|
|
441
|
-
end
|
|
442
|
-
|
|
443
|
-
Pipeline-->>Network: final result
|
|
444
|
-
Network-->>User: SimpleFlow::Result
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
## Conditional Routing with ClassifierRobot
|
|
448
|
-
|
|
449
|
-
Use a custom Robot subclass to implement intelligent routing. Override `call(result)` to inspect the LLM output and activate optional tasks:
|
|
450
|
-
|
|
451
|
-
```ruby
|
|
452
|
-
class ClassifierRobot < RobotLab::Robot
|
|
453
|
-
def call(result)
|
|
454
|
-
# Extract context and message from the pipeline result
|
|
455
|
-
context = extract_run_context(result)
|
|
456
|
-
message = context.delete(:message)
|
|
457
|
-
|
|
458
|
-
# Run the robot to classify the input
|
|
459
|
-
robot_result = run(message, **context)
|
|
460
|
-
|
|
461
|
-
new_result = result
|
|
462
|
-
.with_context(@name.to_sym, robot_result)
|
|
463
|
-
.continue(robot_result)
|
|
464
|
-
|
|
465
|
-
# Activate the appropriate specialist based on classification
|
|
466
|
-
category = robot_result.last_text_content.to_s.strip.downcase
|
|
467
|
-
case category
|
|
468
|
-
when /billing/ then new_result.activate(:billing)
|
|
469
|
-
when /technical/ then new_result.activate(:technical)
|
|
470
|
-
else new_result.activate(:general)
|
|
471
|
-
end
|
|
472
|
-
end
|
|
473
|
-
end
|
|
474
|
-
|
|
475
|
-
# Build the classifier (uses a .md template with YAML front matter)
|
|
476
|
-
classifier = ClassifierRobot.new(
|
|
477
|
-
name: "classifier",
|
|
478
|
-
template: :classifier,
|
|
479
|
-
model: "claude-sonnet-4"
|
|
480
|
-
)
|
|
481
|
-
|
|
482
|
-
# Build specialist robots
|
|
483
|
-
billing_robot = RobotLab.build(name: "billing", template: :billing)
|
|
484
|
-
technical_robot = RobotLab.build(name: "technical", template: :technical)
|
|
485
|
-
general_robot = RobotLab.build(name: "general", template: :general)
|
|
486
|
-
|
|
487
|
-
# Create network with optional task routing
|
|
488
|
-
network = RobotLab.create_network(name: "support_network") do
|
|
489
|
-
task :classifier, classifier, depends_on: :none
|
|
490
|
-
task :billing, billing_robot, depends_on: :optional
|
|
491
|
-
task :technical, technical_robot, depends_on: :optional
|
|
492
|
-
task :general, general_robot, depends_on: :optional
|
|
493
|
-
end
|
|
494
|
-
|
|
495
|
-
result = network.run(message: "I was charged twice for my subscription.")
|
|
496
|
-
puts result.value.last_text_content
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
## Message Bus
|
|
500
|
-
|
|
501
|
-
The **Message Bus** enables bidirectional, cyclic communication between robots via `typed_bus`. While Networks enforce DAG-based execution, the bus supports negotiation loops, convergence patterns, and multi-turn dialogues.
|
|
502
|
-
|
|
503
|
-
```ruby
|
|
504
|
-
bus = TypedBus::MessageBus.new
|
|
505
|
-
|
|
506
|
-
bob = RobotLab.build(name: "bob", system_prompt: "You tell jokes.", bus: bus)
|
|
507
|
-
alice = RobotLab.build(name: "alice", system_prompt: "You evaluate jokes.", bus: bus)
|
|
508
|
-
|
|
509
|
-
# Register handlers
|
|
510
|
-
bob.on_message do |message|
|
|
511
|
-
joke = bob.run(message.content.to_s).last_text_content
|
|
512
|
-
bob.send_reply(to: message.from.to_sym, content: joke, in_reply_to: message.key)
|
|
513
|
-
end
|
|
514
|
-
|
|
515
|
-
alice.on_message do |message|
|
|
516
|
-
verdict = alice.run("Is this funny? #{message.content}").last_text_content
|
|
517
|
-
# Send another request if not satisfied
|
|
518
|
-
alice.send_message(to: :bob, content: "Try again.") unless verdict.start_with?("FUNNY")
|
|
519
|
-
end
|
|
520
|
-
|
|
521
|
-
# Start the conversation
|
|
522
|
-
alice.send_message(to: :bob, content: "Tell me a robot joke.")
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
Key features:
|
|
526
|
-
|
|
527
|
-
- **Typed channels** — only `RobotMessage` objects accepted per channel
|
|
528
|
-
- **Auto-ACK** — 1-arg `on_message` blocks auto-acknowledge; 2-arg blocks give manual control
|
|
529
|
-
- **Reply correlation** — `send_reply(to:, content:, in_reply_to:)` tracks threads via `in_reply_to`
|
|
530
|
-
- **Independent of Network** — bus works without a Network pipeline
|
|
531
|
-
|
|
532
|
-
### Dynamic Spawning
|
|
533
|
-
|
|
534
|
-
Robots can create new robots at runtime using `spawn`. The bus is created lazily:
|
|
535
|
-
|
|
536
|
-
```ruby
|
|
537
|
-
dispatcher = RobotLab.build(name: "dispatcher", system_prompt: "You delegate work.")
|
|
538
|
-
|
|
539
|
-
# spawn creates a child on the same bus (bus created automatically)
|
|
540
|
-
helper = dispatcher.spawn(name: "helper", system_prompt: "You answer questions.")
|
|
541
|
-
answer = helper.run("What is 2+2?").last_text_content
|
|
542
|
-
helper.send_message(to: :dispatcher, content: answer)
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
Robots can also join a bus after creation with `with_bus`:
|
|
546
|
-
|
|
547
|
-
```ruby
|
|
548
|
-
bot = RobotLab.build(name: "latecomer", system_prompt: "Hello.")
|
|
549
|
-
bot.with_bus(existing_bus)
|
|
550
|
-
```
|
|
551
|
-
|
|
552
|
-
Multiple robots with the same name enable fan-out — messages sent to that name are delivered to all subscribers.
|
|
553
|
-
|
|
554
|
-
## Templates
|
|
555
|
-
|
|
556
|
-
Templates are `.md` files with YAML front matter, managed by the prompt_manager gem. They live in the configured template path (default: `./prompts/` or `app/prompts/` in Rails).
|
|
557
|
-
|
|
558
|
-
```markdown
|
|
559
|
-
---
|
|
560
|
-
description: Customer support classifier
|
|
561
|
-
model: claude-sonnet-4
|
|
562
|
-
temperature: 0.3
|
|
563
|
-
---
|
|
564
|
-
You are a request classifier. Analyze the user's request and classify it
|
|
565
|
-
as either "billing", "technical", or "general".
|
|
566
|
-
|
|
567
|
-
Respond with ONLY the category name, nothing else.
|
|
568
|
-
```
|
|
569
|
-
|
|
570
|
-
Reference templates by symbol when building robots:
|
|
571
|
-
|
|
572
|
-
```ruby
|
|
573
|
-
robot = RobotLab.build(
|
|
574
|
-
name: "classifier",
|
|
575
|
-
template: :classifier, # loads prompts/classifier.md
|
|
576
|
-
context: { tone: "professional" } # variables passed to the template
|
|
577
|
-
)
|
|
578
|
-
```
|
|
579
|
-
|
|
580
|
-
### Front Matter Keys
|
|
581
|
-
|
|
582
|
-
Templates support two categories of front matter keys:
|
|
583
|
-
|
|
584
|
-
**LLM Config:** only `model` and `temperature` take effect.
|
|
585
|
-
|
|
586
|
-
**Robot Extras:** `robot_name`, `description`, `tools`, `mcp`, `skills`, `parameters` — applied to the robot's identity and capabilities. These make templates self-contained: reading the `.md` file tells you everything about the robot.
|
|
587
|
-
|
|
588
|
-
> [!WARNING]
|
|
589
|
-
> Front matter is parsed into a `RunConfig` and then applied by dispatching
|
|
590
|
-
> `chat.with_<field>` for each field the chat responds to. The chat object only
|
|
591
|
-
> has `with_model` and `with_temperature`, so **`top_p`, `top_k`, `max_tokens`,
|
|
592
|
-
> `presence_penalty`, `frequency_penalty`, and `stop` are parsed and silently
|
|
593
|
-
> dropped** when declared in front matter. A template declaring all eight yields
|
|
594
|
-
> a chat with empty params.
|
|
595
|
-
>
|
|
596
|
-
> Those six *do* work as constructor keyword arguments or via a `config:`
|
|
597
|
-
> `RunConfig`, because that path goes through `with_params`:
|
|
598
|
-
>
|
|
599
|
-
> ```ruby
|
|
600
|
-
> RobotLab.build(name: "bot", template: :writer, max_tokens: 2000, top_p: 0.3)
|
|
601
|
-
> ```
|
|
602
|
-
|
|
603
|
-
```markdown
|
|
604
|
-
---
|
|
605
|
-
description: GitHub assistant with MCP tool access
|
|
606
|
-
robot_name: github_bot
|
|
607
|
-
tools:
|
|
608
|
-
- CodeSearchTool
|
|
609
|
-
mcp:
|
|
610
|
-
- name: github
|
|
611
|
-
transport:
|
|
612
|
-
type: stdio
|
|
613
|
-
command: npx
|
|
614
|
-
args: ["-y", "@modelcontextprotocol/server-github"]
|
|
615
|
-
model: claude-sonnet-4
|
|
616
|
-
---
|
|
617
|
-
You are a GitHub assistant. Use available tools to help with repository tasks.
|
|
618
|
-
```
|
|
619
|
-
|
|
620
|
-
> [!IMPORTANT]
|
|
621
|
-
> Note the **nested** `transport:` mapping above. A flat
|
|
622
|
-
> `transport: stdio` with sibling `command:`/`args:` keys raises a `NoMethodError`
|
|
623
|
-
> that is swallowed rather than propagated, leaving you with a robot that has no
|
|
624
|
-
> tools. Look for the `WARN` line on `RobotLab.config.logger` and check
|
|
625
|
-
> `robot.failed_mcp_server_names` — both record the failure.
|
|
626
|
-
|
|
627
|
-
```ruby
|
|
628
|
-
# Template provides everything — minimal constructor
|
|
629
|
-
robot = RobotLab.build(template: :github_assistant)
|
|
630
|
-
|
|
631
|
-
# ...but the tools and MCP servers it declares still have to be requested per run
|
|
632
|
-
robot.run("Open issues in MadBomber/robot_lab?", mcp: :inherit, tools: :inherit)
|
|
633
|
-
```
|
|
634
|
-
|
|
635
|
-
Constructor-provided values (`local_tools:`, `mcp:`, `name:`, `description:`) always take precedence over front matter values. In the cascade, front matter is the **base**: template front matter → `config:` RunConfig → constructor keyword arguments, with constructor arguments always winning.
|
|
636
|
-
|
|
637
|
-
## Next Steps
|
|
638
|
-
|
|
639
|
-
- [Quick Start Guide](getting-started/quick-start.md) - Build your first robot
|
|
640
|
-
- [Building Robots](guides/building-robots.md) - Detailed robot creation guide
|
|
641
|
-
- [Creating Networks](guides/creating-networks.md) - Network orchestration patterns
|