robot_lab 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +1 -1
- data/lib/robot_lab/mcp/transports/streamable_http.rb +1 -1
- data/lib/robot_lab/version.rb +1 -1
- metadata +3 -253
- data/.architecture/AGENTS.md +0 -32
- data/.architecture/config.yml +0 -8
- data/.architecture/members.yml +0 -60
- data/.architecture/reviews/feature-free-will.md +0 -490
- data/.architecture/reviews/overall-codebase.md +0 -427
- data/.claude/settings.local.json +0 -57
- data/.codex/config.toml +0 -2
- data/.envrc +0 -6
- data/.github/workflows/deploy-github-pages.yml +0 -52
- data/.irbrc +0 -6
- data/.loki +0 -65
- data/.quality/reek_baseline.txt +0 -43
- data/.rubocop.yml +0 -10
- data/Archspec.rb +0 -44
- data/CLAUDE.md +0 -141
- data/COMMITS.md +0 -196
- data/Rakefile +0 -193
- data/_typos.toml +0 -21
- data/agent2agent_review.md +0 -192
- data/agentf_improvements.md +0 -253
- data/agents.md +0 -14
- data/docs/api/core/index.md +0 -147
- data/docs/api/core/memory.md +0 -578
- data/docs/api/core/network.md +0 -553
- data/docs/api/core/result.md +0 -211
- data/docs/api/core/robot.md +0 -1667
- data/docs/api/core/state.md +0 -145
- data/docs/api/core/tool.md +0 -520
- data/docs/api/errors.md +0 -323
- data/docs/api/hooks.md +0 -469
- data/docs/api/index.md +0 -137
- data/docs/api/mcp/client.md +0 -387
- data/docs/api/mcp/index.md +0 -267
- data/docs/api/mcp/server.md +0 -252
- data/docs/api/mcp/transports.md +0 -339
- data/docs/api/messages/index.md +0 -126
- data/docs/api/messages/text-message.md +0 -170
- data/docs/api/messages/tool-call-message.md +0 -206
- data/docs/api/messages/tool-result-message.md +0 -242
- data/docs/api/messages/user-message.md +0 -232
- data/docs/api/skills.md +0 -423
- data/docs/api/streaming/context.md +0 -280
- data/docs/api/streaming/events.md +0 -185
- data/docs/api/streaming/index.md +0 -156
- data/docs/api/support.md +0 -846
- data/docs/architecture/core-concepts.md +0 -575
- data/docs/architecture/index.md +0 -252
- data/docs/architecture/message-flow.md +0 -366
- data/docs/architecture/network-orchestration.md +0 -478
- data/docs/architecture/robot-execution.md +0 -396
- data/docs/architecture/state-management.md +0 -410
- data/docs/assets/css/custom.css +0 -56
- data/docs/assets/images/robot_lab.jpg +0 -0
- data/docs/concepts.md +0 -641
- data/docs/examples/basic-chat.md +0 -258
- data/docs/examples/index.md +0 -425
- data/docs/examples/mcp-server.md +0 -403
- data/docs/examples/multi-robot-network.md +0 -476
- data/docs/examples/tool-usage.md +0 -468
- data/docs/getting-started/configuration.md +0 -645
- data/docs/getting-started/index.md +0 -56
- data/docs/getting-started/installation.md +0 -197
- data/docs/getting-started/quick-start.md +0 -293
- data/docs/guides/building-robots.md +0 -1187
- data/docs/guides/creating-networks.md +0 -736
- data/docs/guides/hooks.md +0 -1206
- data/docs/guides/index.md +0 -78
- data/docs/guides/knowledge.md +0 -219
- data/docs/guides/mcp-integration.md +0 -740
- data/docs/guides/memory.md +0 -458
- data/docs/guides/observability.md +0 -733
- data/docs/guides/streaming.md +0 -323
- data/docs/guides/using-tools.md +0 -780
- data/docs/index.md +0 -280
- data/docs/superpowers/plans/2026-05-06-agentskills.md +0 -1303
- data/docs/superpowers/specs/2026-05-06-agentskills-design.md +0 -247
- data/examples/.envrc +0 -3
- data/examples/01_simple_robot.rb +0 -31
- data/examples/02_tools.rb +0 -109
- data/examples/03_network.rb +0 -118
- data/examples/04_mcp.rb +0 -217
- data/examples/05_streaming.rb +0 -137
- data/examples/06_prompt_templates.rb +0 -325
- data/examples/07_network_memory.rb +0 -313
- data/examples/08_llm_config.rb +0 -162
- data/examples/09_chaining.rb +0 -251
- data/examples/10_memory.rb +0 -315
- data/examples/11_network_introspection.rb +0 -239
- data/examples/12_message_bus.rb +0 -67
- data/examples/13_spawn.rb +0 -85
- data/examples/14_rusty_circuit/.envrc +0 -1
- data/examples/14_rusty_circuit/.gitignore +0 -1
- data/examples/14_rusty_circuit/README.md +0 -1
- data/examples/14_rusty_circuit/comic.rb +0 -149
- data/examples/14_rusty_circuit/display.rb +0 -217
- data/examples/14_rusty_circuit/heckler.rb +0 -65
- data/examples/14_rusty_circuit/open_mic.rb +0 -132
- data/examples/14_rusty_circuit/prompts/open_mic_comic.md +0 -20
- data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +0 -23
- data/examples/14_rusty_circuit/prompts/open_mic_scout.md +0 -20
- data/examples/14_rusty_circuit/scout.rb +0 -165
- data/examples/14_rusty_circuit/show.log +0 -234
- data/examples/15_memory_network_and_bus/.envrc +0 -1
- data/examples/15_memory_network_and_bus/README.md +0 -66
- data/examples/15_memory_network_and_bus/editor_in_chief.rb +0 -24
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +0 -213
- data/examples/15_memory_network_and_bus/linux_writer.rb +0 -80
- data/examples/15_memory_network_and_bus/os_editor.rb +0 -48
- data/examples/15_memory_network_and_bus/os_writer.rb +0 -49
- data/examples/15_memory_network_and_bus/output/combined_article.md +0 -11
- data/examples/15_memory_network_and_bus/output/final_article.md +0 -8
- data/examples/15_memory_network_and_bus/output/linux_draft.md +0 -7
- data/examples/15_memory_network_and_bus/output/mac_draft.md +0 -7
- data/examples/15_memory_network_and_bus/output/memory.json +0 -13
- data/examples/15_memory_network_and_bus/output/revision_1.md +0 -29
- data/examples/15_memory_network_and_bus/output/revision_2.md +0 -50
- data/examples/15_memory_network_and_bus/output/revision_3.md +0 -8
- data/examples/15_memory_network_and_bus/output/windows_draft.md +0 -7
- data/examples/15_memory_network_and_bus/prompts/os_advocate.md +0 -13
- data/examples/15_memory_network_and_bus/prompts/os_chief.md +0 -13
- data/examples/15_memory_network_and_bus/prompts/os_editor.md +0 -13
- data/examples/16_writers_room/.envrc +0 -1
- data/examples/16_writers_room/display.rb +0 -158
- data/examples/16_writers_room/output/.gitignore +0 -4
- data/examples/16_writers_room/output/README.md +0 -69
- data/examples/16_writers_room/output/opus_001.md +0 -263
- data/examples/16_writers_room/output/opus_001_notes.log +0 -470
- data/examples/16_writers_room/output/opus_002.md +0 -245
- data/examples/16_writers_room/output/opus_002_notes.log +0 -546
- data/examples/16_writers_room/output/opus_002_screenplay.md +0 -7989
- data/examples/16_writers_room/output/opus_002_screenplay_notes.md +0 -993
- data/examples/16_writers_room/prompts/screenplay_writer.md +0 -66
- data/examples/16_writers_room/prompts/writer.md +0 -37
- data/examples/16_writers_room/room.rb +0 -186
- data/examples/16_writers_room/tools.rb +0 -173
- data/examples/16_writers_room/writer.rb +0 -121
- data/examples/16_writers_room/writers_room.rb +0 -256
- data/examples/17_skills.rb +0 -234
- data/examples/18_rails/.envrc +0 -3
- data/examples/18_rails/.gitignore +0 -5
- data/examples/18_rails/Gemfile +0 -11
- data/examples/18_rails/README.md +0 -67
- data/examples/18_rails/Rakefile +0 -4
- data/examples/18_rails/app/controllers/application_controller.rb +0 -4
- data/examples/18_rails/app/controllers/chat_controller.rb +0 -50
- data/examples/18_rails/app/jobs/application_job.rb +0 -4
- data/examples/18_rails/app/jobs/robot_run_job.rb +0 -25
- data/examples/18_rails/app/models/application_record.rb +0 -5
- data/examples/18_rails/app/models/robot_lab_result.rb +0 -36
- data/examples/18_rails/app/models/robot_lab_thread.rb +0 -23
- data/examples/18_rails/app/robots/chat_robot.rb +0 -25
- data/examples/18_rails/app/tools/time_tool.rb +0 -9
- data/examples/18_rails/app/views/chat/_user_message.html.erb +0 -1
- data/examples/18_rails/app/views/chat/index.html.erb +0 -67
- data/examples/18_rails/app/views/layouts/application.html.erb +0 -49
- data/examples/18_rails/bin/dev +0 -7
- data/examples/18_rails/bin/rails +0 -6
- data/examples/18_rails/bin/setup +0 -15
- data/examples/18_rails/config/application.rb +0 -33
- data/examples/18_rails/config/cable.yml +0 -2
- data/examples/18_rails/config/database.yml +0 -5
- data/examples/18_rails/config/environment.rb +0 -4
- data/examples/18_rails/config/initializers/robot_lab.rb +0 -11
- data/examples/18_rails/config/routes.rb +0 -6
- data/examples/18_rails/config.ru +0 -4
- data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +0 -32
- data/examples/19_token_tracking.rb +0 -138
- data/examples/20_circuit_breaker.rb +0 -147
- data/examples/21_learning_loop.rb +0 -181
- data/examples/22_context_compression.rb +0 -172
- data/examples/23_convergence.rb +0 -133
- data/examples/24_structured_delegation.rb +0 -151
- data/examples/25_history_search/conversation.jsonl +0 -30
- data/examples/25_history_search.rb +0 -133
- data/examples/26_document_store/api_versioning_adr.md +0 -52
- data/examples/26_document_store/incident_postmortem.md +0 -46
- data/examples/26_document_store/postgres_runbook.md +0 -49
- data/examples/26_document_store/redis_caching_guide.md +0 -48
- data/examples/26_document_store/sidekiq_guide.md +0 -51
- data/examples/26_document_store.rb +0 -140
- data/examples/27_incident_response/README.md +0 -65
- data/examples/27_incident_response/incident_response.rb +0 -261
- data/examples/28_mcp_discovery.rb +0 -113
- data/examples/29_ractor_tools.rb +0 -240
- data/examples/30_ractor_network.rb +0 -252
- data/examples/31_launch_assessment.rb +0 -246
- data/examples/32_newsletter_reader.rb +0 -193
- data/examples/33_stock_generator.rb +0 -80
- data/examples/33_stock_predictor.rb +0 -327
- data/examples/34_agentskills.rb +0 -76
- data/examples/35_hooks.rb +0 -266
- data/examples/README.md +0 -550
- data/examples/common.rb +0 -180
- data/examples/prompts/assistant.md +0 -5
- data/examples/prompts/audit_trail.md +0 -8
- data/examples/prompts/billing.md +0 -10
- data/examples/prompts/classifier.md +0 -7
- data/examples/prompts/comedian.md +0 -6
- data/examples/prompts/comedy_critic.md +0 -10
- data/examples/prompts/configurable.md +0 -9
- data/examples/prompts/dispatcher.md +0 -12
- data/examples/prompts/entity_extractor.md +0 -14
- data/examples/prompts/escalation.md +0 -42
- data/examples/prompts/frontmatter_mcp_test.md +0 -9
- data/examples/prompts/frontmatter_named_test.md +0 -5
- data/examples/prompts/frontmatter_tools_test.md +0 -6
- data/examples/prompts/general.md +0 -7
- data/examples/prompts/github_assistant.md +0 -14
- data/examples/prompts/helper.md +0 -4
- data/examples/prompts/incident_responder.md +0 -18
- data/examples/prompts/keyword_extractor.md +0 -11
- data/examples/prompts/llm_config_demo.md +0 -20
- data/examples/prompts/order_support.md +0 -35
- data/examples/prompts/os_advocate.md +0 -13
- data/examples/prompts/os_chief.md +0 -13
- data/examples/prompts/os_editor.md +0 -13
- data/examples/prompts/parameterized_main_test.md +0 -6
- data/examples/prompts/pii_redactor.md +0 -7
- data/examples/prompts/product_support.md +0 -37
- data/examples/prompts/runbook_protocol.md +0 -12
- data/examples/prompts/sentiment_analyzer.md +0 -12
- data/examples/prompts/skill_a_test.md +0 -4
- data/examples/prompts/skill_b_test.md +0 -4
- data/examples/prompts/skill_config_test.md +0 -5
- data/examples/prompts/skill_cycle_a_test.md +0 -6
- data/examples/prompts/skill_cycle_b_test.md +0 -6
- data/examples/prompts/skill_description_test.md +0 -4
- data/examples/prompts/skill_leaf_test.md +0 -4
- data/examples/prompts/skill_nested_test.md +0 -6
- data/examples/prompts/skill_refs_main_test.md +0 -6
- data/examples/prompts/skill_self_ref_test.md +0 -6
- data/examples/prompts/skill_with_mcp_test.md +0 -9
- data/examples/prompts/skill_with_params_test.md +0 -6
- data/examples/prompts/skill_with_robot_name_test.md +0 -5
- data/examples/prompts/skill_with_tools_test.md +0 -6
- data/examples/prompts/sre_compliance.md +0 -10
- data/examples/prompts/structured_output.md +0 -7
- data/examples/prompts/synthesizer.md +0 -17
- data/examples/prompts/technical.md +0 -10
- data/examples/prompts/template_with_skills_test.md +0 -6
- data/examples/prompts/triage.md +0 -22
- data/examples/ruboruby.md +0 -423
- data/examples/run_all.rb +0 -60
- data/examples/xyzzy.rb +0 -97
- data/logfile +0 -8
- data/mkdocs.yml +0 -209
- data/robot_concurrency.md +0 -38
- data/simple_acp_review.md +0 -298
- data/temp.md +0 -6
- data/tool_manifest_plan.md +0 -155
data/docs/guides/memory.md
DELETED
|
@@ -1,458 +0,0 @@
|
|
|
1
|
-
# Memory System
|
|
2
|
-
|
|
3
|
-
The memory system provides key-value storage for robots, supporting both standalone and network execution modes.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
Memory is a reactive key-value store that provides:
|
|
8
|
-
|
|
9
|
-
- Key-value storage with `[]` and `[]=` accessors
|
|
10
|
-
- Reserved keys for structured data (`:data`, `:results`, `:messages`, `:session_id`, `:cache`)
|
|
11
|
-
- Reactive subscriptions and blocking reads for inter-robot communication
|
|
12
|
-
- Optional Redis backend for persistence
|
|
13
|
-
- Semantic caching via `RubyLLM::SemanticCache` (optional ruby_llm-semantic_cache gem)
|
|
14
|
-
|
|
15
|
-
## Standalone Robot Memory
|
|
16
|
-
|
|
17
|
-
Every robot has its own inherent memory that persists across runs:
|
|
18
|
-
|
|
19
|
-
```ruby
|
|
20
|
-
robot = RobotLab.build(
|
|
21
|
-
name: "assistant",
|
|
22
|
-
system_prompt: "You are helpful."
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
# Memory persists across runs
|
|
26
|
-
robot.memory[:user_name] = "Alice"
|
|
27
|
-
robot.memory[:preferences] = { theme: "dark", language: "en" }
|
|
28
|
-
|
|
29
|
-
result = robot.run("Hello!")
|
|
30
|
-
|
|
31
|
-
# Read it back later
|
|
32
|
-
robot.memory[:user_name] # => "Alice"
|
|
33
|
-
robot.memory[:preferences] # => { theme: "dark", language: "en" }
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Basic Operations
|
|
37
|
-
|
|
38
|
-
### Store Values
|
|
39
|
-
|
|
40
|
-
```ruby
|
|
41
|
-
robot.memory[:key] = "value"
|
|
42
|
-
robot.memory[:count] = 42
|
|
43
|
-
robot.memory[:config] = { timeout: 30, retries: 3 }
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### Retrieve Values
|
|
47
|
-
|
|
48
|
-
```ruby
|
|
49
|
-
name = robot.memory[:user_name] # => "Alice"
|
|
50
|
-
missing = robot.memory[:unknown] # => nil
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### Check Existence
|
|
54
|
-
|
|
55
|
-
```ruby
|
|
56
|
-
robot.memory.key?(:user_name) # => true
|
|
57
|
-
robot.memory.key?(:unknown) # => false
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### Delete Values
|
|
61
|
-
|
|
62
|
-
```ruby
|
|
63
|
-
robot.memory.delete(:temp_data)
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### List Keys
|
|
67
|
-
|
|
68
|
-
```ruby
|
|
69
|
-
robot.memory.keys # => [:user_name, :preferences] (excludes reserved keys)
|
|
70
|
-
robot.memory.all_keys # => [:data, :results, :messages, :session_id, :cache, :user_name, ...]
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### Merge Values
|
|
74
|
-
|
|
75
|
-
```ruby
|
|
76
|
-
robot.memory.merge!(user_id: 123, session: "abc")
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## Reserved Keys
|
|
80
|
-
|
|
81
|
-
Memory has reserved keys with special behavior:
|
|
82
|
-
|
|
83
|
-
| Key | Type | Description |
|
|
84
|
-
|-----|------|-------------|
|
|
85
|
-
| `:data` | Hash (StateProxy) | Runtime data with method-style access |
|
|
86
|
-
| `:results` | Array | Accumulated robot results |
|
|
87
|
-
| `:messages` | Array | Conversation history |
|
|
88
|
-
| `:session_id` | String | Session identifier for history persistence |
|
|
89
|
-
| `:cache` | `RubyLLM::SemanticCache` module, or `nil` | Semantic cache (read-only after init). Set at construction time; `nil` when built with `enable_cache: false` or when the optional gem is absent |
|
|
90
|
-
|
|
91
|
-
### The Data Hash
|
|
92
|
-
|
|
93
|
-
The `:data` key provides a `StateProxy` for method-style access:
|
|
94
|
-
|
|
95
|
-
```ruby
|
|
96
|
-
robot.memory.data[:category] = "billing"
|
|
97
|
-
robot.memory.data.category # => "billing" (method-style access)
|
|
98
|
-
robot.memory.data.to_h # => { category: "billing" }
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### Results and Messages
|
|
102
|
-
|
|
103
|
-
```ruby
|
|
104
|
-
robot.memory.results # => Array of RobotResult objects
|
|
105
|
-
robot.memory.messages # => Array of Message objects
|
|
106
|
-
robot.memory.session_id # => "abc123" or nil
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
## Runtime Memory Injection
|
|
110
|
-
|
|
111
|
-
Pass memory values for a single run using the `memory:` keyword:
|
|
112
|
-
|
|
113
|
-
```ruby
|
|
114
|
-
# Inject a hash -- values are merged into the active memory
|
|
115
|
-
result = robot.run("What's my order status?", memory: { user_id: 123, order_id: "ORD-456" })
|
|
116
|
-
|
|
117
|
-
# The robot's memory now contains those keys
|
|
118
|
-
robot.memory[:user_id] # => 123
|
|
119
|
-
robot.memory[:order_id] # => "ORD-456"
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
You can also pass a full `Memory` object to replace the active memory for that run:
|
|
123
|
-
|
|
124
|
-
```ruby
|
|
125
|
-
custom_memory = RobotLab.create_memory(data: { user_id: 123 })
|
|
126
|
-
custom_memory[:context] = "billing inquiry"
|
|
127
|
-
|
|
128
|
-
result = robot.run("Help me", memory: custom_memory)
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
## Resetting Memory
|
|
132
|
-
|
|
133
|
-
Clear a robot's memory back to its initial state:
|
|
134
|
-
|
|
135
|
-
```ruby
|
|
136
|
-
robot.reset_memory
|
|
137
|
-
robot.memory.keys # => [] (custom keys cleared, reserved keys reset)
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
You can also clear just the custom keys without resetting reserved keys:
|
|
141
|
-
|
|
142
|
-
```ruby
|
|
143
|
-
robot.memory.clear # Clears non-reserved keys only
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
> [!WARNING]
|
|
147
|
-
> `reset_memory` resets **only the key-value store**. It does not touch the
|
|
148
|
-
> robot's conversation history — the chat still holds every prior message, and
|
|
149
|
-
> the LLM will still see them. Clearing the transcript is a separate call:
|
|
150
|
-
>
|
|
151
|
-
> ```ruby
|
|
152
|
-
> robot.reset_memory # key-value store only
|
|
153
|
-
> robot.clear_messages(keep_system: true) # conversation history only
|
|
154
|
-
> ```
|
|
155
|
-
|
|
156
|
-
## Network Shared Memory
|
|
157
|
-
|
|
158
|
-
When robots run in a network, they share the network's memory instead of using their own inherent memory. This allows robots to communicate through shared state:
|
|
159
|
-
|
|
160
|
-
```ruby
|
|
161
|
-
network = RobotLab.create_network(name: "pipeline") do
|
|
162
|
-
task :analyzer, analyzer_robot, depends_on: :none
|
|
163
|
-
task :writer, writer_robot, depends_on: [:analyzer]
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
# All robots in the network share this memory
|
|
167
|
-
network.memory[:project] = "quarterly_report"
|
|
168
|
-
|
|
169
|
-
result = network.run(message: "Analyze sales data")
|
|
170
|
-
|
|
171
|
-
# After the run, shared memory contains values written by all robots
|
|
172
|
-
network.memory[:analysis_result] # Written by analyzer
|
|
173
|
-
network.memory[:draft] # Written by writer
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### Resetting Network Memory
|
|
177
|
-
|
|
178
|
-
```ruby
|
|
179
|
-
network.reset_memory # Clear shared memory between runs
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
## Reactive Memory
|
|
183
|
-
|
|
184
|
-
Memory supports reactive features for concurrent robot execution.
|
|
185
|
-
|
|
186
|
-
### Blocking Reads
|
|
187
|
-
|
|
188
|
-
Wait for a value to become available (useful in parallel pipelines):
|
|
189
|
-
|
|
190
|
-
```ruby
|
|
191
|
-
# In robot A (writer)
|
|
192
|
-
memory.set(:sentiment, { score: 0.8, confidence: 0.95 })
|
|
193
|
-
|
|
194
|
-
# In robot B (reader, may run concurrently)
|
|
195
|
-
result = memory.get(:sentiment, wait: true) # Blocks until available
|
|
196
|
-
result = memory.get(:sentiment, wait: 30) # Blocks up to 30 seconds
|
|
197
|
-
|
|
198
|
-
# Multiple keys
|
|
199
|
-
results = memory.get(:sentiment, :entities, :keywords, wait: 60)
|
|
200
|
-
# => { sentiment: {...}, entities: [...], keywords: [...] }
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
> [!WARNING]
|
|
204
|
-
> A blocking `get` that expires **raises `RobotLab::AwaitTimeout`** — it does
|
|
205
|
-
> not return `nil`. (A *non*-blocking `get`, the default, returns `nil` for a
|
|
206
|
-
> missing key.) Wrap it if a missing value is survivable:
|
|
207
|
-
>
|
|
208
|
-
> ```ruby
|
|
209
|
-
> begin
|
|
210
|
-
> memory.get(:sentiment, wait: 30)
|
|
211
|
-
> rescue RobotLab::AwaitTimeout => e
|
|
212
|
-
> # => "Timeout waiting for :sentiment after 30 seconds"
|
|
213
|
-
> nil
|
|
214
|
-
> end
|
|
215
|
-
> ```
|
|
216
|
-
|
|
217
|
-
> [!CAUTION]
|
|
218
|
-
> With multiple keys the timeout is applied **per missing key**, not to the call
|
|
219
|
-
> as a whole. `memory.get(:a, :b, :c, wait: 30)` awaits the missing keys
|
|
220
|
-
> sequentially, each with its own fresh 30-second budget.
|
|
221
|
-
>
|
|
222
|
-
> It does not, however, spend the whole 90 seconds before reporting: the first
|
|
223
|
-
> key whose wait expires **raises `AwaitTimeout` immediately**, aborting the call
|
|
224
|
-
> — so the keys that were already resolved are lost along with the ones not yet
|
|
225
|
-
> attempted. The 90 seconds is the worst case only for a call that *succeeds*
|
|
226
|
-
> (each key arriving just under its own deadline).
|
|
227
|
-
|
|
228
|
-
Each blocking wait is backed by an `IO.pipe` pair (`Waiter` class). The waiting
|
|
229
|
-
side calls `@read_io.wait_readable(timeout)`; `signal` writes one byte per
|
|
230
|
-
waiting caller so every blocked waiter wakes exactly once. `wait_readable`
|
|
231
|
-
yields to Ruby's Async fiber scheduler when one is installed — no mutex
|
|
232
|
-
contention or spurious wakeups.
|
|
233
|
-
|
|
234
|
-
### Subscriptions
|
|
235
|
-
|
|
236
|
-
Subscribe to key changes:
|
|
237
|
-
|
|
238
|
-
```ruby
|
|
239
|
-
# Subscribe to a single key
|
|
240
|
-
memory.subscribe(:raw_data) do |change|
|
|
241
|
-
puts "#{change.key} changed by #{change.writer}"
|
|
242
|
-
puts "Old: #{change.previous}, New: #{change.value}"
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
# Subscribe to multiple keys
|
|
246
|
-
memory.subscribe(:sentiment, :entities) do |change|
|
|
247
|
-
update_dashboard(change.key, change.value)
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
# Pattern subscriptions (glob-style)
|
|
251
|
-
memory.subscribe_pattern("analysis:*") do |change|
|
|
252
|
-
puts "Analysis key #{change.key} updated"
|
|
253
|
-
end
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
> [!IMPORTANT]
|
|
257
|
-
> Subscription callbacks are dispatched through `Async { }`. Inside a running
|
|
258
|
-
> Async reactor that defers them; **outside one — which is the normal case for
|
|
259
|
-
> plain Ruby, Rails request threads, and tests — the block runs synchronously
|
|
260
|
-
> on the writer's thread**, before `memory[:key] = value` returns:
|
|
261
|
-
>
|
|
262
|
-
> ```ruby
|
|
263
|
-
> order = []
|
|
264
|
-
> memory.subscribe(:k) { |c| order << "callback" }
|
|
265
|
-
> order << "before-set"
|
|
266
|
-
> memory[:k] = 1
|
|
267
|
-
> order << "after-set"
|
|
268
|
-
> order # => ["before-set", "callback", "after-set"]
|
|
269
|
-
> ```
|
|
270
|
-
>
|
|
271
|
-
> Keep subscription callbacks fast, and never assume the writer has moved on by
|
|
272
|
-
> the time your callback runs.
|
|
273
|
-
|
|
274
|
-
### Unsubscribe
|
|
275
|
-
|
|
276
|
-
```ruby
|
|
277
|
-
sub_id = memory.subscribe(:status) { |c| puts c.value }
|
|
278
|
-
memory.unsubscribe(sub_id)
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
## Creating Standalone Memory
|
|
282
|
-
|
|
283
|
-
Use the factory method for standalone memory objects:
|
|
284
|
-
|
|
285
|
-
```ruby
|
|
286
|
-
memory = RobotLab.create_memory(
|
|
287
|
-
data: { user_id: 123, category: nil },
|
|
288
|
-
enable_cache: true
|
|
289
|
-
)
|
|
290
|
-
|
|
291
|
-
memory[:session_id] = "abc123"
|
|
292
|
-
memory[:custom_key] = "custom_value"
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
## Serialization
|
|
296
|
-
|
|
297
|
-
Memory can be exported and reconstructed:
|
|
298
|
-
|
|
299
|
-
```ruby
|
|
300
|
-
# Export to hash
|
|
301
|
-
hash = robot.memory.to_h
|
|
302
|
-
# => { data: {...}, results: [...], messages: [...], session_id: "...", custom: {...} }
|
|
303
|
-
# to_h is compacted: nil entries are dropped, so an unset :session_id (and the
|
|
304
|
-
# :cache key, which is never exported) simply will not appear.
|
|
305
|
-
|
|
306
|
-
# Export to JSON
|
|
307
|
-
json = robot.memory.to_json
|
|
308
|
-
|
|
309
|
-
# Reconstruct from hash
|
|
310
|
-
restored = RobotLab::Memory.from_hash(hash)
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
## Patterns
|
|
314
|
-
|
|
315
|
-
### Accumulating Data Across Robots
|
|
316
|
-
|
|
317
|
-
```ruby
|
|
318
|
-
# In each robot's processing
|
|
319
|
-
def accumulate_finding(memory, finding)
|
|
320
|
-
findings = memory[:findings] || []
|
|
321
|
-
findings << finding
|
|
322
|
-
memory[:findings] = findings
|
|
323
|
-
end
|
|
324
|
-
|
|
325
|
-
# In the final robot
|
|
326
|
-
all_findings = memory[:findings]
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
### Tracking Progress
|
|
330
|
-
|
|
331
|
-
```ruby
|
|
332
|
-
memory[:stage] = "intake"
|
|
333
|
-
# ... processing ...
|
|
334
|
-
memory[:stage] = "analysis"
|
|
335
|
-
# ... processing ...
|
|
336
|
-
memory[:stage] = "response"
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
### Caching Expensive Operations
|
|
340
|
-
|
|
341
|
-
A tool reaches memory through its owning robot. Subclass `RobotLab::Tool` (which
|
|
342
|
-
has a `robot` accessor) and attach an **instance constructed with `robot:`** —
|
|
343
|
-
that is the only supported way for tool code to read and write robot memory:
|
|
344
|
-
|
|
345
|
-
```ruby
|
|
346
|
-
class FetchUser < RobotLab::Tool
|
|
347
|
-
description "Fetch user details by ID"
|
|
348
|
-
param :user_id, type: :string, desc: "User ID"
|
|
349
|
-
|
|
350
|
-
def execute(user_id:)
|
|
351
|
-
cache_key = :"cache:user:#{user_id}"
|
|
352
|
-
|
|
353
|
-
cached = robot&.memory&.[](cache_key)
|
|
354
|
-
return cached if cached
|
|
355
|
-
|
|
356
|
-
user = User.find(user_id).to_h
|
|
357
|
-
robot&.memory&.[]=(cache_key, user)
|
|
358
|
-
user
|
|
359
|
-
end
|
|
360
|
-
end
|
|
361
|
-
|
|
362
|
-
robot = RobotLab.build(name: "support", system_prompt: "...")
|
|
363
|
-
robot.local_tools << FetchUser.new(robot: robot)
|
|
364
|
-
|
|
365
|
-
robot.run("Look up user 42", tools: :inherit)
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
> [!WARNING]
|
|
369
|
-
> There is **no thread-local memory handle** in RobotLab — nothing anywhere in
|
|
370
|
-
> the codebase ever assigns `Thread.current[:robot_memory]`. A tool that reads
|
|
371
|
-
> it will always see `nil` and silently cache nothing. Go through `robot.memory`
|
|
372
|
-
> as above, and remember that `FetchUser.new` without `robot:` leaves `robot`
|
|
373
|
-
> `nil`.
|
|
374
|
-
|
|
375
|
-
### Semantic Caching
|
|
376
|
-
|
|
377
|
-
Memory exposes a semantic cache for LLM response caching. It is on by default
|
|
378
|
-
and becomes `nil` when you opt out with `enable_cache: false`:
|
|
379
|
-
|
|
380
|
-
```ruby
|
|
381
|
-
RobotLab.create_memory.cache # => RubyLLM::SemanticCache
|
|
382
|
-
RobotLab.create_memory(enable_cache: false).cache # => nil
|
|
383
|
-
|
|
384
|
-
RobotLab.build(name: "x", system_prompt: "…").memory.cache
|
|
385
|
-
# => RubyLLM::SemanticCache
|
|
386
|
-
RobotLab.build(name: "x", system_prompt: "…", enable_cache: false).memory.cache
|
|
387
|
-
# => nil
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
Guard for `nil` in any code that might run against a cache-disabled memory.
|
|
391
|
-
|
|
392
|
-
```ruby
|
|
393
|
-
# Access the semantic cache
|
|
394
|
-
cache = robot.memory.cache
|
|
395
|
-
|
|
396
|
-
# Use it to cache semantically similar queries
|
|
397
|
-
response = cache.fetch("What is Ruby?") do
|
|
398
|
-
robot.run("What is Ruby?")
|
|
399
|
-
end
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
> [!NOTE]
|
|
403
|
-
> `memory.cache` is the `RubyLLM::SemanticCache` **module itself**, not a
|
|
404
|
-
> per-memory instance. Its cache store, vector store, and configuration are
|
|
405
|
-
> process-global — two memories with `enable_cache: true` share one cache. It
|
|
406
|
-
> also embeds every query, so `fetch` costs an embedding call.
|
|
407
|
-
|
|
408
|
-
## Best Practices
|
|
409
|
-
|
|
410
|
-
### 1. Use Descriptive Keys
|
|
411
|
-
|
|
412
|
-
```ruby
|
|
413
|
-
# Good
|
|
414
|
-
robot.memory[:classification_intent] = "billing"
|
|
415
|
-
robot.memory[:user_last_order_id] = "ord_456"
|
|
416
|
-
|
|
417
|
-
# Bad
|
|
418
|
-
robot.memory[:x] = "billing"
|
|
419
|
-
robot.memory[:temp1] = "ord_456"
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
### 2. Use Data Hash for Structured Runtime Input
|
|
423
|
-
|
|
424
|
-
```ruby
|
|
425
|
-
memory = RobotLab.create_memory(
|
|
426
|
-
data: { order_id: "123", priority: "high", customer_tier: "gold" }
|
|
427
|
-
)
|
|
428
|
-
|
|
429
|
-
# Access via data proxy
|
|
430
|
-
memory.data.order_id # => "123"
|
|
431
|
-
memory.data.priority # => "high"
|
|
432
|
-
memory.data.customer_tier # => "gold"
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
### 3. Clean Up Temporary Values
|
|
436
|
-
|
|
437
|
-
```ruby
|
|
438
|
-
# After processing is done
|
|
439
|
-
robot.memory.delete(:temp_calculation)
|
|
440
|
-
robot.memory.delete(:intermediate_result)
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
### 4. Document Memory Keys
|
|
444
|
-
|
|
445
|
-
```ruby
|
|
446
|
-
# In your robot definitions, document expected keys:
|
|
447
|
-
#
|
|
448
|
-
# Memory keys used by this pipeline:
|
|
449
|
-
# - :intent - Classification result (set by classifier)
|
|
450
|
-
# - :entities - Extracted entities (set by entity_extractor)
|
|
451
|
-
# - :response - Final response draft (set by responder)
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
## Next Steps
|
|
455
|
-
|
|
456
|
-
- [Building Robots](building-robots.md) - Using memory in robots
|
|
457
|
-
- [Creating Networks](creating-networks.md) - Shared memory in networks
|
|
458
|
-
- [API Reference: Memory](../api/core/memory.md) - Complete API
|