robot_lab 0.0.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 +7 -0
- data/.envrc +1 -0
- data/.github/workflows/deploy-github-pages.yml +52 -0
- data/.github/workflows/deploy-yard-docs.yml +52 -0
- data/CHANGELOG.md +55 -0
- data/COMMITS.md +196 -0
- data/LICENSE.txt +21 -0
- data/README.md +332 -0
- data/Rakefile +67 -0
- data/docs/api/adapters/anthropic.md +121 -0
- data/docs/api/adapters/gemini.md +133 -0
- data/docs/api/adapters/index.md +104 -0
- data/docs/api/adapters/openai.md +134 -0
- data/docs/api/core/index.md +113 -0
- data/docs/api/core/memory.md +314 -0
- data/docs/api/core/network.md +291 -0
- data/docs/api/core/robot.md +273 -0
- data/docs/api/core/state.md +273 -0
- data/docs/api/core/tool.md +353 -0
- data/docs/api/history/active-record-adapter.md +195 -0
- data/docs/api/history/config.md +191 -0
- data/docs/api/history/index.md +132 -0
- data/docs/api/history/thread-manager.md +144 -0
- data/docs/api/index.md +82 -0
- data/docs/api/mcp/client.md +221 -0
- data/docs/api/mcp/index.md +111 -0
- data/docs/api/mcp/server.md +225 -0
- data/docs/api/mcp/transports.md +264 -0
- data/docs/api/messages/index.md +67 -0
- data/docs/api/messages/text-message.md +102 -0
- data/docs/api/messages/tool-call-message.md +144 -0
- data/docs/api/messages/tool-result-message.md +154 -0
- data/docs/api/messages/user-message.md +171 -0
- data/docs/api/streaming/context.md +174 -0
- data/docs/api/streaming/events.md +237 -0
- data/docs/api/streaming/index.md +108 -0
- data/docs/architecture/core-concepts.md +243 -0
- data/docs/architecture/index.md +138 -0
- data/docs/architecture/message-flow.md +320 -0
- data/docs/architecture/network-orchestration.md +216 -0
- data/docs/architecture/robot-execution.md +243 -0
- data/docs/architecture/state-management.md +323 -0
- data/docs/assets/css/custom.css +56 -0
- data/docs/assets/images/robot_lab.jpg +0 -0
- data/docs/concepts.md +216 -0
- data/docs/examples/basic-chat.md +193 -0
- data/docs/examples/index.md +129 -0
- data/docs/examples/mcp-server.md +290 -0
- data/docs/examples/multi-robot-network.md +312 -0
- data/docs/examples/rails-application.md +420 -0
- data/docs/examples/tool-usage.md +310 -0
- data/docs/getting-started/configuration.md +230 -0
- data/docs/getting-started/index.md +56 -0
- data/docs/getting-started/installation.md +179 -0
- data/docs/getting-started/quick-start.md +203 -0
- data/docs/guides/building-robots.md +376 -0
- data/docs/guides/creating-networks.md +366 -0
- data/docs/guides/history.md +359 -0
- data/docs/guides/index.md +68 -0
- data/docs/guides/mcp-integration.md +356 -0
- data/docs/guides/memory.md +309 -0
- data/docs/guides/rails-integration.md +432 -0
- data/docs/guides/streaming.md +314 -0
- data/docs/guides/using-tools.md +394 -0
- data/docs/index.md +160 -0
- data/examples/01_simple_robot.rb +38 -0
- data/examples/02_tools.rb +106 -0
- data/examples/03_network.rb +103 -0
- data/examples/04_mcp.rb +219 -0
- data/examples/05_streaming.rb +124 -0
- data/examples/06_prompt_templates.rb +324 -0
- data/examples/07_network_memory.rb +329 -0
- data/examples/prompts/assistant/system.txt.erb +2 -0
- data/examples/prompts/assistant/user.txt.erb +1 -0
- data/examples/prompts/billing/system.txt.erb +7 -0
- data/examples/prompts/billing/user.txt.erb +1 -0
- data/examples/prompts/classifier/system.txt.erb +4 -0
- data/examples/prompts/classifier/user.txt.erb +1 -0
- data/examples/prompts/entity_extractor/system.txt.erb +11 -0
- data/examples/prompts/entity_extractor/user.txt.erb +3 -0
- data/examples/prompts/escalation/system.txt.erb +35 -0
- data/examples/prompts/escalation/user.txt.erb +34 -0
- data/examples/prompts/general/system.txt.erb +4 -0
- data/examples/prompts/general/user.txt.erb +1 -0
- data/examples/prompts/github_assistant/system.txt.erb +6 -0
- data/examples/prompts/github_assistant/user.txt.erb +1 -0
- data/examples/prompts/helper/system.txt.erb +1 -0
- data/examples/prompts/helper/user.txt.erb +1 -0
- data/examples/prompts/keyword_extractor/system.txt.erb +8 -0
- data/examples/prompts/keyword_extractor/user.txt.erb +3 -0
- data/examples/prompts/order_support/system.txt.erb +27 -0
- data/examples/prompts/order_support/user.txt.erb +22 -0
- data/examples/prompts/product_support/system.txt.erb +30 -0
- data/examples/prompts/product_support/user.txt.erb +32 -0
- data/examples/prompts/sentiment_analyzer/system.txt.erb +9 -0
- data/examples/prompts/sentiment_analyzer/user.txt.erb +3 -0
- data/examples/prompts/synthesizer/system.txt.erb +14 -0
- data/examples/prompts/synthesizer/user.txt.erb +15 -0
- data/examples/prompts/technical/system.txt.erb +7 -0
- data/examples/prompts/technical/user.txt.erb +1 -0
- data/examples/prompts/triage/system.txt.erb +16 -0
- data/examples/prompts/triage/user.txt.erb +17 -0
- data/lib/generators/robot_lab/install_generator.rb +78 -0
- data/lib/generators/robot_lab/robot_generator.rb +55 -0
- data/lib/generators/robot_lab/templates/initializer.rb.tt +41 -0
- data/lib/generators/robot_lab/templates/migration.rb.tt +32 -0
- data/lib/generators/robot_lab/templates/result_model.rb.tt +52 -0
- data/lib/generators/robot_lab/templates/robot.rb.tt +46 -0
- data/lib/generators/robot_lab/templates/robot_test.rb.tt +32 -0
- data/lib/generators/robot_lab/templates/routing_robot.rb.tt +53 -0
- data/lib/generators/robot_lab/templates/thread_model.rb.tt +40 -0
- data/lib/robot_lab/adapters/anthropic.rb +163 -0
- data/lib/robot_lab/adapters/base.rb +85 -0
- data/lib/robot_lab/adapters/gemini.rb +193 -0
- data/lib/robot_lab/adapters/openai.rb +159 -0
- data/lib/robot_lab/adapters/registry.rb +81 -0
- data/lib/robot_lab/configuration.rb +143 -0
- data/lib/robot_lab/error.rb +32 -0
- data/lib/robot_lab/errors.rb +70 -0
- data/lib/robot_lab/history/active_record_adapter.rb +146 -0
- data/lib/robot_lab/history/config.rb +115 -0
- data/lib/robot_lab/history/thread_manager.rb +93 -0
- data/lib/robot_lab/mcp/client.rb +210 -0
- data/lib/robot_lab/mcp/server.rb +84 -0
- data/lib/robot_lab/mcp/transports/base.rb +56 -0
- data/lib/robot_lab/mcp/transports/sse.rb +117 -0
- data/lib/robot_lab/mcp/transports/stdio.rb +133 -0
- data/lib/robot_lab/mcp/transports/streamable_http.rb +139 -0
- data/lib/robot_lab/mcp/transports/websocket.rb +108 -0
- data/lib/robot_lab/memory.rb +882 -0
- data/lib/robot_lab/memory_change.rb +123 -0
- data/lib/robot_lab/message.rb +357 -0
- data/lib/robot_lab/network.rb +350 -0
- data/lib/robot_lab/rails/engine.rb +29 -0
- data/lib/robot_lab/rails/railtie.rb +42 -0
- data/lib/robot_lab/robot.rb +560 -0
- data/lib/robot_lab/robot_result.rb +205 -0
- data/lib/robot_lab/robotic_model.rb +324 -0
- data/lib/robot_lab/state_proxy.rb +188 -0
- data/lib/robot_lab/streaming/context.rb +144 -0
- data/lib/robot_lab/streaming/events.rb +95 -0
- data/lib/robot_lab/streaming/sequence_counter.rb +48 -0
- data/lib/robot_lab/task.rb +117 -0
- data/lib/robot_lab/tool.rb +223 -0
- data/lib/robot_lab/tool_config.rb +112 -0
- data/lib/robot_lab/tool_manifest.rb +234 -0
- data/lib/robot_lab/user_message.rb +118 -0
- data/lib/robot_lab/version.rb +5 -0
- data/lib/robot_lab/waiter.rb +73 -0
- data/lib/robot_lab.rb +195 -0
- data/mkdocs.yml +214 -0
- data/sig/robot_lab.rbs +4 -0
- metadata +442 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Guides
|
|
2
|
+
|
|
3
|
+
Practical guides for building applications with RobotLab.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
If you're new to RobotLab, start here:
|
|
8
|
+
|
|
9
|
+
<div class="grid cards" markdown>
|
|
10
|
+
|
|
11
|
+
- [:octicons-cpu-24: **Building Robots**](building-robots.md)
|
|
12
|
+
|
|
13
|
+
Create specialized AI agents with personalities and tools
|
|
14
|
+
|
|
15
|
+
- [:octicons-git-branch-24: **Creating Networks**](creating-networks.md)
|
|
16
|
+
|
|
17
|
+
Orchestrate multiple robots for complex workflows
|
|
18
|
+
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
## Core Features
|
|
22
|
+
|
|
23
|
+
<div class="grid cards" markdown>
|
|
24
|
+
|
|
25
|
+
- [:octicons-tools-24: **Using Tools**](using-tools.md)
|
|
26
|
+
|
|
27
|
+
Give robots custom capabilities to interact with external systems
|
|
28
|
+
|
|
29
|
+
- [:octicons-server-24: **MCP Integration**](mcp-integration.md)
|
|
30
|
+
|
|
31
|
+
Connect to Model Context Protocol servers
|
|
32
|
+
|
|
33
|
+
- [:octicons-broadcast-24: **Streaming Responses**](streaming.md)
|
|
34
|
+
|
|
35
|
+
Real-time streaming of LLM responses
|
|
36
|
+
|
|
37
|
+
- [:octicons-database-24: **Conversation History**](history.md)
|
|
38
|
+
|
|
39
|
+
Persist and restore conversation threads
|
|
40
|
+
|
|
41
|
+
- [:octicons-cpu-24: **Memory System**](memory.md)
|
|
42
|
+
|
|
43
|
+
Share data between robots with the memory system
|
|
44
|
+
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
## Framework Integration
|
|
48
|
+
|
|
49
|
+
<div class="grid cards" markdown>
|
|
50
|
+
|
|
51
|
+
- [:material-language-ruby:{ .lg } **Rails Integration**](rails-integration.md)
|
|
52
|
+
|
|
53
|
+
Use RobotLab in Ruby on Rails applications
|
|
54
|
+
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
## Guide Index
|
|
58
|
+
|
|
59
|
+
| Guide | Description | Time |
|
|
60
|
+
|-------|-------------|------|
|
|
61
|
+
| [Building Robots](building-robots.md) | Create and configure robots | 10 min |
|
|
62
|
+
| [Creating Networks](creating-networks.md) | Multi-robot orchestration | 15 min |
|
|
63
|
+
| [Using Tools](using-tools.md) | Add custom capabilities | 10 min |
|
|
64
|
+
| [MCP Integration](mcp-integration.md) | External tool servers | 10 min |
|
|
65
|
+
| [Streaming](streaming.md) | Real-time responses | 5 min |
|
|
66
|
+
| [History](history.md) | Conversation persistence | 10 min |
|
|
67
|
+
| [Memory](memory.md) | Shared data store | 5 min |
|
|
68
|
+
| [Rails Integration](rails-integration.md) | Rails application setup | 15 min |
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
# MCP Integration
|
|
2
|
+
|
|
3
|
+
RobotLab supports the Model Context Protocol (MCP) for connecting to external tool servers.
|
|
4
|
+
|
|
5
|
+
## What is MCP?
|
|
6
|
+
|
|
7
|
+
MCP is a protocol that allows LLM applications to connect to external servers that provide tools, resources, and context. This enables:
|
|
8
|
+
|
|
9
|
+
- Reusable tool servers across applications
|
|
10
|
+
- Separation of tool logic from AI logic
|
|
11
|
+
- Dynamic tool discovery
|
|
12
|
+
|
|
13
|
+
## Configuring MCP Servers
|
|
14
|
+
|
|
15
|
+
### At Network Level
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
network = RobotLab.create_network do
|
|
19
|
+
name "dev_assistant"
|
|
20
|
+
|
|
21
|
+
mcp [
|
|
22
|
+
{
|
|
23
|
+
name: "filesystem",
|
|
24
|
+
transport: {
|
|
25
|
+
type: "stdio",
|
|
26
|
+
command: "mcp-server-filesystem",
|
|
27
|
+
args: ["--root", "/home/user/projects"]
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: "github",
|
|
32
|
+
transport: {
|
|
33
|
+
type: "stdio",
|
|
34
|
+
command: "mcp-server-github"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
end
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### At Robot Level
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
robot = RobotLab.build do
|
|
45
|
+
name "coder"
|
|
46
|
+
|
|
47
|
+
# Use network's MCP servers
|
|
48
|
+
mcp :inherit
|
|
49
|
+
|
|
50
|
+
# Or specific servers
|
|
51
|
+
mcp [
|
|
52
|
+
{ name: "filesystem", transport: { type: "stdio", command: "mcp-fs" } }
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
# Or disable MCP
|
|
56
|
+
mcp :none
|
|
57
|
+
end
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Global Configuration
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
RobotLab.configure do |config|
|
|
64
|
+
config.mcp = [
|
|
65
|
+
{ name: "common_tools", transport: { type: "stdio", command: "common-mcp" } }
|
|
66
|
+
]
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Transport Types
|
|
71
|
+
|
|
72
|
+
### Stdio Transport
|
|
73
|
+
|
|
74
|
+
Communicate via stdin/stdout with a subprocess:
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
{
|
|
78
|
+
name: "server_name",
|
|
79
|
+
transport: {
|
|
80
|
+
type: "stdio",
|
|
81
|
+
command: "mcp-server-command",
|
|
82
|
+
args: ["--option", "value"],
|
|
83
|
+
env: { "API_KEY" => ENV["API_KEY"] }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### WebSocket Transport
|
|
89
|
+
|
|
90
|
+
Connect via WebSocket:
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
{
|
|
94
|
+
name: "remote_server",
|
|
95
|
+
transport: {
|
|
96
|
+
type: "websocket",
|
|
97
|
+
url: "ws://localhost:8080/mcp"
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
!!! note "Dependency Required"
|
|
103
|
+
WebSocket transport requires the `async-websocket` gem.
|
|
104
|
+
|
|
105
|
+
### SSE Transport
|
|
106
|
+
|
|
107
|
+
Server-Sent Events transport:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
{
|
|
111
|
+
name: "sse_server",
|
|
112
|
+
transport: {
|
|
113
|
+
type: "sse",
|
|
114
|
+
url: "http://localhost:8080/sse"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### HTTP Transport
|
|
120
|
+
|
|
121
|
+
Streamable HTTP transport with session support:
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
{
|
|
125
|
+
name: "http_server",
|
|
126
|
+
transport: {
|
|
127
|
+
type: "streamable_http",
|
|
128
|
+
url: "https://api.example.com/mcp",
|
|
129
|
+
session_id: "optional_session_id",
|
|
130
|
+
auth_provider: -> { "Bearer #{fetch_token}" }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Using MCP Tools
|
|
136
|
+
|
|
137
|
+
Once configured, MCP tools are automatically available to robots:
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
network = RobotLab.create_network do
|
|
141
|
+
mcp [
|
|
142
|
+
{ name: "github", transport: { type: "stdio", command: "mcp-server-github" } }
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
add_robot RobotLab.build {
|
|
146
|
+
name "helper"
|
|
147
|
+
template <<~PROMPT
|
|
148
|
+
You can help users with GitHub tasks.
|
|
149
|
+
Use available tools to search repositories, create issues, etc.
|
|
150
|
+
PROMPT
|
|
151
|
+
}
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# The robot can now use GitHub MCP tools
|
|
155
|
+
state = RobotLab.create_state(message: "Find repositories about machine learning")
|
|
156
|
+
network.run(state: state)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Filtering MCP Tools
|
|
160
|
+
|
|
161
|
+
Restrict which MCP tools are available:
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
robot = RobotLab.build do
|
|
165
|
+
name "reader"
|
|
166
|
+
mcp :inherit
|
|
167
|
+
|
|
168
|
+
# Only allow specific MCP tools
|
|
169
|
+
tools %w[read_file search_code list_directory]
|
|
170
|
+
end
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## MCP Server Configuration
|
|
174
|
+
|
|
175
|
+
### Server Object
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
server = RobotLab::MCP::Server.new(
|
|
179
|
+
name: "my_server",
|
|
180
|
+
transport: {
|
|
181
|
+
type: "stdio",
|
|
182
|
+
command: "my-mcp-server"
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
server.name # => "my_server"
|
|
187
|
+
server.transport_type # => "stdio"
|
|
188
|
+
server.to_h # Hash representation
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Client Object
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
client = RobotLab::MCP::Client.new(server: server)
|
|
195
|
+
client.connect
|
|
196
|
+
|
|
197
|
+
client.connected? # => true
|
|
198
|
+
client.to_h # Client info
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Common MCP Servers
|
|
202
|
+
|
|
203
|
+
### Filesystem
|
|
204
|
+
|
|
205
|
+
```ruby
|
|
206
|
+
{
|
|
207
|
+
name: "filesystem",
|
|
208
|
+
transport: {
|
|
209
|
+
type: "stdio",
|
|
210
|
+
command: "mcp-server-filesystem",
|
|
211
|
+
args: ["--root", "/path/to/files"]
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Tools: `read_file`, `write_file`, `list_directory`, `search_files`
|
|
217
|
+
|
|
218
|
+
### GitHub
|
|
219
|
+
|
|
220
|
+
```ruby
|
|
221
|
+
{
|
|
222
|
+
name: "github",
|
|
223
|
+
transport: {
|
|
224
|
+
type: "stdio",
|
|
225
|
+
command: "mcp-server-github",
|
|
226
|
+
env: { "GITHUB_TOKEN" => ENV["GITHUB_TOKEN"] }
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Tools: `search_repositories`, `create_issue`, `get_file_contents`, etc.
|
|
232
|
+
|
|
233
|
+
### Database
|
|
234
|
+
|
|
235
|
+
```ruby
|
|
236
|
+
{
|
|
237
|
+
name: "postgres",
|
|
238
|
+
transport: {
|
|
239
|
+
type: "stdio",
|
|
240
|
+
command: "mcp-server-postgres",
|
|
241
|
+
env: { "DATABASE_URL" => ENV["DATABASE_URL"] }
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Tools: `query`, `list_tables`, `describe_table`
|
|
247
|
+
|
|
248
|
+
## Error Handling
|
|
249
|
+
|
|
250
|
+
### Connection Errors
|
|
251
|
+
|
|
252
|
+
```ruby
|
|
253
|
+
begin
|
|
254
|
+
network.run(state: state)
|
|
255
|
+
rescue RobotLab::MCPError => e
|
|
256
|
+
puts "MCP Error: #{e.message}"
|
|
257
|
+
# Handle gracefully
|
|
258
|
+
end
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Missing Dependencies
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
# If async-websocket not installed
|
|
265
|
+
rescue RobotLab::MCPError => e
|
|
266
|
+
if e.message.include?("async-websocket")
|
|
267
|
+
puts "Install async-websocket gem for WebSocket support"
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Disconnecting
|
|
273
|
+
|
|
274
|
+
Robots automatically disconnect from MCP servers when done:
|
|
275
|
+
|
|
276
|
+
```ruby
|
|
277
|
+
robot.disconnect # Manually disconnect
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Networks handle this automatically at the end of a run.
|
|
281
|
+
|
|
282
|
+
## Patterns
|
|
283
|
+
|
|
284
|
+
### Development vs Production
|
|
285
|
+
|
|
286
|
+
```ruby
|
|
287
|
+
network = RobotLab.create_network do
|
|
288
|
+
mcp_config = if Rails.env.development?
|
|
289
|
+
[{ name: "local_fs", transport: { type: "stdio", command: "mcp-fs", args: ["--root", "."] } }]
|
|
290
|
+
else
|
|
291
|
+
[{ name: "s3", transport: { type: "stdio", command: "mcp-s3" } }]
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
mcp mcp_config
|
|
295
|
+
end
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Dynamic Server Selection
|
|
299
|
+
|
|
300
|
+
```ruby
|
|
301
|
+
def mcp_servers_for_user(user)
|
|
302
|
+
servers = []
|
|
303
|
+
servers << github_server if user.github_connected?
|
|
304
|
+
servers << slack_server if user.slack_connected?
|
|
305
|
+
servers
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
network = RobotLab.create_network do
|
|
309
|
+
mcp mcp_servers_for_user(current_user)
|
|
310
|
+
end
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Best Practices
|
|
314
|
+
|
|
315
|
+
### 1. Use Environment Variables for Credentials
|
|
316
|
+
|
|
317
|
+
```ruby
|
|
318
|
+
{
|
|
319
|
+
name: "github",
|
|
320
|
+
transport: {
|
|
321
|
+
type: "stdio",
|
|
322
|
+
command: "mcp-server-github",
|
|
323
|
+
env: {
|
|
324
|
+
"GITHUB_TOKEN" => ENV["GITHUB_TOKEN"],
|
|
325
|
+
"GITHUB_ORG" => ENV["GITHUB_ORG"]
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### 2. Limit Tool Access
|
|
332
|
+
|
|
333
|
+
```ruby
|
|
334
|
+
# Don't expose all tools
|
|
335
|
+
robot = RobotLab.build do
|
|
336
|
+
mcp :inherit
|
|
337
|
+
tools %w[read_file search_files] # No write access
|
|
338
|
+
end
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### 3. Handle Disconnections
|
|
342
|
+
|
|
343
|
+
```ruby
|
|
344
|
+
begin
|
|
345
|
+
result = network.run(state: state)
|
|
346
|
+
rescue RobotLab::MCPError
|
|
347
|
+
# Retry without MCP
|
|
348
|
+
result = network.run(state: state, mcp: :none)
|
|
349
|
+
end
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## Next Steps
|
|
353
|
+
|
|
354
|
+
- [Using Tools](using-tools.md) - Local tool patterns
|
|
355
|
+
- [Creating Networks](creating-networks.md) - Network configuration
|
|
356
|
+
- [API Reference: MCP](../api/mcp/index.md) - Complete MCP API
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# Memory System
|
|
2
|
+
|
|
3
|
+
The memory system allows robots to share data within a network run.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Memory provides:
|
|
8
|
+
|
|
9
|
+
- Key-value storage accessible by all robots
|
|
10
|
+
- Namespaced scopes for organization
|
|
11
|
+
- Persistence within a single network run
|
|
12
|
+
|
|
13
|
+
## Basic Usage
|
|
14
|
+
|
|
15
|
+
### Store Values
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
state.memory.remember("user_name", "Alice")
|
|
19
|
+
state.memory.remember("preferences", { theme: "dark", language: "en" })
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Retrieve Values
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
name = state.memory.recall("user_name") # => "Alice"
|
|
26
|
+
prefs = state.memory.recall("preferences") # => { theme: "dark", ... }
|
|
27
|
+
|
|
28
|
+
# Returns nil if not found
|
|
29
|
+
missing = state.memory.recall("unknown") # => nil
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Check Existence
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
state.memory.exists?("user_name") # => true
|
|
36
|
+
state.memory.exists?("unknown") # => false
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Remove Values
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
state.memory.forget("user_name")
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Scoped Memory
|
|
46
|
+
|
|
47
|
+
Organize data with namespaces:
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
# Create a scoped view
|
|
51
|
+
user_memory = state.memory.scoped("user:123")
|
|
52
|
+
|
|
53
|
+
# Operations are scoped
|
|
54
|
+
user_memory.remember("name", "Alice")
|
|
55
|
+
user_memory.remember("email", "alice@example.com")
|
|
56
|
+
|
|
57
|
+
# Keys are prefixed
|
|
58
|
+
state.memory.recall("user:123:name") # => "Alice"
|
|
59
|
+
|
|
60
|
+
# Scoped recall
|
|
61
|
+
user_memory.recall("name") # => "Alice"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Nested Scopes
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
session = state.memory.scoped("session:abc")
|
|
68
|
+
prefs = session.scoped("preferences")
|
|
69
|
+
|
|
70
|
+
prefs.remember("theme", "dark")
|
|
71
|
+
# Full key: "session:abc:preferences:theme"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Memory Operations
|
|
75
|
+
|
|
76
|
+
### List All Keys
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
state.memory.all
|
|
80
|
+
# => {
|
|
81
|
+
# "user_name" => "Alice",
|
|
82
|
+
# "user:123:email" => "alice@example.com",
|
|
83
|
+
# ...
|
|
84
|
+
# }
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### List Namespaces
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
state.memory.namespaces
|
|
91
|
+
# => ["user:123", "session:abc", ...]
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Search by Pattern
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
# Find keys matching pattern
|
|
98
|
+
matches = state.memory.search("user:*")
|
|
99
|
+
# => { "user:123:name" => "Alice", "user:123:email" => "..." }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Statistics
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
state.memory.stats
|
|
106
|
+
# => { total_keys: 15, namespaces: ["user:123", "session"] }
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Clear Memory
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
# Clear a namespace
|
|
113
|
+
state.memory.scoped("temp").clear
|
|
114
|
+
|
|
115
|
+
# Clear all memory
|
|
116
|
+
state.memory.clear_all
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Shared Namespace
|
|
120
|
+
|
|
121
|
+
The `SHARED` namespace is a convention for cross-robot data:
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
# In first robot
|
|
125
|
+
state.memory.remember("SHARED:context", important_data)
|
|
126
|
+
|
|
127
|
+
# In later robot
|
|
128
|
+
context = state.memory.recall("SHARED:context")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Using Shared Scope
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
shared = state.memory.scoped(RobotLab::Memory::SHARED_NAMESPACE)
|
|
135
|
+
shared.remember("workflow_status", "in_progress")
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## In Tool Handlers
|
|
139
|
+
|
|
140
|
+
Access memory from tools:
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
tool :update_preference do
|
|
144
|
+
description "Update user preference"
|
|
145
|
+
parameter :key, type: :string, required: true
|
|
146
|
+
parameter :value, type: :string, required: true
|
|
147
|
+
|
|
148
|
+
handler do |key:, value:, state:, **_|
|
|
149
|
+
prefs = state.memory.scoped("preferences")
|
|
150
|
+
prefs.remember(key, value)
|
|
151
|
+
{ success: true, key: key, value: value }
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## In Routers
|
|
157
|
+
|
|
158
|
+
Use memory for routing decisions:
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
router = ->(args) {
|
|
162
|
+
case args.call_count
|
|
163
|
+
when 0
|
|
164
|
+
:classifier
|
|
165
|
+
when 1
|
|
166
|
+
# Read classification from memory
|
|
167
|
+
intent = args.network.state.memory.recall("SHARED:intent")
|
|
168
|
+
case intent
|
|
169
|
+
when "billing" then :billing_agent
|
|
170
|
+
when "technical" then :tech_agent
|
|
171
|
+
else :general_agent
|
|
172
|
+
end
|
|
173
|
+
else
|
|
174
|
+
nil
|
|
175
|
+
end
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Patterns
|
|
180
|
+
|
|
181
|
+
### Accumulating Data
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
# In each robot
|
|
185
|
+
def add_finding(state, finding)
|
|
186
|
+
findings = state.memory.recall("findings") || []
|
|
187
|
+
findings << finding
|
|
188
|
+
state.memory.remember("findings", findings)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# In final robot
|
|
192
|
+
all_findings = state.memory.recall("findings")
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Tracking Progress
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
# Track workflow stages
|
|
199
|
+
state.memory.remember("stage", "intake")
|
|
200
|
+
# ... processing ...
|
|
201
|
+
state.memory.remember("stage", "analysis")
|
|
202
|
+
# ... processing ...
|
|
203
|
+
state.memory.remember("stage", "response")
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Caching Expensive Operations
|
|
207
|
+
|
|
208
|
+
```ruby
|
|
209
|
+
tool :fetch_user do
|
|
210
|
+
handler do |user_id:, state:, **_|
|
|
211
|
+
cache_key = "cache:user:#{user_id}"
|
|
212
|
+
|
|
213
|
+
# Check cache
|
|
214
|
+
cached = state.memory.recall(cache_key)
|
|
215
|
+
return cached if cached
|
|
216
|
+
|
|
217
|
+
# Fetch and cache
|
|
218
|
+
user = User.find(user_id).to_h
|
|
219
|
+
state.memory.remember(cache_key, user)
|
|
220
|
+
user
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### User Session Data
|
|
226
|
+
|
|
227
|
+
```ruby
|
|
228
|
+
# Store session data
|
|
229
|
+
session = state.memory.scoped("session:#{session_id}")
|
|
230
|
+
session.remember("started_at", Time.now.iso8601)
|
|
231
|
+
session.remember("page_views", 0)
|
|
232
|
+
|
|
233
|
+
# Update during conversation
|
|
234
|
+
views = session.recall("page_views") || 0
|
|
235
|
+
session.remember("page_views", views + 1)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Memory vs State.data
|
|
239
|
+
|
|
240
|
+
| Feature | Memory | State.data |
|
|
241
|
+
|---------|--------|------------|
|
|
242
|
+
| Purpose | Robot-to-robot sharing | Input/output data |
|
|
243
|
+
| Scope | Namespaced | Flat hash |
|
|
244
|
+
| Typical Use | Intermediate results | User input, workflow config |
|
|
245
|
+
| Persistence | Within run | Can be serialized |
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
# Use state.data for input configuration
|
|
249
|
+
state = RobotLab.create_state(
|
|
250
|
+
message: "Process order",
|
|
251
|
+
data: { order_id: "123", priority: "high" }
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
# Use memory for intermediate findings
|
|
255
|
+
state.memory.remember("validation_result", { valid: true })
|
|
256
|
+
state.memory.remember("processing_steps", ["validated", "charged"])
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Best Practices
|
|
260
|
+
|
|
261
|
+
### 1. Use Descriptive Keys
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
# Good
|
|
265
|
+
state.memory.remember("classification:intent", "billing")
|
|
266
|
+
state.memory.remember("user:123:last_order_id", "ord_456")
|
|
267
|
+
|
|
268
|
+
# Bad
|
|
269
|
+
state.memory.remember("x", "billing")
|
|
270
|
+
state.memory.remember("temp1", "ord_456")
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### 2. Scope Related Data
|
|
274
|
+
|
|
275
|
+
```ruby
|
|
276
|
+
# Good
|
|
277
|
+
user = state.memory.scoped("user:#{user_id}")
|
|
278
|
+
user.remember("name", name)
|
|
279
|
+
user.remember("email", email)
|
|
280
|
+
user.remember("plan", plan)
|
|
281
|
+
|
|
282
|
+
# Less organized
|
|
283
|
+
state.memory.remember("user_name", name)
|
|
284
|
+
state.memory.remember("user_email", email)
|
|
285
|
+
state.memory.remember("user_plan", plan)
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### 3. Clean Up Temporary Data
|
|
289
|
+
|
|
290
|
+
```ruby
|
|
291
|
+
# At end of processing
|
|
292
|
+
state.memory.scoped("temp").clear
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### 4. Document Memory Keys
|
|
296
|
+
|
|
297
|
+
```ruby
|
|
298
|
+
# In your robot definitions, document expected keys
|
|
299
|
+
# Memory keys used:
|
|
300
|
+
# - SHARED:intent - Classification result
|
|
301
|
+
# - SHARED:entities - Extracted entities
|
|
302
|
+
# - user:{id}:* - User-specific data
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Next Steps
|
|
306
|
+
|
|
307
|
+
- [State Management](../architecture/state-management.md) - Full state details
|
|
308
|
+
- [Building Robots](building-robots.md) - Using memory in robots
|
|
309
|
+
- [API Reference: Memory](../api/core/memory.md) - Complete API
|