robot_lab 0.0.1 → 0.0.4

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.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/deploy-github-pages.yml +9 -9
  3. data/.irbrc +6 -0
  4. data/CHANGELOG.md +90 -0
  5. data/README.md +203 -46
  6. data/Rakefile +70 -1
  7. data/docs/api/core/index.md +12 -0
  8. data/docs/api/core/robot.md +478 -130
  9. data/docs/api/core/tool.md +205 -209
  10. data/docs/api/history/active-record-adapter.md +174 -94
  11. data/docs/api/history/config.md +186 -93
  12. data/docs/api/history/index.md +57 -61
  13. data/docs/api/history/thread-manager.md +123 -73
  14. data/docs/api/mcp/client.md +119 -48
  15. data/docs/api/mcp/index.md +75 -60
  16. data/docs/api/mcp/server.md +120 -136
  17. data/docs/api/mcp/transports.md +172 -184
  18. data/docs/api/streaming/context.md +157 -74
  19. data/docs/api/streaming/events.md +114 -166
  20. data/docs/api/streaming/index.md +74 -72
  21. data/docs/architecture/core-concepts.md +361 -112
  22. data/docs/architecture/index.md +97 -59
  23. data/docs/architecture/message-flow.md +138 -129
  24. data/docs/architecture/network-orchestration.md +197 -50
  25. data/docs/architecture/robot-execution.md +199 -146
  26. data/docs/architecture/state-management.md +255 -187
  27. data/docs/concepts.md +312 -48
  28. data/docs/examples/basic-chat.md +89 -77
  29. data/docs/examples/index.md +222 -47
  30. data/docs/examples/mcp-server.md +207 -203
  31. data/docs/examples/multi-robot-network.md +129 -35
  32. data/docs/examples/rails-application.md +159 -160
  33. data/docs/examples/tool-usage.md +295 -204
  34. data/docs/getting-started/configuration.md +275 -162
  35. data/docs/getting-started/index.md +1 -1
  36. data/docs/getting-started/installation.md +22 -13
  37. data/docs/getting-started/quick-start.md +166 -121
  38. data/docs/guides/building-robots.md +417 -212
  39. data/docs/guides/creating-networks.md +94 -24
  40. data/docs/guides/mcp-integration.md +152 -113
  41. data/docs/guides/memory.md +220 -164
  42. data/docs/guides/streaming.md +80 -110
  43. data/docs/guides/using-tools.md +259 -212
  44. data/docs/index.md +50 -37
  45. data/examples/01_simple_robot.rb +6 -9
  46. data/examples/02_tools.rb +6 -9
  47. data/examples/03_network.rb +13 -14
  48. data/examples/04_mcp.rb +5 -8
  49. data/examples/05_streaming.rb +5 -8
  50. data/examples/06_prompt_templates.rb +42 -37
  51. data/examples/07_network_memory.rb +13 -14
  52. data/examples/08_llm_config.rb +140 -0
  53. data/examples/09_chaining.rb +223 -0
  54. data/examples/10_memory.rb +331 -0
  55. data/examples/11_network_introspection.rb +230 -0
  56. data/examples/12_message_bus.rb +74 -0
  57. data/examples/13_spawn.rb +90 -0
  58. data/examples/14_rusty_circuit/comic.rb +143 -0
  59. data/examples/14_rusty_circuit/display.rb +203 -0
  60. data/examples/14_rusty_circuit/heckler.rb +57 -0
  61. data/examples/14_rusty_circuit/open_mic.rb +121 -0
  62. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +20 -0
  63. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +23 -0
  64. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +20 -0
  65. data/examples/14_rusty_circuit/scout.rb +173 -0
  66. data/examples/14_rusty_circuit/scout_notes.md +89 -0
  67. data/examples/14_rusty_circuit/show.log +234 -0
  68. data/examples/15_memory_network_and_bus/editor_in_chief.rb +24 -0
  69. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +206 -0
  70. data/examples/15_memory_network_and_bus/linux_writer.rb +80 -0
  71. data/examples/15_memory_network_and_bus/os_editor.rb +46 -0
  72. data/examples/15_memory_network_and_bus/os_writer.rb +46 -0
  73. data/examples/15_memory_network_and_bus/output/combined_article.md +13 -0
  74. data/examples/15_memory_network_and_bus/output/final_article.md +15 -0
  75. data/examples/15_memory_network_and_bus/output/linux_draft.md +5 -0
  76. data/examples/15_memory_network_and_bus/output/mac_draft.md +7 -0
  77. data/examples/15_memory_network_and_bus/output/memory.json +13 -0
  78. data/examples/15_memory_network_and_bus/output/revision_1.md +19 -0
  79. data/examples/15_memory_network_and_bus/output/revision_2.md +15 -0
  80. data/examples/15_memory_network_and_bus/output/windows_draft.md +7 -0
  81. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +13 -0
  82. data/examples/15_memory_network_and_bus/prompts/os_chief.md +13 -0
  83. data/examples/15_memory_network_and_bus/prompts/os_editor.md +13 -0
  84. data/examples/README.md +197 -0
  85. data/examples/prompts/{assistant/system.txt.erb → assistant.md} +3 -0
  86. data/examples/prompts/{billing/system.txt.erb → billing.md} +3 -0
  87. data/examples/prompts/{classifier/system.txt.erb → classifier.md} +3 -0
  88. data/examples/prompts/comedian.md +6 -0
  89. data/examples/prompts/comedy_critic.md +10 -0
  90. data/examples/prompts/configurable.md +9 -0
  91. data/examples/prompts/dispatcher.md +12 -0
  92. data/examples/prompts/{entity_extractor/system.txt.erb → entity_extractor.md} +3 -0
  93. data/examples/prompts/{escalation/system.txt.erb → escalation.md} +7 -0
  94. data/examples/prompts/frontmatter_mcp_test.md +9 -0
  95. data/examples/prompts/frontmatter_named_test.md +5 -0
  96. data/examples/prompts/frontmatter_tools_test.md +6 -0
  97. data/examples/prompts/{general/system.txt.erb → general.md} +3 -0
  98. data/examples/prompts/{github_assistant/system.txt.erb → github_assistant.md} +8 -0
  99. data/examples/prompts/{helper/system.txt.erb → helper.md} +3 -0
  100. data/examples/prompts/{keyword_extractor/system.txt.erb → keyword_extractor.md} +3 -0
  101. data/examples/prompts/llm_config_demo.md +20 -0
  102. data/examples/prompts/{order_support/system.txt.erb → order_support.md} +8 -0
  103. data/examples/prompts/os_advocate.md +13 -0
  104. data/examples/prompts/os_chief.md +13 -0
  105. data/examples/prompts/os_editor.md +13 -0
  106. data/examples/prompts/{product_support/system.txt.erb → product_support.md} +7 -0
  107. data/examples/prompts/{sentiment_analyzer/system.txt.erb → sentiment_analyzer.md} +3 -0
  108. data/examples/prompts/{synthesizer/system.txt.erb → synthesizer.md} +3 -0
  109. data/examples/prompts/{technical/system.txt.erb → technical.md} +3 -0
  110. data/examples/prompts/{triage/system.txt.erb → triage.md} +6 -0
  111. data/lib/generators/robot_lab/templates/initializer.rb.tt +1 -1
  112. data/lib/robot_lab/adapters/openai.rb +2 -1
  113. data/lib/robot_lab/ask_user.rb +75 -0
  114. data/lib/robot_lab/config/defaults.yml +121 -0
  115. data/lib/robot_lab/config.rb +183 -0
  116. data/lib/robot_lab/error.rb +6 -0
  117. data/lib/robot_lab/mcp/client.rb +1 -1
  118. data/lib/robot_lab/memory.rb +2 -2
  119. data/lib/robot_lab/robot.rb +523 -249
  120. data/lib/robot_lab/robot_message.rb +44 -0
  121. data/lib/robot_lab/robot_result.rb +1 -0
  122. data/lib/robot_lab/robotic_model.rb +1 -1
  123. data/lib/robot_lab/streaming/context.rb +1 -1
  124. data/lib/robot_lab/tool.rb +108 -172
  125. data/lib/robot_lab/tool_config.rb +1 -1
  126. data/lib/robot_lab/tool_manifest.rb +2 -18
  127. data/lib/robot_lab/version.rb +1 -1
  128. data/lib/robot_lab.rb +66 -55
  129. metadata +107 -116
  130. data/examples/prompts/assistant/user.txt.erb +0 -1
  131. data/examples/prompts/billing/user.txt.erb +0 -1
  132. data/examples/prompts/classifier/user.txt.erb +0 -1
  133. data/examples/prompts/entity_extractor/user.txt.erb +0 -3
  134. data/examples/prompts/escalation/user.txt.erb +0 -34
  135. data/examples/prompts/general/user.txt.erb +0 -1
  136. data/examples/prompts/github_assistant/user.txt.erb +0 -1
  137. data/examples/prompts/helper/user.txt.erb +0 -1
  138. data/examples/prompts/keyword_extractor/user.txt.erb +0 -3
  139. data/examples/prompts/order_support/user.txt.erb +0 -22
  140. data/examples/prompts/product_support/user.txt.erb +0 -32
  141. data/examples/prompts/sentiment_analyzer/user.txt.erb +0 -3
  142. data/examples/prompts/synthesizer/user.txt.erb +0 -15
  143. data/examples/prompts/technical/user.txt.erb +0 -1
  144. data/examples/prompts/triage/user.txt.erb +0 -17
  145. data/lib/robot_lab/configuration.rb +0 -143
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a73bb0d9c45aa5f2f904e744774ecb3396027fa17a7c099a1ede7ac57598929
4
- data.tar.gz: 8ff58ce299752cb0ff12365637070d71f35b011613a62b01a62d2fe43523a87c
3
+ metadata.gz: 8d3bfa06c07301b03b01359a15a945374b135f2055c77396ad456a916917a742
4
+ data.tar.gz: 0b9e81d50841bf9a56d4efcc8f4175f34aa0e17702c89cc4bd5d4750631c0f68
5
5
  SHA512:
6
- metadata.gz: afa598bbaea859d6f70f6bef21c936fc4f6260cf15c8af786dbaee5ce4d71c4d24e9f4e63a243e9a3321d2659f04cd6fab86f19568852b3cbd659eb9f4b339a1
7
- data.tar.gz: 77ea74d0251124766263e3baf64f80889a56ecffa248a1ac612cd0ea1ea495ee090a3830a575090fb5f13dfac55d3c10a0097c182f57cc5a47c3167b5cb4e29f
6
+ metadata.gz: 15649fd320dd84530d49884900d99c2cdc98ddd5841948b052ed6b3a6f3d0fa9cec3e80636883f6d2bcd5b5a8ffe6b61ad681dc907faf5e881f48ac05ebef0fd
7
+ data.tar.gz: cdd136aad382247babb5639cf3a2b0b35db5086b33661823a6e00d8099f2a18a57325c3ed57f3474d23ee8f317f65c1f0b70911d349ad04f111d80cac03dd8c9
@@ -1,4 +1,4 @@
1
- name: Deploy HTM Documentation to GitHub Pages
1
+ name: Deploy Documentation to GitHub Pages
2
2
  on:
3
3
  push:
4
4
  branches:
@@ -41,12 +41,12 @@ jobs:
41
41
  git config --local user.email "action@github.com"
42
42
  git config --local user.name "GitHub Action"
43
43
 
44
+ - name: Build MkDocs site
45
+ run: mkdocs build
46
+
44
47
  - name: Deploy to GitHub Pages
45
- run: |
46
- if [ "${{ github.ref }}" = "refs/heads/main" ]; then
47
- echo "Deploying from main branch"
48
- mkdocs gh-deploy --force --clean
49
- else
50
- echo "Deploying from develop branch"
51
- mkdocs gh-deploy --force --clean
52
- fi
48
+ uses: peaceiris/actions-gh-pages@v4
49
+ with:
50
+ github_token: ${{ secrets.GITHUB_TOKEN }}
51
+ publish_dir: ./site
52
+ keep_files: true
data/.irbrc ADDED
@@ -0,0 +1,6 @@
1
+ begin
2
+ load File.expand_path('lib/robot_lab.rb', __dir__)
3
+ rescue LoadError => e
4
+ $stderr.puts "[robot_lab] #{e.message}"
5
+ $stderr.puts "[robot_lab] Run `bundle exec irb` to load RobotLab"
6
+ end
data/CHANGELOG.md CHANGED
@@ -11,6 +11,96 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [0.0.4] - 2026-02-16 [unreleased]
15
+
16
+ ### Added
17
+
18
+ - **`AskUser` tool** for human-in-the-loop interactions
19
+ - Supports open-ended text, multiple choice, and default values
20
+ - IO sourced from `robot.input`/`robot.output` (defaults to `$stdin`/`$stdout`)
21
+ - Full test suite (`test/robot_lab/ask_user_test.rb`)
22
+ - **`Robot#input` / `Robot#output` accessors** for configurable IO streams
23
+ - **`reply` alias** for `RobotResult#last_text_content` — shorter, more natural API
24
+ - **`.irbrc`** for loading RobotLab in project-level IRB sessions
25
+ - **`wait_until` test helper** replacing flaky `sleep`-based assertions in async tests
26
+ - Documentation for AskUser tool across API reference, guides, and examples
27
+
28
+ ### Changed
29
+
30
+ - Bumped version to 0.0.4
31
+ - **Made Rails dependencies optional** — removed `railties`, `activerecord`, `state_machines`, `state_machines-activemodel`, `state_machines-activerecord` from gemspec hard dependencies; moved to Gemfile `:test` group
32
+ - Replaced `require 'active_support'` with targeted `require 'active_support/core_ext/module/delegation'` — only loads what ruby_llm actually needs
33
+ - Added `activesupport >= 7.0` as explicit gemspec dependency with comment explaining it's required by ruby_llm (undeclared upstream)
34
+ - **Tool JSON schema keys are now symbolized** via `deep_symbolize_keys` in `Tool#to_json_schema`
35
+ - Updated all examples to use `reply` alias instead of `last_text_content`
36
+ - Replaced `sleep`-based test assertions with `wait_until` helper in memory, waiter, and robot tests
37
+ - Disabled branch coverage in SimpleCov except in CI
38
+
39
+ ### Fixed
40
+
41
+ - Gem install conflict (`activesupport` version mismatch) when running outside Bundler
42
+ - IRB loading issue where `require_relative` was a no-op due to partial load in `$LOADED_FEATURES` — switched to `load`
43
+ - Robot tests for `send_message` now register a message handler on the receiver to avoid TypedBus warnings
44
+
45
+ ## [0.0.3] - 2026-02-15 [unreleased]
46
+
47
+ ### Added
48
+
49
+ - **Self-contained templates** with extended YAML front matter support
50
+ - `robot_name` — override robot name from template
51
+ - `description` — set robot description from template
52
+ - `tools` — declare tool class names (resolved via `Object.const_get` at build time)
53
+ - `mcp` — declare MCP server configurations
54
+ - Constructor-provided values always take precedence over front matter
55
+ - **Editorial pipeline example** (`15_memory_network_and_bus/`) demonstrating multi-stage workflow with network, memory, and bus coordination
56
+ - OS-specific writer robots, editor, and editor-in-chief roles
57
+ - New prompt templates: `os_advocate`, `os_editor`, `os_chief`
58
+ - Rakefile support for running subdirectory-based examples with `SUBDIR_ENTRY_POINTS` mapping
59
+
60
+ ### Changed
61
+
62
+ - Bumped version to 0.0.3
63
+ - Refactored Comic and Scout classes to use `attr_accessor` instead of `instance_variable_set`/`instance_variable_get`
64
+ - Extensive documentation updates across README, guides, API reference, and examples for front matter extras
65
+
66
+ ## [0.0.2] - 2026-02-15 (unreleased)
67
+
68
+ ### Added
69
+
70
+ - **TypedBus message bus** for robot-to-robot communication
71
+ - `RobotMessage` immutable data class (`Data.define`) with `id`, `from`, `content`, `in_reply_to`
72
+ - Optional `bus:` parameter on Robot constructor — purely additive
73
+ - `on_message` handler with auto-ACK (1-arg block) and manual ACK/NACK (2-arg block)
74
+ - `publish_to_bus` with Async-aware fiber wrapping
75
+ - Typed channels accepting only `RobotMessage` objects
76
+ - **Dynamic robot spawning** via `Robot#spawn` method for creating child robots at runtime
77
+ - **`with_bus` configuration method** for connecting robots to a message bus after creation
78
+ - **Comic robot class** with dynamic comedy tools (`reinvent_style`, `adjust_energy`, `get_coaching`)
79
+ - New examples:
80
+ - `12_message_bus.rb` — two-robot joke critique workflow
81
+ - `13_spawn.rb` — dynamic robot spawning
82
+ - `14_rusty_circuit/` — multi-robot comedy open mic with bus-based coordination
83
+ - New prompt templates: `comedian`, `comedy_critic`, `dispatcher`, `open_mic_comic`, `open_mic_heckler`, `open_mic_scout`, `configurable`, `llm_config_demo`
84
+ - Rake tasks for building documentation sites
85
+ - GitHub Actions workflow for YARD documentation deployment
86
+
87
+ ### Changed
88
+
89
+ - Bumped version to 0.0.2
90
+ - Replaced `ruby_llm-template` dependency with `prompt_manager` (~> 1.0)
91
+ - Updated `ruby_llm` dependency to ~> 1.12
92
+ - Added `typed_bus` as a core dependency
93
+ - Added `myway_config` (~> 0.1) dependency
94
+ - Added `amazing_print` and `hashdiff` as development dependencies
95
+ - Migrated all prompt templates from directory-based format (`system.txt.erb` / `user.txt.erb`) to single `.md` files with YAML front matter
96
+ - Refactored `Robot` class for simplified configuration
97
+ - Refactored `Config` class
98
+ - Extensive documentation updates across all guide, architecture, and API reference pages
99
+
100
+ ### Fixed
101
+
102
+ - GitHub Actions platform limitation (`arm64-darwin` only in lockfile)
103
+
14
104
  ## [0.0.1] - 2026-01-16
15
105
 
16
106
  - refactored the network concept
data/README.md CHANGED
@@ -18,8 +18,10 @@ RobotLab enables you to build sophisticated AI applications using multiple speci
18
18
  - <strong>Network Orchestration</strong> - Connect robots with flexible routing<br>
19
19
  - <strong>Extensible Tools</strong> - Give robots custom capabilities<br>
20
20
  - <strong>MCP Integration</strong> - Connect to external tool servers<br>
21
- - <strong>Shared Memory</strong> - Hierarchical memory with namespaced scopes<br>
21
+ - <strong>Shared Memory</strong> - Reactive key-value store with subscriptions<br>
22
22
  - <strong>Conversation History</strong> - Persist and restore threads<br>
23
+ - <strong>Message Bus</strong> - Bidirectional robot communication via TypedBus<br>
24
+ - <strong>Dynamic Spawning</strong> - Robots create new robots at runtime<br>
23
25
  - <strong>Streaming</strong> - Real-time event streaming<br>
24
26
  - <strong>Rails Integration</strong> - Generators and ActiveRecord support
25
27
  </td>
@@ -52,11 +54,6 @@ The simplest way to create a robot is with an inline `system_prompt`. This appro
52
54
  ```ruby
53
55
  require "robot_lab"
54
56
 
55
- # Configure RobotLab
56
- RobotLab.configure do |config|
57
- config.default_model = "claude-sonnet-4"
58
- end
59
-
60
57
  # Create a robot with an inline system prompt
61
58
  robot = RobotLab.build(
62
59
  name: "assistant",
@@ -64,66 +61,119 @@ robot = RobotLab.build(
64
61
  )
65
62
 
66
63
  # Run the robot
67
- result = robot.run(message: "What is the capital of France?")
64
+ result = robot.run("What is the capital of France?")
68
65
 
69
- puts result.output.first.content
66
+ puts result.last_text_content
70
67
  # => "The capital of France is Paris."
71
68
  ```
72
69
 
73
- ### Using Templates
70
+ ### Configuration
74
71
 
75
- For production applications, RobotLab supports a powerful template system built on ERB. Templates allow you to:
72
+ RobotLab uses [MywayConfig](https://github.com/MadBomber/myway_config) for layered configuration. There is no `configure` block. Configuration is loaded automatically from multiple sources in priority order:
76
73
 
77
- - **Compose prompts** from reusable components
78
- - **Inject dynamic context** at build-time and run-time
79
- - **Version control** your prompts alongside your code
80
- - **Share prompts** across multiple robots
74
+ 1. Bundled defaults (`lib/robot_lab/config/defaults.yml`)
75
+ 2. Environment-specific overrides (development, test, production)
76
+ 3. XDG user config (`~/.config/robot_lab/config.yml`)
77
+ 4. Project config (`./config/robot_lab.yml`)
78
+ 5. Environment variables (`ROBOT_LAB_*` prefix)
81
79
 
82
- Configure the template directory:
80
+ ```bash
81
+ # Set API keys via environment variables (double underscore for nesting)
82
+ export ROBOT_LAB_RUBY_LLM__ANTHROPIC_API_KEY=sk-ant-...
83
+ export ROBOT_LAB_RUBY_LLM__OPENAI_API_KEY=sk-...
84
+ export ROBOT_LAB_RUBY_LLM__MODEL=claude-sonnet-4
85
+ ```
83
86
 
84
87
  ```ruby
85
- RobotLab.configure do |config|
86
- config.template_path = "app/prompts"
87
- end
88
+ # Access configuration values
89
+ RobotLab.config.ruby_llm.model #=> "claude-sonnet-4"
90
+ RobotLab.config.ruby_llm.request_timeout #=> 120
91
+ RobotLab.config.streaming_enabled #=> true
92
+ ```
93
+
94
+ Or create a project config file at `./config/robot_lab.yml`:
95
+
96
+ ```yaml
97
+ ruby_llm:
98
+ model: claude-sonnet-4
99
+ anthropic_api_key: sk-ant-...
100
+ request_timeout: 180
88
101
  ```
89
102
 
90
- Each template is a **directory** containing ERB files for different message roles:
103
+ ### Using Templates
104
+
105
+ For production applications, RobotLab supports a template system built on [PromptManager](https://github.com/MadBomber/prompt_manager). Templates allow you to:
106
+
107
+ - **Compose prompts** from reusable Markdown files
108
+ - **Inject dynamic context** at build-time
109
+ - **Version control** your prompts alongside your code
110
+ - **Share prompts** across multiple robots
111
+
112
+ Each template is a `.md` file with YAML front matter for metadata and parameters:
91
113
 
92
114
  ```
93
- app/prompts/
94
- assistant/
95
- ├── system.txt.erb # System message (required)
96
- ├── user.txt.erb # User prompt template (optional)
97
- ├── assistant.txt.erb # Pre-filled assistant response (optional)
98
- └── schema.rb # Structured output schema (optional)
115
+ prompts/
116
+ assistant.md
117
+ classifier.md
118
+ billing.md
99
119
  ```
100
120
 
101
- Create the system message at `app/prompts/assistant/system.txt.erb`:
121
+ Create a template at `prompts/assistant.md`:
102
122
 
103
- ```erb
123
+ ```markdown
124
+ ---
125
+ description: A helpful assistant
126
+ parameters:
127
+ company_name: null
128
+ tone: friendly
129
+ ---
104
130
  You are a helpful assistant for <%= company_name %>.
105
131
 
132
+ Your communication style should be <%= tone %>.
133
+
106
134
  Your responsibilities:
107
135
  - Answer questions accurately and concisely
108
136
  - Be friendly and professional
109
137
  - Admit when you don't know something
110
-
111
- <% if guidelines %>
112
- Additional guidelines:
113
- <%= guidelines %>
114
- <% end %>
115
138
  ```
116
139
 
117
- Reference the template directory using a Symbol:
140
+ Reference the template by symbol:
118
141
 
119
142
  ```ruby
120
143
  robot = RobotLab.build(
121
144
  name: "assistant",
122
145
  template: :assistant,
123
- context: { company_name: "Acme Corp", guidelines: nil }
146
+ context: { company_name: "Acme Corp", tone: "professional" }
124
147
  )
125
148
  ```
126
149
 
150
+ ### Self-Contained Templates
151
+
152
+ Templates can declare tools, MCP servers, name, and description in front matter, making the `.md` file a complete robot definition:
153
+
154
+ ```markdown
155
+ ---
156
+ description: GitHub assistant with MCP tool access
157
+ robot_name: github_bot
158
+ tools:
159
+ - CodeSearchTool
160
+ mcp:
161
+ - name: github
162
+ transport: stdio
163
+ command: npx
164
+ args: ["-y", "@modelcontextprotocol/server-github"]
165
+ model: claude-sonnet-4
166
+ ---
167
+ You are a GitHub assistant. Use available tools to help with repository tasks.
168
+ ```
169
+
170
+ ```ruby
171
+ # Template provides everything — minimal constructor call
172
+ robot = RobotLab.build(template: :github_assistant)
173
+ ```
174
+
175
+ Front matter supports: `description`, `robot_name`, `tools`, `mcp`, `parameters`, and LLM config keys (`model`, `temperature`, `top_p`, `top_k`, `max_tokens`, `presence_penalty`, `frequency_penalty`, `stop`). Constructor-provided values always take precedence over front matter.
176
+
127
177
  ### Combining Templates with System Prompts
128
178
 
129
179
  The `system_prompt` parameter can also be used alongside a template. When both are provided, the template renders first and the `system_prompt` is appended. This is particularly useful during development and testing when you want to add temporary instructions or context to an existing template:
@@ -132,11 +182,25 @@ The `system_prompt` parameter can also be used alongside a template. When both a
132
182
  robot = RobotLab.build(
133
183
  name: "assistant",
134
184
  template: :assistant,
135
- context: { company_name: "Acme Corp" },
185
+ context: { company_name: "Acme Corp", tone: "friendly" },
136
186
  system_prompt: "DEBUG MODE: Log all tool calls. Today's date is #{Date.today}."
137
187
  )
138
188
  ```
139
189
 
190
+ ### Chaining Configuration
191
+
192
+ Robots support method chaining to adjust configuration after creation:
193
+
194
+ ```ruby
195
+ robot = RobotLab.build(name: "writer", system_prompt: "You are a creative writer.")
196
+
197
+ result = robot
198
+ .with_temperature(0.9)
199
+ .with_model("claude-sonnet-4")
200
+ .with_max_tokens(2000)
201
+ .run("Write a haiku about Ruby programming")
202
+ ```
203
+
140
204
  ## Creating a Robot with Tools
141
205
 
142
206
  ```ruby
@@ -163,14 +227,14 @@ class Magic8Ball < RubyLLM::Tool
163
227
  end
164
228
  end
165
229
 
166
- # Create robot with tools
230
+ # Create robot with tools via local_tools: parameter
167
231
  robot = RobotLab.build(
168
232
  name: "oracle",
169
233
  system_prompt: "You are a mystical oracle. Use the Magic 8-Ball to answer questions about the future.",
170
- tools: [Magic8Ball]
234
+ local_tools: [Magic8Ball]
171
235
  )
172
236
 
173
- result = robot.run(message: "Should I start learning Rust?")
237
+ result = robot.run("Should I start learning Rust?")
174
238
  ```
175
239
 
176
240
  ## Orchestrating Multiple Robots
@@ -181,7 +245,10 @@ Networks use [SimpleFlow](https://github.com/MadBomber/simple_flow) pipelines wi
181
245
  # Custom classifier that activates the appropriate specialist
182
246
  class ClassifierRobot < RobotLab::Robot
183
247
  def call(result)
184
- robot_result = run(**extract_run_context(result))
248
+ context = extract_run_context(result)
249
+ message = context.delete(:message)
250
+
251
+ robot_result = run(message, **context)
185
252
 
186
253
  new_result = result
187
254
  .with_context(@name.to_sym, robot_result)
@@ -228,15 +295,15 @@ Both robots and networks have inherent memory that persists across runs:
228
295
  # Standalone robot with inherent memory
229
296
  robot = RobotLab.build(name: "assistant", system_prompt: "You are helpful.")
230
297
 
231
- robot.run(message: "My name is Alice")
232
- robot.run(message: "What's my name?") # Memory persists automatically
298
+ robot.run("My name is Alice")
299
+ robot.run("What's my name?") # Memory persists automatically
233
300
 
234
301
  # Access robot's memory
235
302
  robot.memory[:user_id] = 123
236
303
  robot.memory.data[:category] = "billing"
237
304
 
238
305
  # Runtime memory injection
239
- robot.run(message: "Help me", memory: { session_id: "abc123", tier: "premium" })
306
+ robot.run("Help me", memory: { session_id: "abc123", tier: "premium" })
240
307
 
241
308
  # Reset memory when needed
242
309
  robot.reset_memory
@@ -285,19 +352,109 @@ filesystem_server = {
285
352
  robot = RobotLab.build(
286
353
  name: "developer",
287
354
  template: :developer,
288
- mcp_servers: [filesystem_server]
355
+ mcp: [filesystem_server]
289
356
  )
290
357
 
291
358
  # Robot can now use filesystem tools
292
- result = robot.run(message: "List the files in the current directory")
359
+ result = robot.run("List the files in the current directory")
293
360
  ```
294
361
 
362
+ ## Message Bus
363
+
364
+ Robots can communicate bidirectionally via an optional message bus, independent of the Network pipeline. This enables negotiation loops, convergence patterns, and cyclic workflows.
365
+
366
+ Connect robots to a bus at construction time with `bus:`, or after creation with `with_bus`:
367
+
368
+ ```ruby
369
+ require "robot_lab"
370
+
371
+ bus = TypedBus::MessageBus.new
372
+
373
+ class Comedian < RobotLab::Robot
374
+ def initialize(bus:)
375
+ super(name: "bob", template: :comedian, bus: bus)
376
+ on_message do |message|
377
+ joke = run(message.content.to_s).last_text_content.strip
378
+ reply(message, joke)
379
+ end
380
+ end
381
+ end
382
+
383
+ class ComedyCritic < RobotLab::Robot
384
+ def initialize(bus:)
385
+ super(name: "alice", template: :comedy_critic, bus: bus)
386
+ @accepted = false
387
+ on_message do |message|
388
+ verdict = run("Evaluate this joke:\n\n#{message.content}").last_text_content.strip
389
+ @accepted = verdict.start_with?("FUNNY")
390
+ send_message(to: :bob, content: "Try again.") unless @accepted
391
+ end
392
+ end
393
+ attr_reader :accepted
394
+ end
395
+
396
+ bob = Comedian.new(bus: bus)
397
+ alice = ComedyCritic.new(bus: bus)
398
+ alice.send_message(to: :bob, content: "Tell me a funny robot joke.")
399
+ ```
400
+
401
+ Key features:
402
+
403
+ - **Typed channels** — only `RobotMessage` objects are accepted (type enforcement via `typed_bus`)
404
+ - **Auto-ACK** — `on_message { |message| }` auto-acknowledges; use `|delivery, message|` for manual ACK/NACK
405
+ - **Reply correlation** — `reply(message, content)` tracks conversation threads via `in_reply_to`
406
+ - **Outbox tracking** — sent messages tracked in `robot.outbox` with status and replies
407
+ - **Independent of Network** — bus communication works without a Network pipeline
408
+
409
+ ## Dynamic Robot Spawning
410
+
411
+ Robots can create new robots at runtime using `spawn`. The bus is created lazily — no upfront wiring required:
412
+
413
+ ```ruby
414
+ require "robot_lab"
415
+
416
+ class Dispatcher < RobotLab::Robot
417
+ attr_reader :spawned
418
+
419
+ def initialize(bus: nil)
420
+ super(name: "dispatcher", system_prompt: "Decide which specialist to create.", bus: bus)
421
+ @spawned = {}
422
+
423
+ on_message do |message|
424
+ puts "Got reply from #{message.from}: #{message.content.to_s.lines.first&.strip}"
425
+ end
426
+ end
427
+
428
+ def dispatch(question)
429
+ # Spawn a specialist (reuse if already spawned)
430
+ specialist = @spawned["helper"] ||= spawn(
431
+ name: "helper",
432
+ system_prompt: "You answer questions concisely."
433
+ )
434
+
435
+ # Have the specialist work and reply
436
+ answer = specialist.run(question).last_text_content.strip
437
+ specialist.send_message(to: :dispatcher, content: answer)
438
+ end
439
+ end
440
+
441
+ dispatcher = Dispatcher.new
442
+ dispatcher.dispatch("What is the capital of France?")
443
+ ```
444
+
445
+ Key features:
446
+
447
+ - **`spawn`** — creates a child robot on the same bus; creates a bus lazily if none exists
448
+ - **`with_bus`** — connect a robot to a bus after creation (`bot.with_bus(existing_bus)`)
449
+ - **Fan-out** — multiple robots with the same name all receive messages sent to that name
450
+ - **No setup required** — bus and channels are created automatically on first use
451
+
295
452
  ## Streaming
296
453
 
297
- Subscribe to real-time events during execution:
454
+ Pass a block to `run` to receive real-time events during execution:
298
455
 
299
456
  ```ruby
300
- result = robot.run(message: "Tell me a story") do |event|
457
+ result = robot.run("Tell me a story") do |event|
301
458
  case event[:event]
302
459
  when "text.delta"
303
460
  print event[:data][:delta]
data/Rakefile CHANGED
@@ -45,23 +45,92 @@ task :rubocop_fix do
45
45
  end
46
46
 
47
47
  namespace :examples do
48
+ # Map of subdirectory-based demos to their entry point scripts
49
+ SUBDIR_ENTRY_POINTS = {
50
+ "14_rusty_circuit" => "open_mic.rb",
51
+ "15_memory_network_and_bus" => "editorial_pipeline.rb"
52
+ }.freeze
53
+
48
54
  desc "Run all examples"
49
55
  task :all do
56
+ # Single-file examples
50
57
  Dir.glob("examples/*.rb").sort.each do |example|
51
58
  puts "\n#{'=' * 60}"
52
59
  puts "Running: #{example}"
53
60
  puts '=' * 60
54
61
  ruby example
55
62
  end
63
+
64
+ # Subdirectory-based demos
65
+ SUBDIR_ENTRY_POINTS.each do |dir, entry|
66
+ path = "examples/#{dir}/#{entry}"
67
+ next unless File.exist?(path)
68
+
69
+ puts "\n#{'=' * 60}"
70
+ puts "Running: #{path}"
71
+ puts '=' * 60
72
+ ruby path
73
+ end
56
74
  end
57
75
 
58
76
  desc "Run a specific example by number (e.g., rake examples:run[1])"
59
77
  task :run, [:num] do |_t, args|
60
- example = Dir.glob("examples/#{args[:num].rjust(2, '0')}_*.rb").first
78
+ padded = args[:num].rjust(2, '0')
79
+
80
+ # Try single-file example first
81
+ example = Dir.glob("examples/#{padded}_*.rb").first
61
82
  if example
62
83
  ruby example
84
+ next
85
+ end
86
+
87
+ # Try subdirectory-based demo
88
+ dir = Dir.glob("examples/#{padded}_*/").first
89
+ if dir
90
+ dir_name = File.basename(dir)
91
+ entry = SUBDIR_ENTRY_POINTS[dir_name]
92
+ if entry && File.exist?(File.join(dir, entry))
93
+ ruby File.join(dir, entry)
94
+ else
95
+ puts "Example #{args[:num]} directory found (#{dir_name}) but no entry point configured"
96
+ end
63
97
  else
64
98
  puts "Example #{args[:num]} not found"
65
99
  end
66
100
  end
67
101
  end
102
+
103
+ namespace :docs do
104
+ desc "Build all documentation (YARD and MkDocs)"
105
+ task build: %i[yard mkdocs]
106
+
107
+ desc "Clean generated documentation"
108
+ task :clean do
109
+ rm_rf "doc"
110
+ rm_rf "site"
111
+ end
112
+
113
+ desc "Build YARD API documentation"
114
+ task :yard do
115
+ sh "yard doc"
116
+ end
117
+
118
+ namespace :yard do
119
+ desc "Serve YARD documentation locally"
120
+ task :serve do
121
+ sh "yard server --reload"
122
+ end
123
+ end
124
+
125
+ desc "Build MkDocs documentation"
126
+ task :mkdocs do
127
+ sh "mkdocs build"
128
+ end
129
+
130
+ namespace :mkdocs do
131
+ desc "Serve MkDocs documentation locally"
132
+ task :serve do
133
+ sh "mkdocs serve"
134
+ end
135
+ end
136
+ end
@@ -43,10 +43,20 @@ classDiagram
43
43
  +scoped(namespace)
44
44
  }
45
45
 
46
+ class RobotMessage {
47
+ +id: Integer
48
+ +from: String
49
+ +content: String
50
+ +in_reply_to: String
51
+ +key()
52
+ +reply?()
53
+ }
54
+
46
55
  Network --> Robot : contains
47
56
  Robot --> Tool : has
48
57
  State --> Memory : has
49
58
  Network --> State : uses
59
+ Robot ..> RobotMessage : sends/receives
50
60
  ```
51
61
 
52
62
  ## Classes
@@ -57,7 +67,9 @@ classDiagram
57
67
  | [Network](network.md) | Container for robots with routing and orchestration |
58
68
  | [State](state.md) | Conversation state with data, results, and memory |
59
69
  | [Tool](tool.md) | Callable function with parameters and handler |
70
+ | [AskUser](tool.md#built-in-askuser) | Built-in tool for terminal-based user interaction |
60
71
  | [Memory](memory.md) | Namespaced key-value store for sharing data |
72
+ | RobotMessage | Typed envelope for bus-based inter-robot communication |
61
73
 
62
74
  ## Quick Examples
63
75