htm 0.0.20 → 0.0.30
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 +60 -0
- data/Rakefile +104 -18
- data/db/migrate/00001_enable_extensions.rb +9 -5
- data/db/migrate/00002_create_robots.rb +18 -6
- data/db/migrate/00003_create_file_sources.rb +30 -17
- data/db/migrate/00004_create_nodes.rb +60 -48
- data/db/migrate/00005_create_tags.rb +24 -12
- data/db/migrate/00006_create_node_tags.rb +28 -13
- data/db/migrate/00007_create_robot_nodes.rb +40 -26
- data/db/schema.sql +17 -1
- data/db/seeds.rb +33 -33
- data/docs/database/naming-convention.md +244 -0
- data/docs/database_rake_tasks.md +31 -0
- data/docs/development/rake-tasks.md +80 -35
- data/docs/guides/mcp-server.md +70 -1
- data/examples/.envrc +6 -0
- data/examples/.gitignore +2 -0
- data/examples/00_create_examples_db.rb +94 -0
- data/examples/{basic_usage.rb → 01_basic_usage.rb} +12 -16
- data/examples/{custom_llm_configuration.rb → 03_custom_llm_configuration.rb} +13 -3
- data/examples/{file_loader_usage.rb → 04_file_loader_usage.rb} +11 -14
- data/examples/{timeframe_demo.rb → 05_timeframe_demo.rb} +10 -3
- data/examples/{example_app → 06_example_app}/app.rb +15 -15
- data/examples/{cli_app → 07_cli_app}/htm_cli.rb +15 -22
- data/examples/08_sinatra_app/Gemfile.lock +241 -0
- data/examples/{sinatra_app → 08_sinatra_app}/app.rb +19 -18
- data/examples/{mcp_client.rb → 09_mcp_client.rb} +5 -8
- data/examples/{telemetry → 10_telemetry}/SETUP_README.md +1 -1
- data/examples/{telemetry → 10_telemetry}/demo.rb +14 -10
- data/examples/11_robot_groups/README.md +335 -0
- data/examples/{robot_groups → 11_robot_groups/lib}/robot_worker.rb +17 -3
- data/examples/{robot_groups → 11_robot_groups}/multi_process.rb +9 -9
- data/examples/{robot_groups → 11_robot_groups}/same_process.rb +9 -12
- data/examples/{rails_app → 12_rails_app}/Gemfile +3 -0
- data/examples/{rails_app → 12_rails_app}/Gemfile.lock +87 -58
- data/examples/{rails_app → 12_rails_app}/app/controllers/dashboard_controller.rb +10 -6
- data/examples/{rails_app → 12_rails_app}/app/controllers/files_controller.rb +5 -5
- data/examples/{rails_app → 12_rails_app}/app/controllers/memories_controller.rb +11 -7
- data/examples/{rails_app → 12_rails_app}/app/controllers/robots_controller.rb +8 -8
- data/examples/12_rails_app/app/controllers/tags_controller.rb +36 -0
- data/examples/{rails_app → 12_rails_app}/app/views/dashboard/index.html.erb +2 -2
- data/examples/{rails_app → 12_rails_app}/app/views/files/new.html.erb +5 -2
- data/examples/{rails_app → 12_rails_app}/app/views/memories/_memory_card.html.erb +3 -3
- data/examples/{rails_app → 12_rails_app}/app/views/memories/deleted.html.erb +3 -3
- data/examples/{rails_app → 12_rails_app}/app/views/memories/edit.html.erb +3 -3
- data/examples/{rails_app → 12_rails_app}/app/views/memories/show.html.erb +4 -4
- data/examples/{rails_app → 12_rails_app}/app/views/robots/index.html.erb +2 -2
- data/examples/{rails_app → 12_rails_app}/app/views/robots/show.html.erb +4 -4
- data/examples/{rails_app → 12_rails_app}/app/views/search/index.html.erb +1 -1
- data/examples/{rails_app → 12_rails_app}/app/views/tags/index.html.erb +2 -2
- data/examples/{rails_app → 12_rails_app}/app/views/tags/show.html.erb +1 -1
- data/examples/12_rails_app/config/initializers/htm.rb +7 -0
- data/examples/12_rails_app/config/initializers/rack.rb +5 -0
- data/examples/README.md +230 -211
- data/examples/examples_helper.rb +138 -0
- data/lib/htm/config/builder.rb +167 -0
- data/lib/htm/config/database.rb +317 -0
- data/lib/htm/config/defaults.yml +37 -9
- data/lib/htm/config/section.rb +74 -0
- data/lib/htm/config/validator.rb +83 -0
- data/lib/htm/config.rb +64 -360
- data/lib/htm/database.rb +85 -127
- data/lib/htm/errors.rb +14 -0
- data/lib/htm/integrations/sinatra.rb +13 -44
- data/lib/htm/jobs/generate_embedding_job.rb +3 -4
- data/lib/htm/jobs/generate_propositions_job.rb +4 -5
- data/lib/htm/jobs/generate_tags_job.rb +16 -15
- data/lib/htm/loaders/defaults_loader.rb +23 -0
- data/lib/htm/loaders/markdown_loader.rb +17 -15
- data/lib/htm/loaders/xdg_config_loader.rb +9 -9
- data/lib/htm/long_term_memory/fulltext_search.rb +14 -14
- data/lib/htm/long_term_memory/hybrid_search.rb +396 -229
- data/lib/htm/long_term_memory/node_operations.rb +24 -23
- data/lib/htm/long_term_memory/relevance_scorer.rb +23 -20
- data/lib/htm/long_term_memory/robot_operations.rb +4 -4
- data/lib/htm/long_term_memory/tag_operations.rb +91 -77
- data/lib/htm/long_term_memory/vector_search.rb +4 -5
- data/lib/htm/long_term_memory.rb +13 -13
- data/lib/htm/mcp/cli.rb +115 -8
- data/lib/htm/mcp/resources.rb +4 -3
- data/lib/htm/mcp/server.rb +5 -4
- data/lib/htm/mcp/tools.rb +37 -28
- data/lib/htm/migration.rb +72 -0
- data/lib/htm/models/file_source.rb +52 -31
- data/lib/htm/models/node.rb +224 -108
- data/lib/htm/models/node_tag.rb +49 -28
- data/lib/htm/models/robot.rb +38 -27
- data/lib/htm/models/robot_node.rb +63 -35
- data/lib/htm/models/tag.rb +126 -123
- data/lib/htm/observability.rb +45 -41
- data/lib/htm/proposition_service.rb +76 -7
- data/lib/htm/railtie.rb +2 -2
- data/lib/htm/robot_group.rb +30 -18
- data/lib/htm/sequel_config.rb +215 -0
- data/lib/htm/sql_builder.rb +14 -16
- data/lib/htm/tag_service.rb +78 -0
- data/lib/htm/tasks.rb +3 -0
- data/lib/htm/version.rb +1 -1
- data/lib/htm/workflows/remember_workflow.rb +6 -5
- data/lib/htm.rb +26 -22
- data/lib/tasks/db.rake +0 -2
- data/lib/tasks/doc.rake +2 -2
- data/lib/tasks/files.rake +11 -18
- data/lib/tasks/htm.rake +190 -62
- data/lib/tasks/jobs.rake +179 -54
- data/lib/tasks/tags.rake +8 -13
- data/scripts/backfill_parent_tags.rb +376 -0
- data/scripts/normalize_plural_tags.rb +335 -0
- metadata +109 -80
- data/examples/rails_app/app/controllers/tags_controller.rb +0 -30
- data/examples/sinatra_app/Gemfile.lock +0 -166
- data/lib/htm/active_record_config.rb +0 -104
- /data/examples/{config_file_example → 02_config_file_example}/README.md +0 -0
- /data/examples/{config_file_example → 02_config_file_example}/config/htm.local.yml +0 -0
- /data/examples/{config_file_example → 02_config_file_example}/custom_config.yml +0 -0
- /data/examples/{config_file_example → 02_config_file_example}/show_config.rb +0 -0
- /data/examples/{example_app → 06_example_app}/Rakefile +0 -0
- /data/examples/{cli_app → 07_cli_app}/README.md +0 -0
- /data/examples/{sinatra_app → 08_sinatra_app}/Gemfile +0 -0
- /data/examples/{telemetry → 10_telemetry}/README.md +0 -0
- /data/examples/{telemetry → 10_telemetry}/grafana/dashboards/htm-metrics.json +0 -0
- /data/examples/{rails_app → 12_rails_app}/.gitignore +0 -0
- /data/examples/{rails_app → 12_rails_app}/Procfile.dev +0 -0
- /data/examples/{rails_app → 12_rails_app}/README.md +0 -0
- /data/examples/{rails_app → 12_rails_app}/Rakefile +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/assets/stylesheets/application.css +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/assets/stylesheets/inter-font.css +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/controllers/application_controller.rb +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/controllers/search_controller.rb +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/javascript/application.js +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/javascript/controllers/application.js +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/javascript/controllers/index.js +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/views/files/index.html.erb +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/views/files/show.html.erb +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/views/layouts/application.html.erb +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/views/memories/index.html.erb +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/views/memories/new.html.erb +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/views/robots/new.html.erb +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/views/shared/_navbar.html.erb +0 -0
- /data/examples/{rails_app → 12_rails_app}/app/views/shared/_stat_card.html.erb +0 -0
- /data/examples/{rails_app → 12_rails_app}/bin/dev +0 -0
- /data/examples/{rails_app → 12_rails_app}/bin/rails +0 -0
- /data/examples/{rails_app → 12_rails_app}/bin/rake +0 -0
- /data/examples/{rails_app → 12_rails_app}/config/application.rb +0 -0
- /data/examples/{rails_app → 12_rails_app}/config/boot.rb +0 -0
- /data/examples/{rails_app → 12_rails_app}/config/database.yml +0 -0
- /data/examples/{rails_app → 12_rails_app}/config/environment.rb +0 -0
- /data/examples/{rails_app → 12_rails_app}/config/importmap.rb +0 -0
- /data/examples/{rails_app → 12_rails_app}/config/routes.rb +0 -0
- /data/examples/{rails_app → 12_rails_app}/config/tailwind.config.js +0 -0
- /data/examples/{rails_app → 12_rails_app}/config.ru +0 -0
- /data/examples/{rails_app → 12_rails_app}/log/.keep +0 -0
- /data/examples/{rails_app → 12_rails_app}/tmp/local_secret.txt +0 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# Robot Groups - Multi-Robot Coordination with Shared Working Memory
|
|
2
|
+
|
|
3
|
+
This example demonstrates high-availability patterns for coordinating multiple HTM robots with shared working memory, automatic failover, and real-time synchronization.
|
|
4
|
+
|
|
5
|
+
There are two scenarios demonstrated. 1) multiple robots in their processes. These processes could be on the same computer are not. The only requirement is that all robot processes have access to the same HTM database.
|
|
6
|
+
|
|
7
|
+
The second scenario is all robots are running within the same process.
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
Robot Groups enable multiple AI agents (robots) to share context and collaborate on tasks. Key capabilities:
|
|
12
|
+
|
|
13
|
+
- **Shared Working Memory**: Multiple robots access the same context simultaneously
|
|
14
|
+
- **Active/Passive Roles**: Active robots handle requests; passive robots maintain warm standby
|
|
15
|
+
- **Instant Failover**: When an active robot fails, a passive robot takes over with full context
|
|
16
|
+
- **Real-time Sync**: PostgreSQL LISTEN/NOTIFY propagates changes across all robots instantly
|
|
17
|
+
- **Dynamic Scaling**: Add or remove robots without service interruption
|
|
18
|
+
|
|
19
|
+
## Architecture
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
23
|
+
│ Robot Group │
|
|
24
|
+
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
25
|
+
│ │ Robot A │ │ Robot B │ │ Robot C │ │
|
|
26
|
+
│ │ (active) │ │ (active) │ │ (passive) │ │
|
|
27
|
+
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
|
|
28
|
+
│ │ │ │ │
|
|
29
|
+
│ └────────────────┼────────────────┘ │
|
|
30
|
+
│ │ │
|
|
31
|
+
│ ┌───────────▼───────────┐ │
|
|
32
|
+
│ │ Shared Working Memory │ │
|
|
33
|
+
│ │ (PostgreSQL + NOTIFY)│ │
|
|
34
|
+
│ └───────────────────────┘ │
|
|
35
|
+
└─────────────────────────────────────────────────────────────┘
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Prerequisites
|
|
39
|
+
|
|
40
|
+
1. **Database Setup**
|
|
41
|
+
```bash
|
|
42
|
+
rake examples:setup
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
2. **Ollama Models** (for embeddings and tag extraction)
|
|
46
|
+
```bash
|
|
47
|
+
ollama pull nomic-embed-text
|
|
48
|
+
ollama pull gemma3
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
3. **Ruby Dependencies**
|
|
52
|
+
```bash
|
|
53
|
+
bundle install
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Available Scripts
|
|
57
|
+
|
|
58
|
+
### same_process.rb
|
|
59
|
+
|
|
60
|
+
Demonstrates robot groups within a **single Ruby process**. Ideal for understanding the concepts without process management complexity.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
ruby examples/11_robot_groups/same_process.rb
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Scenarios demonstrated:**
|
|
67
|
+
|
|
68
|
+
1. **Create a Robot Group** - Initialize with primary (active) and standby (passive) robots
|
|
69
|
+
2. **Add Shared Memories** - Primary robot stores information accessible to all
|
|
70
|
+
3. **Verify Synchronization** - Confirm all robots see the same context
|
|
71
|
+
4. **Simulate Failover** - Primary "fails", standby promotes to active
|
|
72
|
+
5. **Verify Context Preservation** - Standby has full context after failover
|
|
73
|
+
6. **Dynamic Scaling** - Add a second active robot on-the-fly
|
|
74
|
+
7. **Collaborative Memory** - Multiple robots contribute to shared context
|
|
75
|
+
8. **Real-time Sync** - PostgreSQL LISTEN/NOTIFY propagates changes instantly
|
|
76
|
+
|
|
77
|
+
**Example output:**
|
|
78
|
+
```
|
|
79
|
+
1. Configuring HTM...
|
|
80
|
+
✓ HTM configured
|
|
81
|
+
|
|
82
|
+
2. Creating robot group with primary + standby...
|
|
83
|
+
✓ Group created: customer-support-ha
|
|
84
|
+
Active: support-primary
|
|
85
|
+
Passive: support-standby
|
|
86
|
+
|
|
87
|
+
3. Adding memories to shared working memory...
|
|
88
|
+
✓ Remembered customer preference
|
|
89
|
+
✓ Remembered open ticket
|
|
90
|
+
✓ Remembered customer status
|
|
91
|
+
|
|
92
|
+
4. Verifying working memory synchronization...
|
|
93
|
+
Working memory nodes: 3
|
|
94
|
+
Token utilization: 12.5%
|
|
95
|
+
In sync: ✓ Yes
|
|
96
|
+
|
|
97
|
+
5. Simulating failover scenario...
|
|
98
|
+
⚠ Primary robot 'support-primary' has stopped responding!
|
|
99
|
+
Active robots now: support-standby
|
|
100
|
+
Passive robots now: (none)
|
|
101
|
+
|
|
102
|
+
6. Verifying standby has full context after failover...
|
|
103
|
+
✓ Standby recalled 3 memories about 'customer'
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### multi_process.rb
|
|
107
|
+
|
|
108
|
+
Demonstrates robot groups across **separate Ruby processes**. This is closer to production deployments where each robot runs independently.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
ruby examples/11_robot_groups/multi_process.rb
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Scenarios demonstrated:**
|
|
115
|
+
|
|
116
|
+
1. **Start Robot Processes** - Spawn 3 independent worker processes
|
|
117
|
+
2. **Cross-Process Memory Sharing** - One robot adds memories, others receive via NOTIFY
|
|
118
|
+
3. **Collaborative Memory** - Multiple processes contribute to shared context
|
|
119
|
+
4. **Simulated Failover** - Kill a process, verify others retain context
|
|
120
|
+
5. **Dynamic Scaling** - Add a new robot process to the group
|
|
121
|
+
|
|
122
|
+
**Example output:**
|
|
123
|
+
```
|
|
124
|
+
╔══════════════════════════════════════════════════════════════╗
|
|
125
|
+
║ HTM Multi-Process Robot Group Demo ║
|
|
126
|
+
║ Real-time Sync via PostgreSQL LISTEN/NOTIFY ║
|
|
127
|
+
╚══════════════════════════════════════════════════════════════╝
|
|
128
|
+
|
|
129
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
130
|
+
SCENARIO 1: Starting Robot Processes
|
|
131
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
132
|
+
|
|
133
|
+
✓ robot-alpha (PID 12345)
|
|
134
|
+
✓ robot-beta (PID 12346)
|
|
135
|
+
✓ robot-gamma (PID 12347)
|
|
136
|
+
|
|
137
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
138
|
+
SCENARIO 4: Simulated Failover
|
|
139
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
140
|
+
|
|
141
|
+
Killing robot-alpha...
|
|
142
|
+
⚠ robot-alpha terminated
|
|
143
|
+
|
|
144
|
+
Remaining robots retain context:
|
|
145
|
+
robot-beta: 3 nodes, recalls 2
|
|
146
|
+
robot-gamma: 3 nodes, recalls 2
|
|
147
|
+
|
|
148
|
+
✓ Failover successful
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### robot_worker.rb
|
|
152
|
+
|
|
153
|
+
A standalone worker process used by `multi_process.rb`. You typically don't run this directly.
|
|
154
|
+
|
|
155
|
+
**Communication protocol** (JSON over stdin/stdout):
|
|
156
|
+
|
|
157
|
+
| Command | Request | Response |
|
|
158
|
+
|---------|---------|----------|
|
|
159
|
+
| ping | `{"cmd": "ping"}` | `{"status": "ok", "message": "pong"}` |
|
|
160
|
+
| remember | `{"cmd": "remember", "content": "..."}` | `{"status": "ok", "node_id": 123}` |
|
|
161
|
+
| recall | `{"cmd": "recall", "query": "...", "limit": 5}` | `{"status": "ok", "count": 3}` |
|
|
162
|
+
| status | `{"cmd": "status"}` | `{"status": "ok", "working_memory_nodes": 5, ...}` |
|
|
163
|
+
| shutdown | `{"cmd": "shutdown"}` | `{"status": "ok", "message": "bye"}` |
|
|
164
|
+
|
|
165
|
+
## Key Concepts
|
|
166
|
+
|
|
167
|
+
### Active vs Passive Robots
|
|
168
|
+
|
|
169
|
+
| Role | Behavior |
|
|
170
|
+
|------|----------|
|
|
171
|
+
| **Active** | Handles requests, adds memories, participates in conversations |
|
|
172
|
+
| **Passive** | Mirrors active robot's context silently, ready for instant takeover |
|
|
173
|
+
|
|
174
|
+
### Failover Process
|
|
175
|
+
|
|
176
|
+
1. Active robot becomes unresponsive
|
|
177
|
+
2. `group.failover!` promotes first passive robot to active
|
|
178
|
+
3. Promoted robot has full context (warm standby)
|
|
179
|
+
4. Service continues without context loss
|
|
180
|
+
|
|
181
|
+
### Real-time Synchronization
|
|
182
|
+
|
|
183
|
+
PostgreSQL LISTEN/NOTIFY enables sub-second sync:
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
Robot A adds memory → PostgreSQL NOTIFY → Robot B receives → Updates local cache
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Events propagated:
|
|
190
|
+
- `:added` - New node added to working memory
|
|
191
|
+
- `:evicted` - Node removed due to token limits
|
|
192
|
+
- `:cleared` - Working memory cleared entirely
|
|
193
|
+
|
|
194
|
+
### Token Budget Management
|
|
195
|
+
|
|
196
|
+
Each group has a `max_tokens` limit. When exceeded:
|
|
197
|
+
1. Oldest/least-accessed nodes are evicted
|
|
198
|
+
2. Eviction notifications sent to all robots
|
|
199
|
+
3. All robots stay within budget
|
|
200
|
+
|
|
201
|
+
## Use Cases
|
|
202
|
+
|
|
203
|
+
### Customer Support
|
|
204
|
+
|
|
205
|
+
Multiple support agents share customer context:
|
|
206
|
+
```ruby
|
|
207
|
+
group = HTM::RobotGroup.new(
|
|
208
|
+
name: 'support-team',
|
|
209
|
+
active: ['agent-1', 'agent-2', 'agent-3'],
|
|
210
|
+
max_tokens: 16000
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Any agent can add context
|
|
214
|
+
group.remember('Customer prefers email', originator: 'agent-1')
|
|
215
|
+
|
|
216
|
+
# All agents see the context
|
|
217
|
+
group.recall('customer preferences') # Returns same results for all
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### High Availability
|
|
221
|
+
|
|
222
|
+
Primary + standby for critical systems:
|
|
223
|
+
```ruby
|
|
224
|
+
group = HTM::RobotGroup.new(
|
|
225
|
+
name: 'production-bot',
|
|
226
|
+
active: ['primary'],
|
|
227
|
+
passive: ['standby-1', 'standby-2'],
|
|
228
|
+
max_tokens: 32000
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# If primary fails
|
|
232
|
+
group.failover! # standby-1 becomes active instantly
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Load Balancing
|
|
236
|
+
|
|
237
|
+
Multiple active robots share the load:
|
|
238
|
+
```ruby
|
|
239
|
+
group = HTM::RobotGroup.new(
|
|
240
|
+
name: 'api-handlers',
|
|
241
|
+
active: ['handler-1', 'handler-2', 'handler-3', 'handler-4'],
|
|
242
|
+
max_tokens: 64000
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Any handler can process requests with shared context
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## API Reference
|
|
249
|
+
|
|
250
|
+
### HTM::RobotGroup
|
|
251
|
+
|
|
252
|
+
```ruby
|
|
253
|
+
# Create a group
|
|
254
|
+
group = HTM::RobotGroup.new(
|
|
255
|
+
name: 'my-group',
|
|
256
|
+
active: ['robot-1'],
|
|
257
|
+
passive: ['robot-2'],
|
|
258
|
+
max_tokens: 8000
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# Add memories
|
|
262
|
+
group.remember(content, originator: 'robot-1')
|
|
263
|
+
|
|
264
|
+
# Search memories
|
|
265
|
+
group.recall(query, limit: 10, strategy: :hybrid)
|
|
266
|
+
|
|
267
|
+
# Check status
|
|
268
|
+
group.status
|
|
269
|
+
# => { name:, active:, passive:, working_memory_nodes:, token_utilization:, in_sync: }
|
|
270
|
+
|
|
271
|
+
# Failover
|
|
272
|
+
group.failover!
|
|
273
|
+
|
|
274
|
+
# Add/remove robots
|
|
275
|
+
group.add_active('new-robot')
|
|
276
|
+
group.add_passive('standby-robot')
|
|
277
|
+
|
|
278
|
+
# Sync operations
|
|
279
|
+
group.sync_all
|
|
280
|
+
group.sync_robot('robot-name')
|
|
281
|
+
|
|
282
|
+
# Cleanup
|
|
283
|
+
group.clear_working_memory
|
|
284
|
+
group.shutdown
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### HTM::WorkingMemoryChannel
|
|
288
|
+
|
|
289
|
+
```ruby
|
|
290
|
+
# Create channel for cross-process sync
|
|
291
|
+
channel = HTM::WorkingMemoryChannel.new(group_name, db_config)
|
|
292
|
+
|
|
293
|
+
# Register callback for changes
|
|
294
|
+
channel.on_change do |event, node_id, origin_robot_id|
|
|
295
|
+
case event
|
|
296
|
+
when :added then # handle new node
|
|
297
|
+
when :evicted then # handle removal
|
|
298
|
+
when :cleared then # handle clear
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Start/stop listening
|
|
303
|
+
channel.start_listening
|
|
304
|
+
channel.stop_listening
|
|
305
|
+
|
|
306
|
+
# Send notifications
|
|
307
|
+
channel.notify(:added, node_id: 123, robot_id: 456)
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Troubleshooting
|
|
311
|
+
|
|
312
|
+
### Robots not receiving notifications
|
|
313
|
+
|
|
314
|
+
1. Verify PostgreSQL is running with LISTEN/NOTIFY support
|
|
315
|
+
2. Check that all robots use the same `group_name`
|
|
316
|
+
3. Ensure `channel.start_listening` was called
|
|
317
|
+
|
|
318
|
+
### High memory usage
|
|
319
|
+
|
|
320
|
+
1. Reduce `max_tokens` for the group
|
|
321
|
+
2. Call `group.clear_working_memory` periodically
|
|
322
|
+
3. Use more aggressive eviction strategies
|
|
323
|
+
|
|
324
|
+
### Slow synchronization
|
|
325
|
+
|
|
326
|
+
1. Check PostgreSQL connection latency
|
|
327
|
+
2. Reduce notification frequency for bulk operations
|
|
328
|
+
3. Consider batching memory additions
|
|
329
|
+
|
|
330
|
+
## Environment Variables
|
|
331
|
+
|
|
332
|
+
| Variable | Description |
|
|
333
|
+
|----------|-------------|
|
|
334
|
+
| `HTM_DATABASE__URL` | PostgreSQL connection URL (required) |
|
|
335
|
+
| `HTM_ENV` | Environment name (set to `examples` by helper) |
|
|
@@ -16,16 +16,30 @@
|
|
|
16
16
|
# { "cmd": "recall", "query": "...", "limit": 5 }
|
|
17
17
|
# { "cmd": "status" }
|
|
18
18
|
# { "cmd": "shutdown" }
|
|
19
|
+
#
|
|
20
|
+
# Note: This worker inherits HTM_ENV and HTM_DATABASE__URL from the parent
|
|
21
|
+
# process (multi_process.rb) which uses examples_helper.rb.
|
|
19
22
|
|
|
20
23
|
require 'logger'
|
|
21
24
|
require 'json'
|
|
22
|
-
require_relative '../../
|
|
25
|
+
require_relative '../../examples_helper'
|
|
23
26
|
|
|
24
27
|
robot_name = ARGV[0]
|
|
25
28
|
group_name = ARGV[1]
|
|
26
29
|
|
|
27
30
|
unless robot_name && group_name
|
|
28
|
-
$stderr.puts
|
|
31
|
+
$stderr.puts <<~NOTICE
|
|
32
|
+
|
|
33
|
+
Usage: ruby robot_worker.rb <robot_name> <group_name>
|
|
34
|
+
|
|
35
|
+
This robot can be run as a standalone process or as part of a group.
|
|
36
|
+
It is not an actual demo program.
|
|
37
|
+
Its just a part of the two scenarios being demonstrated:
|
|
38
|
+
|
|
39
|
+
Scenario 1: Multiple Robots in Separate Processes (multi_process.rb)
|
|
40
|
+
Scenario 2: All Robots Running Within the Same Process (same_process.rb)
|
|
41
|
+
|
|
42
|
+
NOTICE
|
|
29
43
|
exit 1
|
|
30
44
|
end
|
|
31
45
|
|
|
@@ -64,7 +78,7 @@ channel.on_change do |event, node_id, origin_robot_id|
|
|
|
64
78
|
|
|
65
79
|
case event
|
|
66
80
|
when :added
|
|
67
|
-
node = HTM::Models::Node.
|
|
81
|
+
node = HTM::Models::Node.first(id: node_id)
|
|
68
82
|
if node
|
|
69
83
|
htm.working_memory.add_from_sync(
|
|
70
84
|
id: node.id,
|
|
@@ -13,10 +13,13 @@
|
|
|
13
13
|
# 4. Dynamic scaling by spawning new processes
|
|
14
14
|
#
|
|
15
15
|
# Prerequisites:
|
|
16
|
-
# 1. Set
|
|
17
|
-
# 2.
|
|
16
|
+
# 1. Set up examples database: rake examples:setup
|
|
17
|
+
# 2. Install dependencies: bundle install
|
|
18
|
+
#
|
|
19
|
+
# Run via:
|
|
20
|
+
# ruby examples/robot_groups/multi_process.rb
|
|
18
21
|
|
|
19
|
-
require_relative '
|
|
22
|
+
require_relative '../examples_helper'
|
|
20
23
|
require 'json'
|
|
21
24
|
require 'timeout'
|
|
22
25
|
require 'open3'
|
|
@@ -26,7 +29,7 @@ require 'open3'
|
|
|
26
29
|
# =============================================================================
|
|
27
30
|
|
|
28
31
|
class RobotProcess
|
|
29
|
-
WORKER_SCRIPT = File.expand_path('robot_worker.rb', __dir__)
|
|
32
|
+
WORKER_SCRIPT = File.expand_path('lib/robot_worker.rb', __dir__)
|
|
30
33
|
|
|
31
34
|
attr_reader :name, :pid
|
|
32
35
|
|
|
@@ -119,11 +122,8 @@ def run_demo
|
|
|
119
122
|
|
|
120
123
|
BANNER
|
|
121
124
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
puts ' export HTM_DATABASE__URL="postgresql://user@localhost:5432/htm_development"'
|
|
125
|
-
exit 1
|
|
126
|
-
end
|
|
125
|
+
ExamplesHelper.print_environment
|
|
126
|
+
ExamplesHelper.require_database!
|
|
127
127
|
|
|
128
128
|
group_name = "demo-#{Time.now.to_i}"
|
|
129
129
|
robots = []
|
|
@@ -19,25 +19,22 @@
|
|
|
19
19
|
# synchronization of in-memory working memory across robots.
|
|
20
20
|
#
|
|
21
21
|
# Prerequisites:
|
|
22
|
-
# 1. Set
|
|
23
|
-
# 2.
|
|
24
|
-
#
|
|
22
|
+
# 1. Set up examples database: rake examples:setup
|
|
23
|
+
# 2. Install dependencies: bundle install
|
|
24
|
+
#
|
|
25
|
+
# Run via:
|
|
26
|
+
# ruby examples/robot_groups/same_process.rb
|
|
25
27
|
|
|
26
|
-
require_relative '
|
|
28
|
+
require_relative '../examples_helper'
|
|
27
29
|
require 'json'
|
|
28
30
|
|
|
29
31
|
# =============================================================================
|
|
30
32
|
# Demo Script
|
|
31
33
|
# =============================================================================
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
unless ENV['HTM_DATABASE__URL']
|
|
37
|
-
puts 'ERROR: HTM_DATABASE__URL not set. Please set it:'
|
|
38
|
-
puts ' export HTM_DATABASE__URL="postgresql://postgres@localhost:5432/htm_development"'
|
|
39
|
-
exit 1
|
|
40
|
-
end
|
|
35
|
+
ExamplesHelper.section "HTM Robot Group Demo - Shared Working Memory & Failover"
|
|
36
|
+
ExamplesHelper.print_environment
|
|
37
|
+
ExamplesHelper.require_database!
|
|
41
38
|
|
|
42
39
|
begin
|
|
43
40
|
# Configure HTM
|
|
@@ -12,6 +12,9 @@ gem 'htm', path: '../..'
|
|
|
12
12
|
# PostgreSQL (required for HTM)
|
|
13
13
|
gem 'pg', '~> 1.5'
|
|
14
14
|
|
|
15
|
+
# Ruby 4.0+ bundled gems (no longer in stdlib)
|
|
16
|
+
gem 'ostruct'
|
|
17
|
+
|
|
15
18
|
# Frontend - using CDN for Tailwind and Hotwire (no build step)
|
|
16
19
|
gem 'propshaft' # Asset pipeline for CSS/images
|
|
17
20
|
# Pagination uses simple offset/limit (no Kaminari needed)
|