robot_lab 0.0.1 → 0.0.6

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 (187) 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 +140 -0
  5. data/README.md +263 -48
  6. data/Rakefile +71 -1
  7. data/docs/api/core/index.md +53 -46
  8. data/docs/api/core/memory.md +200 -154
  9. data/docs/api/core/network.md +13 -3
  10. data/docs/api/core/robot.md +490 -130
  11. data/docs/api/core/state.md +55 -73
  12. data/docs/api/core/tool.md +205 -209
  13. data/docs/api/index.md +7 -28
  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/messages/index.md +35 -20
  19. data/docs/api/messages/text-message.md +67 -21
  20. data/docs/api/messages/tool-call-message.md +80 -41
  21. data/docs/api/messages/tool-result-message.md +119 -50
  22. data/docs/api/messages/user-message.md +48 -24
  23. data/docs/api/streaming/context.md +157 -74
  24. data/docs/api/streaming/events.md +114 -166
  25. data/docs/api/streaming/index.md +74 -72
  26. data/docs/architecture/core-concepts.md +360 -116
  27. data/docs/architecture/index.md +97 -59
  28. data/docs/architecture/message-flow.md +138 -129
  29. data/docs/architecture/network-orchestration.md +197 -50
  30. data/docs/architecture/robot-execution.md +199 -146
  31. data/docs/architecture/state-management.md +255 -187
  32. data/docs/concepts.md +311 -49
  33. data/docs/examples/basic-chat.md +89 -77
  34. data/docs/examples/index.md +222 -47
  35. data/docs/examples/mcp-server.md +207 -203
  36. data/docs/examples/multi-robot-network.md +129 -35
  37. data/docs/examples/rails-application.md +159 -160
  38. data/docs/examples/tool-usage.md +295 -204
  39. data/docs/getting-started/configuration.md +347 -154
  40. data/docs/getting-started/index.md +1 -1
  41. data/docs/getting-started/installation.md +22 -13
  42. data/docs/getting-started/quick-start.md +166 -121
  43. data/docs/guides/building-robots.md +418 -212
  44. data/docs/guides/creating-networks.md +143 -24
  45. data/docs/guides/index.md +0 -5
  46. data/docs/guides/mcp-integration.md +152 -113
  47. data/docs/guides/memory.md +220 -164
  48. data/docs/guides/rails-integration.md +244 -162
  49. data/docs/guides/streaming.md +137 -187
  50. data/docs/guides/using-tools.md +259 -212
  51. data/docs/index.md +46 -41
  52. data/examples/01_simple_robot.rb +6 -9
  53. data/examples/02_tools.rb +6 -9
  54. data/examples/03_network.rb +19 -17
  55. data/examples/04_mcp.rb +5 -8
  56. data/examples/05_streaming.rb +5 -8
  57. data/examples/06_prompt_templates.rb +42 -37
  58. data/examples/07_network_memory.rb +13 -14
  59. data/examples/08_llm_config.rb +169 -0
  60. data/examples/09_chaining.rb +262 -0
  61. data/examples/10_memory.rb +331 -0
  62. data/examples/11_network_introspection.rb +253 -0
  63. data/examples/12_message_bus.rb +74 -0
  64. data/examples/13_spawn.rb +90 -0
  65. data/examples/14_rusty_circuit/comic.rb +143 -0
  66. data/examples/14_rusty_circuit/display.rb +203 -0
  67. data/examples/14_rusty_circuit/heckler.rb +63 -0
  68. data/examples/14_rusty_circuit/open_mic.rb +123 -0
  69. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +20 -0
  70. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +23 -0
  71. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +20 -0
  72. data/examples/14_rusty_circuit/scout.rb +156 -0
  73. data/examples/14_rusty_circuit/scout_notes.md +89 -0
  74. data/examples/14_rusty_circuit/show.log +234 -0
  75. data/examples/15_memory_network_and_bus/editor_in_chief.rb +24 -0
  76. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +206 -0
  77. data/examples/15_memory_network_and_bus/linux_writer.rb +80 -0
  78. data/examples/15_memory_network_and_bus/os_editor.rb +46 -0
  79. data/examples/15_memory_network_and_bus/os_writer.rb +46 -0
  80. data/examples/15_memory_network_and_bus/output/combined_article.md +13 -0
  81. data/examples/15_memory_network_and_bus/output/final_article.md +15 -0
  82. data/examples/15_memory_network_and_bus/output/linux_draft.md +5 -0
  83. data/examples/15_memory_network_and_bus/output/mac_draft.md +7 -0
  84. data/examples/15_memory_network_and_bus/output/memory.json +13 -0
  85. data/examples/15_memory_network_and_bus/output/revision_1.md +19 -0
  86. data/examples/15_memory_network_and_bus/output/revision_2.md +15 -0
  87. data/examples/15_memory_network_and_bus/output/windows_draft.md +7 -0
  88. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +13 -0
  89. data/examples/15_memory_network_and_bus/prompts/os_chief.md +13 -0
  90. data/examples/15_memory_network_and_bus/prompts/os_editor.md +13 -0
  91. data/examples/16_writers_room/display.rb +158 -0
  92. data/examples/16_writers_room/output/.gitignore +2 -0
  93. data/examples/16_writers_room/output/opus_001.md +263 -0
  94. data/examples/16_writers_room/output/opus_001_notes.log +470 -0
  95. data/examples/16_writers_room/prompts/writer.md +37 -0
  96. data/examples/16_writers_room/room.rb +150 -0
  97. data/examples/16_writers_room/tools.rb +162 -0
  98. data/examples/16_writers_room/writer.rb +121 -0
  99. data/examples/16_writers_room/writers_room.rb +162 -0
  100. data/examples/README.md +197 -0
  101. data/examples/prompts/{assistant/system.txt.erb → assistant.md} +3 -0
  102. data/examples/prompts/{billing/system.txt.erb → billing.md} +3 -0
  103. data/examples/prompts/{classifier/system.txt.erb → classifier.md} +3 -0
  104. data/examples/prompts/comedian.md +6 -0
  105. data/examples/prompts/comedy_critic.md +10 -0
  106. data/examples/prompts/configurable.md +9 -0
  107. data/examples/prompts/dispatcher.md +12 -0
  108. data/examples/prompts/{entity_extractor/system.txt.erb → entity_extractor.md} +3 -0
  109. data/examples/prompts/{escalation/system.txt.erb → escalation.md} +7 -0
  110. data/examples/prompts/frontmatter_mcp_test.md +9 -0
  111. data/examples/prompts/frontmatter_named_test.md +5 -0
  112. data/examples/prompts/frontmatter_tools_test.md +6 -0
  113. data/examples/prompts/{general/system.txt.erb → general.md} +3 -0
  114. data/examples/prompts/{github_assistant/system.txt.erb → github_assistant.md} +8 -0
  115. data/examples/prompts/{helper/system.txt.erb → helper.md} +3 -0
  116. data/examples/prompts/{keyword_extractor/system.txt.erb → keyword_extractor.md} +3 -0
  117. data/examples/prompts/llm_config_demo.md +20 -0
  118. data/examples/prompts/{order_support/system.txt.erb → order_support.md} +8 -0
  119. data/examples/prompts/os_advocate.md +13 -0
  120. data/examples/prompts/os_chief.md +13 -0
  121. data/examples/prompts/os_editor.md +13 -0
  122. data/examples/prompts/{product_support/system.txt.erb → product_support.md} +7 -0
  123. data/examples/prompts/{sentiment_analyzer/system.txt.erb → sentiment_analyzer.md} +3 -0
  124. data/examples/prompts/{synthesizer/system.txt.erb → synthesizer.md} +3 -0
  125. data/examples/prompts/{technical/system.txt.erb → technical.md} +3 -0
  126. data/examples/prompts/{triage/system.txt.erb → triage.md} +6 -0
  127. data/lib/generators/robot_lab/templates/initializer.rb.tt +0 -13
  128. data/lib/robot_lab/ask_user.rb +75 -0
  129. data/lib/robot_lab/config/defaults.yml +121 -0
  130. data/lib/robot_lab/config.rb +183 -0
  131. data/lib/robot_lab/error.rb +6 -0
  132. data/lib/robot_lab/mcp/client.rb +1 -1
  133. data/lib/robot_lab/memory.rb +10 -34
  134. data/lib/robot_lab/network.rb +13 -20
  135. data/lib/robot_lab/robot/bus_messaging.rb +239 -0
  136. data/lib/robot_lab/robot/mcp_management.rb +88 -0
  137. data/lib/robot_lab/robot/template_rendering.rb +130 -0
  138. data/lib/robot_lab/robot.rb +240 -330
  139. data/lib/robot_lab/robot_message.rb +44 -0
  140. data/lib/robot_lab/robot_result.rb +1 -0
  141. data/lib/robot_lab/run_config.rb +184 -0
  142. data/lib/robot_lab/state_proxy.rb +2 -12
  143. data/lib/robot_lab/streaming/context.rb +1 -1
  144. data/lib/robot_lab/task.rb +8 -1
  145. data/lib/robot_lab/tool.rb +108 -172
  146. data/lib/robot_lab/tool_config.rb +1 -1
  147. data/lib/robot_lab/tool_manifest.rb +2 -18
  148. data/lib/robot_lab/utils.rb +39 -0
  149. data/lib/robot_lab/version.rb +1 -1
  150. data/lib/robot_lab.rb +89 -57
  151. data/mkdocs.yml +0 -11
  152. metadata +121 -135
  153. data/docs/api/adapters/anthropic.md +0 -121
  154. data/docs/api/adapters/gemini.md +0 -133
  155. data/docs/api/adapters/index.md +0 -104
  156. data/docs/api/adapters/openai.md +0 -134
  157. data/docs/api/history/active-record-adapter.md +0 -195
  158. data/docs/api/history/config.md +0 -191
  159. data/docs/api/history/index.md +0 -132
  160. data/docs/api/history/thread-manager.md +0 -144
  161. data/docs/guides/history.md +0 -359
  162. data/examples/prompts/assistant/user.txt.erb +0 -1
  163. data/examples/prompts/billing/user.txt.erb +0 -1
  164. data/examples/prompts/classifier/user.txt.erb +0 -1
  165. data/examples/prompts/entity_extractor/user.txt.erb +0 -3
  166. data/examples/prompts/escalation/user.txt.erb +0 -34
  167. data/examples/prompts/general/user.txt.erb +0 -1
  168. data/examples/prompts/github_assistant/user.txt.erb +0 -1
  169. data/examples/prompts/helper/user.txt.erb +0 -1
  170. data/examples/prompts/keyword_extractor/user.txt.erb +0 -3
  171. data/examples/prompts/order_support/user.txt.erb +0 -22
  172. data/examples/prompts/product_support/user.txt.erb +0 -32
  173. data/examples/prompts/sentiment_analyzer/user.txt.erb +0 -3
  174. data/examples/prompts/synthesizer/user.txt.erb +0 -15
  175. data/examples/prompts/technical/user.txt.erb +0 -1
  176. data/examples/prompts/triage/user.txt.erb +0 -17
  177. data/lib/robot_lab/adapters/anthropic.rb +0 -163
  178. data/lib/robot_lab/adapters/base.rb +0 -85
  179. data/lib/robot_lab/adapters/gemini.rb +0 -193
  180. data/lib/robot_lab/adapters/openai.rb +0 -159
  181. data/lib/robot_lab/adapters/registry.rb +0 -81
  182. data/lib/robot_lab/configuration.rb +0 -143
  183. data/lib/robot_lab/errors.rb +0 -70
  184. data/lib/robot_lab/history/active_record_adapter.rb +0 -146
  185. data/lib/robot_lab/history/config.rb +0 -115
  186. data/lib/robot_lab/history/thread_manager.rb +0 -93
  187. data/lib/robot_lab/robotic_model.rb +0 -324
@@ -8,28 +8,30 @@ RobotLab is designed around a few core architectural principles that enable flex
8
8
 
9
9
  Each component has a single, well-defined responsibility:
10
10
 
11
- - **Robot**: Encapsulates LLM interaction logic and personality
12
- - **Network**: Orchestrates robot execution and routing
13
- - **State**: Manages conversation and workflow data
14
- - **Tool**: Provides external capabilities to robots
11
+ - **Robot**: LLM-powered agent (subclass of `RubyLLM::Agent`) with personality, tools, and memory
12
+ - **Network**: Orchestrates robot execution as a DAG pipeline via SimpleFlow
13
+ - **Memory**: Reactive key-value store for robot and network data
14
+ - **Tool**: Provides external capabilities to robots (inherits from `RubyLLM::Tool`)
15
+ - **Task**: Wraps a robot for pipeline execution with per-task configuration
15
16
 
16
17
  ### 2. Composability
17
18
 
18
19
  Components are designed to be mixed and matched:
19
20
 
20
- - Robots can be reused across multiple networks
21
- - Tools can be shared or robot-specific
22
- - Networks can be nested or chained
23
- - State can be persisted and restored
21
+ - Robots can be used standalone or within networks
22
+ - Tools can be shared across robots or scoped per-robot via `local_tools:`
23
+ - Networks define DAG pipelines with sequential, parallel, and optional execution
24
+ - Memory can be standalone (per-robot) or shared (per-network)
25
+ - `with_*` methods return `self` for fluent chaining
24
26
 
25
27
  ### 3. Provider Agnostic
26
28
 
27
- RobotLab abstracts away LLM provider differences:
29
+ RobotLab abstracts away LLM provider differences through RubyLLM:
28
30
 
29
- - Unified message format across providers
31
+ - Unified interface across Anthropic, OpenAI, Gemini, DeepSeek, Mistral, and others
30
32
  - Consistent tool calling interface
31
33
  - Automatic provider detection from model names
32
- - Easy switching between providers
34
+ - Easy switching between providers via configuration
33
35
 
34
36
  ## System Architecture
35
37
 
@@ -41,98 +43,134 @@ graph TB
41
43
 
42
44
  subgraph "RobotLab Core"
43
45
  B[Network]
44
- C[Router]
45
- D[Robot]
46
- E[State]
47
- F[Memory]
46
+ C[Task]
47
+ D[Robot < RubyLLM::Agent]
48
+ E[Memory]
49
+ F[RobotResult]
50
+ end
51
+
52
+ subgraph "Configuration"
53
+ G[Config < MywayConfig::Base]
48
54
  end
49
55
 
50
56
  subgraph "Integration Layer"
51
- G[Adapters]
52
57
  H[MCP Client]
53
- I[Tools]
58
+ I[Tools < RubyLLM::Tool]
59
+ J[Templates / prompt_manager]
60
+ end
61
+
62
+ subgraph "Execution Layer"
63
+ K[SimpleFlow::Pipeline]
64
+ L[RubyLLM Chat]
54
65
  end
55
66
 
56
67
  subgraph "Provider Layer"
57
- J[Anthropic]
58
- K[OpenAI]
59
- L[Gemini]
60
- M[MCP Servers]
68
+ M[Anthropic]
69
+ N[OpenAI]
70
+ O[Gemini]
71
+ P[MCP Servers]
61
72
  end
62
73
 
63
74
  A --> B
75
+ A --> D
64
76
  B --> C
65
- B --> D
77
+ C --> D
78
+ B --> K
66
79
  B --> E
67
- E --> F
68
- D --> G
80
+ D --> E
81
+ D --> L
69
82
  D --> H
70
83
  D --> I
71
- G --> J
72
- G --> K
84
+ D --> J
85
+ D --> F
86
+ G --> D
73
87
  G --> L
74
- H --> M
88
+ L --> M
89
+ L --> N
90
+ L --> O
91
+ H --> P
75
92
  ```
76
93
 
77
94
  ## Core Components
78
95
 
79
96
  | Component | Description | Documentation |
80
97
  |-----------|-------------|---------------|
81
- | **Robot** | LLM-powered agent with personality and tools | [Core Concepts](core-concepts.md) |
82
- | **Network** | Orchestrates multiple robots | [Network Orchestration](network-orchestration.md) |
83
- | **State** | Conversation and workflow data | [State Management](state-management.md) |
84
- | **Router** | Determines robot execution order | [Network Orchestration](network-orchestration.md) |
85
- | **Memory** | Shared key-value store | [State Management](state-management.md) |
86
- | **Adapter** | Provider-specific message conversion | [Message Flow](message-flow.md) |
98
+ | **Robot** | LLM agent (subclass of `RubyLLM::Agent`) with template-based prompts, tools, and memory | [Core Concepts](core-concepts.md) |
99
+ | **Network** | Orchestrates multiple robots as a SimpleFlow pipeline | [Network Orchestration](network-orchestration.md) |
100
+ | **Memory** | Reactive key-value store with pub/sub and blocking reads | [Memory Management](state-management.md) |
101
+ | **Task** | Wraps a robot for pipeline execution with per-task config | [Network Orchestration](network-orchestration.md) |
102
+ | **RobotResult** | Captures LLM output, tool calls, and metadata from a run | [Message Flow](message-flow.md) |
103
+ | **Config** | MywayConfig-based configuration with env var and file support | [Configuration](#configuration) |
104
+
105
+ ## Configuration
106
+
107
+ RobotLab uses MywayConfig (`Config < MywayConfig::Base`) instead of a `configure` block. Configuration is loaded from multiple sources in priority order:
108
+
109
+ 1. **Bundled defaults** (`lib/robot_lab/config/defaults.yml`)
110
+ 2. **Environment overrides** (development, test, production sections)
111
+ 3. **XDG user config** (`~/.config/robot_lab/config.yml`)
112
+ 4. **Project config** (`./config/robot_lab.yml`)
113
+ 5. **Environment variables** (`ROBOT_LAB_*` prefix, double underscore for nesting)
114
+
115
+ ```ruby
116
+ # Access configuration
117
+ RobotLab.config.ruby_llm.model #=> "claude-sonnet-4"
118
+ RobotLab.config.ruby_llm.request_timeout #=> 120
119
+ ```
87
120
 
88
121
  ## Data Flow
89
122
 
90
- 1. **Input**: User message enters via State
91
- 2. **Routing**: Router selects robot(s) to execute
92
- 3. **Execution**: Robot processes message with LLM
93
- 4. **Tools**: Robot may call tools during execution
94
- 5. **Result**: Robot returns RobotResult
95
- 6. **Iteration**: Router checks for next robot
96
- 7. **Output**: Final results returned to application
123
+ 1. **Input**: User calls `robot.run("message")` or `network.run(message: "...")`
124
+ 2. **Memory**: Robot resolves active memory (standalone or network-shared)
125
+ 3. **MCP**: Robot resolves and initializes MCP clients from hierarchical config
126
+ 4. **Tools**: Robot resolves and filters tools from hierarchical config
127
+ 5. **Execution**: Robot delegates to `Agent#ask` which calls `@chat.ask` on RubyLLM
128
+ 6. **Tool Loop**: LLM may invoke tools; RubyLLM handles the tool call/result loop
129
+ 7. **Result**: Robot builds and returns a `RobotResult`
130
+ 8. **Network**: If in a network, result flows to dependent tasks via SimpleFlow
97
131
 
98
132
  ## Key Patterns
99
133
 
100
- ### Builder Pattern
134
+ ### Factory Methods
101
135
 
102
- Robots and networks are constructed using a fluent DSL:
136
+ Robots and networks are created via factory methods on the `RobotLab` module:
103
137
 
104
138
  ```ruby
105
- robot = RobotLab.build do
106
- name "assistant"
107
- model "claude-sonnet-4"
108
- template "You are helpful."
139
+ robot = RobotLab.build(
140
+ name: "assistant",
141
+ template: :assistant,
142
+ context: { tone: "friendly" }
143
+ )
144
+
145
+ network = RobotLab.create_network(name: "pipeline") do
146
+ task :analyst, analyst_robot, depends_on: :none
147
+ task :writer, writer_robot, depends_on: [:analyst]
109
148
  end
110
149
  ```
111
150
 
112
- ### Strategy Pattern
151
+ ### Fluent Chaining
113
152
 
114
- Routers implement custom selection logic:
153
+ `with_*` methods on Robot delegate to the underlying `@chat` and return `self`:
115
154
 
116
155
  ```ruby
117
- router = ->(args) {
118
- # Custom routing strategy
119
- args.call_count.zero? ? :first_robot : nil
120
- }
156
+ robot = RobotLab.build(name: "bot")
157
+ .with_instructions("Be concise.")
158
+ .with_temperature(0.3)
159
+ .with_model("gpt-4o")
121
160
  ```
122
161
 
123
- ### Adapter Pattern
162
+ ### Hierarchical Configuration
124
163
 
125
- Provider adapters normalize LLM interfaces:
164
+ Tools and MCP servers use hierarchical resolution: `runtime > robot build > network > global config`. Values can be `:none`, `:inherit`, or explicit arrays.
126
165
 
127
- ```ruby
128
- adapter = Adapters::Registry.for(:anthropic)
129
- messages = adapter.format_messages(robot_lab_messages)
130
- ```
166
+ ### SimpleFlow Pipeline
167
+
168
+ Networks are thin wrappers around `SimpleFlow::Pipeline`. Each robot is wrapped in a `Task` that implements the `call(result)` interface. Tasks define dependencies (`:none`, `[:task_names]`, or `:optional`) to control execution order.
131
169
 
132
170
  ## Next Steps
133
171
 
134
172
  - [Core Concepts](core-concepts.md) - Deep dive into robots and tools
135
173
  - [Robot Execution](robot-execution.md) - How robots process messages
136
174
  - [Network Orchestration](network-orchestration.md) - Multi-robot workflows
137
- - [State Management](state-management.md) - Managing conversation state
175
+ - [Memory Management](state-management.md) - Managing memory and reactive features
138
176
  - [Message Flow](message-flow.md) - How messages move through the system
@@ -41,9 +41,10 @@ classDiagram
41
41
  }
42
42
 
43
43
  Message <|-- TextMessage
44
- Message <|-- ToolMessage
45
- ToolMessage <|-- ToolCallMessage
46
- ToolMessage <|-- ToolResultMessage
44
+ Message <|-- ToolCallMessage
45
+ Message <|-- ToolResultMessage
46
+ ToolMessage -- ToolCallMessage
47
+ ToolMessage -- ToolResultMessage
47
48
  ```
48
49
 
49
50
  ### TextMessage
@@ -58,14 +59,14 @@ TextMessage.new(
58
59
 
59
60
  TextMessage.new(
60
61
  role: "assistant",
61
- content: "The weather in Paris is sunny and 22°C.",
62
+ content: "The weather in Paris is sunny and 22 degrees C.",
62
63
  stop_reason: "stop"
63
64
  )
64
65
  ```
65
66
 
66
67
  ### ToolMessage
67
68
 
68
- Base class for tool-related messages:
69
+ Represents a tool invocation with its parameters:
69
70
 
70
71
  ```ruby
71
72
  ToolMessage.new(
@@ -77,7 +78,7 @@ ToolMessage.new(
77
78
 
78
79
  ### ToolCallMessage
79
80
 
80
- LLM's request to execute tools:
81
+ LLM's request to execute one or more tools:
81
82
 
82
83
  ```ruby
83
84
  ToolCallMessage.new(
@@ -101,147 +102,159 @@ ToolResultMessage.new(
101
102
  )
102
103
  ```
103
104
 
104
- ## Message Flow Diagram
105
+ ## Message Flow: Standalone Robot
106
+
107
+ The primary execution path is `robot.run("message")`:
105
108
 
106
109
  ```mermaid
107
110
  sequenceDiagram
108
111
  participant User
109
- participant State
110
112
  participant Robot
111
- participant Adapter
113
+ participant Memory
114
+ participant MCP
115
+ participant Tools
116
+ participant Agent
117
+ participant Chat
112
118
  participant LLM
113
119
 
114
- User->>State: UserMessage
115
- State->>State: Format messages
116
- State->>Robot: state.messages
117
- Robot->>Adapter: Convert messages
118
- Adapter->>LLM: Provider-specific format
119
-
120
- LLM-->>Adapter: Response
121
- Adapter-->>Robot: Parse response
122
- Robot->>Robot: Execute tools (if any)
123
- Robot-->>State: RobotResult
124
- State->>State: Append result
125
- State-->>User: Response
126
- ```
120
+ User->>Robot: robot.run("message")
121
+ Robot->>Memory: resolve_active_memory
122
+ Memory-->>Robot: active memory
127
123
 
128
- ## Adapter Layer
124
+ Robot->>MCP: resolve_mcp_hierarchy
125
+ MCP-->>Robot: resolved MCP config
126
+ Robot->>Robot: ensure_mcp_clients
129
127
 
130
- Adapters convert between RobotLab's message format and provider-specific formats.
128
+ Robot->>Tools: resolve_tools_hierarchy
129
+ Tools-->>Robot: filtered tools
130
+ Robot->>Chat: @chat.with_tools(...)
131
131
 
132
- ### Anthropic Adapter
132
+ Robot->>Agent: ask("message")
133
+ Agent->>Chat: @chat.ask("message")
134
+ Chat->>LLM: Provider API call
133
135
 
134
- ```ruby
135
- # RobotLab format
136
- messages = [
137
- TextMessage.new(role: "user", content: "Hello"),
138
- TextMessage.new(role: "assistant", content: "Hi there!")
139
- ]
140
-
141
- # Converted to Anthropic format
142
- [
143
- { role: "user", content: "Hello" },
144
- { role: "assistant", content: "Hi there!" }
145
- ]
136
+ loop Tool Loop (handled by RubyLLM)
137
+ LLM-->>Chat: Tool call response
138
+ Chat->>Tools: Execute tool
139
+ Tools-->>Chat: Tool result
140
+ Chat->>LLM: Send tool result
141
+ end
142
+
143
+ LLM-->>Chat: Final response
144
+ Chat-->>Agent: RubyLLM::Response
145
+ Agent-->>Robot: response
146
+
147
+ Robot->>Robot: build_result(response, memory)
148
+ Robot-->>User: RobotResult
146
149
  ```
147
150
 
148
- ### System Message Handling
151
+ ### Step-by-Step
149
152
 
150
- System messages are handled specially:
153
+ 1. **`robot.run("message")`**: Entry point. Accepts a positional string argument.
151
154
 
152
- ```ruby
153
- # RobotLab
154
- messages = [
155
- TextMessage.new(role: "system", content: "You are helpful."),
156
- TextMessage.new(role: "user", content: "Hello")
157
- ]
158
-
159
- # Anthropic: system extracted separately
160
- # OpenAI: system message stays in array
161
- # Gemini: converted to context
162
- ```
155
+ 2. **Resolve Memory**: Determines which memory to use:
156
+ - `network_memory` if provided (network execution)
157
+ - `network.memory` if in a network context
158
+ - `robot.memory` (standalone, the default)
163
159
 
164
- ### Tool Message Conversion
160
+ 3. **Merge Runtime Memory**: If a `memory:` keyword argument is passed, it is merged into the active memory.
165
161
 
166
- Tool calls are provider-specific:
162
+ 4. **Set Current Writer**: Sets `memory.current_writer = robot.name` so subscription callbacks know which robot wrote a value.
167
163
 
168
- === "Anthropic"
164
+ 5. **Resolve MCP Hierarchy**: Resolves MCP server configuration through the hierarchy: `runtime > robot build > network > global config`.
169
165
 
170
- ```ruby
171
- {
172
- type: "tool_use",
173
- id: "tool_123",
174
- name: "get_weather",
175
- input: { location: "Paris" }
176
- }
177
- ```
166
+ 6. **Ensure MCP Clients**: Initializes or updates MCP client connections. Discovers tools from connected MCP servers.
178
167
 
179
- === "OpenAI"
168
+ 7. **Resolve Tools Hierarchy**: Resolves which tools are available through the same hierarchy.
180
169
 
181
- ```ruby
182
- {
183
- type: "function",
184
- function: {
185
- name: "get_weather",
186
- arguments: '{"location":"Paris"}'
187
- }
188
- }
189
- ```
170
+ 8. **Filter Tools**: Applies the resolved tool list to `@chat.with_tools(...)`.
190
171
 
191
- ## Conversation Building
172
+ 9. **Agent#ask**: Delegates to the parent class `RubyLLM::Agent#ask`, which calls `@chat.ask(message)`.
192
173
 
193
- ### Initial State
174
+ 10. **LLM Interaction**: RubyLLM handles the provider-specific API call, including the tool call/result loop.
194
175
 
195
- ```ruby
196
- state = RobotLab.create_state(
197
- message: "What's the weather?",
198
- data: { location: "Paris" }
199
- )
176
+ 11. **Build Result**: Wraps the LLM response in a `RobotResult` containing output messages, tool calls, and metadata.
200
177
 
201
- state.messages
202
- # => [TextMessage(role: "user", content: "What's the weather?")]
203
- ```
178
+ 12. **Return**: Returns the `RobotResult` to the caller.
204
179
 
205
- ### After Robot Execution
180
+ ## Message Flow: Network Execution
206
181
 
207
- ```ruby
208
- result = robot.run(state: state, network: network)
209
- state.append_result(result)
210
-
211
- state.messages
212
- # => [
213
- # TextMessage(role: "user", content: "What's the weather?"),
214
- # TextMessage(role: "assistant", content: "The weather is sunny.")
215
- # ]
182
+ When running through a network, the flow adds pipeline orchestration:
183
+
184
+ ```mermaid
185
+ sequenceDiagram
186
+ participant User
187
+ participant Network
188
+ participant Pipeline
189
+ participant Task
190
+ participant Robot
191
+ participant LLM
192
+
193
+ User->>Network: network.run(message: "...")
194
+ Network->>Network: Inject network_memory into run_context
195
+ Network->>Pipeline: SimpleFlow::Pipeline.call_parallel(initial_result)
196
+
197
+ loop For each ready task
198
+ Pipeline->>Task: task.call(result)
199
+ Task->>Task: Deep merge task context with run_params
200
+ Task->>Robot: robot.call(enhanced_result)
201
+ Robot->>Robot: extract_run_context(result)
202
+ Robot->>Robot: run(message, network_memory: ...)
203
+ Robot->>LLM: Agent#ask -> @chat.ask
204
+ LLM-->>Robot: Response
205
+ Robot-->>Task: result.with_context(:name, robot_result).continue(robot_result)
206
+ Task-->>Pipeline: SimpleFlow::Result
207
+ end
208
+
209
+ Pipeline-->>Network: Final SimpleFlow::Result
210
+ Network-->>User: result
216
211
  ```
217
212
 
218
- ### With Tool Calls
213
+ ### Key Points
214
+
215
+ - **Network creates initial result**: `SimpleFlow::Result.new(run_context, context: { run_params: run_context })`
216
+ - **Task wraps robot**: Each `Task` deep-merges its own context with the run params before delegating to the robot
217
+ - **Robot extracts context**: `extract_run_context(result)` pulls the message, MCP, tools, and memory from the SimpleFlow result
218
+ - **Shared memory**: All robots use `network.memory` during network execution
219
+ - **Result accumulation**: Each task stores its `RobotResult` in `result.context[:task_name]`
220
+
221
+ ## RobotResult
222
+
223
+ The return value of `robot.run("message")`:
219
224
 
220
225
  ```ruby
221
- state.messages
222
- # => [
223
- # TextMessage(role: "user", content: "What's the weather?"),
224
- # ToolCallMessage(tools: [ToolMessage(name: "get_weather", ...)]),
225
- # ToolResultMessage(content: { temp: 22 }),
226
- # TextMessage(role: "assistant", content: "It's 22°C and sunny.")
227
- # ]
226
+ result = robot.run("What is Ruby?")
227
+
228
+ result.last_text_content #=> "Ruby is a dynamic programming language..."
229
+ result.has_tool_calls? #=> false
230
+ result.robot_name #=> "assistant"
231
+ result.output #=> [TextMessage(role: "assistant", content: "...")]
232
+ result.tool_calls #=> []
233
+ result.stop_reason #=> "stop"
234
+ result.created_at #=> Time
235
+ result.id #=> "uuid"
236
+ result.checksum #=> "sha256-hex"
228
237
  ```
229
238
 
230
- ## Format History
231
-
232
- The `format_history` method prepares messages for LLM:
239
+ ### Result Serialization
233
240
 
234
241
  ```ruby
235
- # Includes results from previous robots
236
- formatted = state.format_history
242
+ # Export for persistence (excludes debug fields)
243
+ hash = result.export
244
+
245
+ # Full hash including debug fields
246
+ hash = result.to_h
247
+
248
+ # JSON
249
+ json = result.to_json
237
250
 
238
- # Returns alternating user/assistant messages
239
- # with tool calls/results properly interleaved
251
+ # Reconstruct from hash
252
+ result = RobotResult.from_hash(hash)
240
253
  ```
241
254
 
242
255
  ## Message Predicates
243
256
 
244
- Check message types easily:
257
+ Check message types:
245
258
 
246
259
  ```ruby
247
260
  message.text? # Is it a TextMessage?
@@ -274,14 +287,6 @@ Message.from_hash(
274
287
  )
275
288
  ```
276
289
 
277
- ### From UserMessage
278
-
279
- ```ruby
280
- user_msg = UserMessage.new("Hello", thread_id: "123")
281
- text_msg = user_msg.to_message
282
- # => TextMessage(role: "user", content: "Hello")
283
- ```
284
-
285
290
  ## Serialization
286
291
 
287
292
  Messages can be serialized:
@@ -289,32 +294,36 @@ Messages can be serialized:
289
294
  ```ruby
290
295
  # To hash
291
296
  hash = message.to_h
292
- # => { type: "text", role: "user", content: "Hello" }
297
+ #=> { type: "text", role: "user", content: "Hello" }
293
298
 
294
299
  # To JSON
295
300
  json = message.to_json
296
- # => '{"type":"text","role":"user","content":"Hello"}'
297
301
 
298
302
  # From hash
299
303
  message = Message.from_hash(hash)
300
304
  ```
301
305
 
302
- ## Provider Registry
306
+ ## Template Resolution
303
307
 
304
- The adapter registry maps providers to adapters:
308
+ When a robot has a template, it is resolved at build time via prompt_manager:
305
309
 
306
310
  ```ruby
307
- Adapters::Registry.for(:anthropic) # => Adapters::Anthropic
308
- Adapters::Registry.for(:openai) # => Adapters::OpenAI
309
- Adapters::Registry.for(:gemini) # => Adapters::Gemini
310
-
311
- # Aliases
312
- Adapters::Registry.for(:azure_openai) # => Adapters::OpenAI
313
- Adapters::Registry.for(:bedrock) # => Adapters::Anthropic
311
+ robot = RobotLab.build(
312
+ name: "helper",
313
+ template: :helper,
314
+ context: { tone: "friendly" }
315
+ )
314
316
  ```
315
317
 
318
+ The template resolution process:
319
+ 1. `PM.parse(:helper)` loads the template file from the configured prompts directory
320
+ 2. YAML front matter is extracted and applied to the chat (model, temperature, etc.)
321
+ 3. The template body is rendered with the provided context
322
+ 4. The rendered text is set as system instructions via `@chat.with_instructions(rendered)`
323
+
324
+ If both `template:` and `system_prompt:` are provided, the template is applied first, then the system prompt is appended via a second `@chat.with_instructions` call.
325
+
316
326
  ## Next Steps
317
327
 
318
- - [Building Robots](../guides/building-robots.md) - Creating custom robots
319
- - [Using Tools](../guides/using-tools.md) - Tool message handling
320
- - [API Reference: Messages](../api/messages/index.md) - Detailed message API
328
+ - [Memory Management](state-management.md) - How memory stores conversation data
329
+ - [Network Orchestration](network-orchestration.md) - Multi-robot pipeline execution