htm 0.0.1 → 0.0.2
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/.envrc +1 -0
- data/.tbls.yml +30 -0
- data/CHANGELOG.md +30 -0
- data/SETUP.md +132 -101
- data/db/migrate/20250125000001_add_content_hash_to_nodes.rb +14 -0
- data/db/migrate/20250125000002_create_robot_nodes.rb +35 -0
- data/db/migrate/20250125000003_remove_source_and_robot_id_from_nodes.rb +28 -0
- data/db/migrate/20250126000001_create_working_memories.rb +19 -0
- data/db/migrate/20250126000002_remove_unused_columns.rb +12 -0
- data/db/schema.sql +226 -43
- data/docs/api/database.md +20 -232
- data/docs/api/embedding-service.md +1 -7
- data/docs/api/htm.md +195 -449
- data/docs/api/index.md +1 -7
- data/docs/api/long-term-memory.md +342 -590
- data/docs/architecture/adrs/001-postgresql-timescaledb.md +1 -1
- data/docs/architecture/adrs/003-ollama-embeddings.md +1 -1
- data/docs/architecture/adrs/010-redis-working-memory-rejected.md +2 -27
- data/docs/architecture/adrs/index.md +2 -13
- data/docs/architecture/hive-mind.md +165 -166
- data/docs/architecture/index.md +2 -2
- data/docs/architecture/overview.md +5 -171
- data/docs/architecture/two-tier-memory.md +1 -35
- data/docs/assets/images/adr-010-current-architecture.svg +37 -0
- data/docs/assets/images/adr-010-proposed-architecture.svg +48 -0
- data/docs/assets/images/adr-dependency-tree.svg +93 -0
- data/docs/assets/images/class-hierarchy.svg +55 -0
- data/docs/assets/images/exception-hierarchy.svg +45 -0
- data/docs/assets/images/htm-architecture-overview.svg +83 -0
- data/docs/assets/images/htm-complete-memory-flow.svg +160 -0
- data/docs/assets/images/htm-context-assembly-flow.svg +148 -0
- data/docs/assets/images/htm-eviction-process.svg +141 -0
- data/docs/assets/images/htm-memory-addition-flow.svg +138 -0
- data/docs/assets/images/htm-memory-recall-flow.svg +152 -0
- data/docs/assets/images/htm-node-states.svg +123 -0
- data/docs/assets/images/project-structure.svg +78 -0
- data/docs/assets/images/test-directory-structure.svg +38 -0
- data/{dbdoc → docs/database}/README.md +5 -3
- data/{dbdoc → docs/database}/public.node_tags.md +4 -5
- data/docs/database/public.node_tags.svg +106 -0
- data/{dbdoc → docs/database}/public.nodes.md +3 -8
- data/docs/database/public.nodes.svg +152 -0
- data/docs/database/public.robot_nodes.md +44 -0
- data/docs/database/public.robot_nodes.svg +121 -0
- data/{dbdoc → docs/database}/public.robots.md +1 -2
- data/docs/database/public.robots.svg +106 -0
- data/docs/database/public.working_memories.md +40 -0
- data/docs/database/public.working_memories.svg +112 -0
- data/{dbdoc → docs/database}/schema.json +342 -110
- data/docs/database/schema.svg +223 -0
- data/docs/development/index.md +1 -29
- data/docs/development/schema.md +84 -324
- data/docs/development/testing.md +1 -9
- data/docs/getting-started/index.md +47 -0
- data/docs/{installation.md → getting-started/installation.md} +2 -2
- data/docs/{quick-start.md → getting-started/quick-start.md} +5 -5
- data/docs/guides/adding-memories.md +221 -655
- data/docs/guides/search-strategies.md +85 -51
- data/docs/images/htm-er-diagram.svg +156 -0
- data/docs/index.md +16 -31
- data/docs/multi_framework_support.md +4 -4
- data/examples/basic_usage.rb +18 -16
- data/examples/cli_app/htm_cli.rb +86 -8
- data/examples/custom_llm_configuration.rb +1 -2
- data/examples/example_app/app.rb +11 -14
- data/examples/sinatra_app/Gemfile +1 -0
- data/examples/sinatra_app/Gemfile.lock +166 -0
- data/examples/sinatra_app/app.rb +219 -24
- data/lib/htm/active_record_config.rb +10 -3
- data/lib/htm/configuration.rb +265 -78
- data/lib/htm/{sinatra.rb → integrations/sinatra.rb} +87 -12
- data/lib/htm/job_adapter.rb +10 -3
- data/lib/htm/long_term_memory.rb +220 -57
- data/lib/htm/models/node.rb +36 -7
- data/lib/htm/models/robot.rb +30 -4
- data/lib/htm/models/robot_node.rb +50 -0
- data/lib/htm/models/tag.rb +52 -0
- data/lib/htm/models/working_memory_entry.rb +88 -0
- data/lib/htm/tasks.rb +4 -0
- data/lib/htm/version.rb +1 -1
- data/lib/htm.rb +34 -13
- data/lib/tasks/htm.rake +32 -1
- data/lib/tasks/jobs.rake +7 -3
- data/lib/tasks/tags.rake +34 -0
- data/mkdocs.yml +56 -9
- metadata +61 -31
- data/dbdoc/public.node_tags.svg +0 -112
- data/dbdoc/public.nodes.svg +0 -118
- data/dbdoc/public.robots.svg +0 -90
- data/dbdoc/schema.svg +0 -154
- /data/{dbdoc → docs/database}/public.node_stats.md +0 -0
- /data/{dbdoc → docs/database}/public.node_stats.svg +0 -0
- /data/{dbdoc → docs/database}/public.nodes_tags.md +0 -0
- /data/{dbdoc → docs/database}/public.nodes_tags.svg +0 -0
- /data/{dbdoc → docs/database}/public.ontology_structure.md +0 -0
- /data/{dbdoc → docs/database}/public.ontology_structure.svg +0 -0
- /data/{dbdoc → docs/database}/public.operations_log.md +0 -0
- /data/{dbdoc → docs/database}/public.operations_log.svg +0 -0
- /data/{dbdoc → docs/database}/public.relationships.md +0 -0
- /data/{dbdoc → docs/database}/public.relationships.svg +0 -0
- /data/{dbdoc → docs/database}/public.robot_activity.md +0 -0
- /data/{dbdoc → docs/database}/public.robot_activity.svg +0 -0
- /data/{dbdoc → docs/database}/public.schema_migrations.md +0 -0
- /data/{dbdoc → docs/database}/public.schema_migrations.svg +0 -0
- /data/{dbdoc → docs/database}/public.tags.md +0 -0
- /data/{dbdoc → docs/database}/public.tags.svg +0 -0
- /data/{dbdoc → docs/database}/public.topic_relationships.md +0 -0
- /data/{dbdoc → docs/database}/public.topic_relationships.svg +0 -0
|
@@ -299,7 +299,7 @@ ALTER TABLE nodes SET (
|
|
|
299
299
|
- [pgvector Documentation](https://github.com/pgvector/pgvector)
|
|
300
300
|
- [PostgreSQL Full-Text Search](https://www.postgresql.org/docs/current/textsearch.html)
|
|
301
301
|
- [HTM Database Schema Guide](../../development/schema.md)
|
|
302
|
-
- [HTM Configuration Guide](../../installation.md)
|
|
302
|
+
- [HTM Configuration Guide](../../getting-started/installation.md)
|
|
303
303
|
|
|
304
304
|
---
|
|
305
305
|
|
|
@@ -404,7 +404,7 @@ htm = HTM.new(
|
|
|
404
404
|
- [pgvector Documentation](https://github.com/pgvector/pgvector)
|
|
405
405
|
- [pgai Documentation](https://github.com/timescale/pgai)
|
|
406
406
|
- [ADR-011: Database-Side Embedding Generation with pgai](011-pgai-integration.md) - **Supersedes this ADR**
|
|
407
|
-
- [HTM Setup Guide](../../installation.md)
|
|
407
|
+
- [HTM Setup Guide](../../getting-started/installation.md)
|
|
408
408
|
|
|
409
409
|
---
|
|
410
410
|
|
|
@@ -30,18 +30,7 @@ During architectural review, we identified that working memory is currently vola
|
|
|
30
30
|
|
|
31
31
|
### Current Architecture (Two-Tier)
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
┌─────────────────┐
|
|
35
|
-
│ HTM Instance │
|
|
36
|
-
│ │
|
|
37
|
-
│ ┌───────────┐ │ ┌──────────────┐
|
|
38
|
-
│ │ Working │ │────>│ PostgreSQL │
|
|
39
|
-
│ │ Memory │ │ │ (Long-Term) │
|
|
40
|
-
│ │ (Hash) │ │ │ │
|
|
41
|
-
│ └───────────┘ │ └──────────────┘
|
|
42
|
-
│ volatile │ persistent
|
|
43
|
-
└─────────────────┘
|
|
44
|
-
```
|
|
33
|
+

|
|
45
34
|
|
|
46
35
|
**How it works**:
|
|
47
36
|
1. `add_node()` saves **immediately** to PostgreSQL
|
|
@@ -53,21 +42,7 @@ During architectural review, we identified that working memory is currently vola
|
|
|
53
42
|
|
|
54
43
|
### Proposed Architecture (Three-Tier)
|
|
55
44
|
|
|
56
|
-
|
|
57
|
-
┌─────────────────┐
|
|
58
|
-
│ HTM Instance │
|
|
59
|
-
│ │
|
|
60
|
-
│ ││ │
|
|
61
|
-
│ ││ │
|
|
62
|
-
│ ▼▼ │
|
|
63
|
-
│ ┌───────────┐ │ ┌──────────────┐
|
|
64
|
-
│ │ Redis │ │────>│ PostgreSQL │
|
|
65
|
-
│ │ (Working) │ │ │ (Long-Term) │
|
|
66
|
-
│ │ │ │ │ │
|
|
67
|
-
│ └───────────┘ │ └──────────────┘
|
|
68
|
-
│ persistent │ persistent
|
|
69
|
-
└─────────────────┘
|
|
70
|
-
```
|
|
45
|
+

|
|
71
46
|
|
|
72
47
|
**Proposed changes**:
|
|
73
48
|
- Store working memory in Redis (shared across processes)
|
|
@@ -174,24 +174,13 @@ Proposal to add Redis as a persistent storage layer for working memory was thoro
|
|
|
174
174
|
|
|
175
175
|
## ADR Dependencies
|
|
176
176
|
|
|
177
|
-
|
|
178
|
-
ADR-001 (Storage)
|
|
179
|
-
└─> ADR-002 (Two-Tier Memory)
|
|
180
|
-
├─> ADR-007 (Eviction Strategy)
|
|
181
|
-
├─> ADR-009 (Never-Forget)
|
|
182
|
-
└─> ADR-010 (Redis WM - Rejected Alternative)
|
|
183
|
-
└─> ADR-003 (Embeddings)
|
|
184
|
-
└─> ADR-005 (RAG Retrieval)
|
|
185
|
-
└─> ADR-004 (Hive Mind)
|
|
186
|
-
└─> ADR-008 (Robot ID)
|
|
187
|
-
└─> ADR-006 (Context Assembly)
|
|
188
|
-
```
|
|
177
|
+

|
|
189
178
|
|
|
190
179
|
## Related Documentation
|
|
191
180
|
|
|
192
181
|
- [HTM API Guide](../../api/index.md)
|
|
193
182
|
- [Database Schema](../../development/schema.md)
|
|
194
|
-
- [Configuration Guide](../../installation.md)
|
|
183
|
+
- [Configuration Guide](../../getting-started/installation.md)
|
|
195
184
|
- [Development Workflow](../../development/index.md)
|
|
196
185
|
|
|
197
186
|
## Contributing to ADRs
|
|
@@ -250,95 +250,106 @@ end
|
|
|
250
250
|
!!! info "Related ADR"
|
|
251
251
|
See [ADR-008: Robot Identification System](adrs/008-robot-identification.md) for detailed design decisions.
|
|
252
252
|
|
|
253
|
-
## Memory Attribution
|
|
253
|
+
## Memory Attribution and Deduplication
|
|
254
254
|
|
|
255
|
-
|
|
255
|
+
HTM uses a many-to-many relationship between robots and nodes, enabling both content deduplication and attribution tracking.
|
|
256
256
|
|
|
257
257
|
### Attribution Schema
|
|
258
258
|
|
|
259
259
|
```sql
|
|
260
|
+
-- Nodes are content-deduplicated via SHA-256 hash
|
|
260
261
|
CREATE TABLE nodes (
|
|
261
262
|
id BIGSERIAL PRIMARY KEY,
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
robot_id TEXT NOT NULL REFERENCES robots(id), -- Attribution!
|
|
263
|
+
content TEXT NOT NULL,
|
|
264
|
+
content_hash VARCHAR(64) UNIQUE, -- SHA-256 for deduplication
|
|
265
265
|
...
|
|
266
266
|
);
|
|
267
267
|
|
|
268
|
-
--
|
|
269
|
-
CREATE
|
|
268
|
+
-- Robot-node relationships tracked in join table
|
|
269
|
+
CREATE TABLE robot_nodes (
|
|
270
|
+
id BIGSERIAL PRIMARY KEY,
|
|
271
|
+
robot_id BIGINT NOT NULL REFERENCES robots(id),
|
|
272
|
+
node_id BIGINT NOT NULL REFERENCES nodes(id),
|
|
273
|
+
first_remembered_at TIMESTAMPTZ, -- When robot first saw this content
|
|
274
|
+
last_remembered_at TIMESTAMPTZ, -- When robot last tried to remember
|
|
275
|
+
remember_count INTEGER DEFAULT 1 -- How many times robot remembered this
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
-- Indexes for efficient queries
|
|
279
|
+
CREATE UNIQUE INDEX idx_robot_nodes_unique ON robot_nodes(robot_id, node_id);
|
|
280
|
+
CREATE INDEX idx_robot_nodes_robot_id ON robot_nodes(robot_id);
|
|
281
|
+
CREATE INDEX idx_robot_nodes_node_id ON robot_nodes(node_id);
|
|
270
282
|
```
|
|
271
283
|
|
|
272
|
-
###
|
|
284
|
+
### Content Deduplication
|
|
273
285
|
|
|
274
|
-
When a robot
|
|
286
|
+
When a robot remembers content:
|
|
275
287
|
|
|
276
288
|
```ruby
|
|
277
|
-
def
|
|
278
|
-
#
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
289
|
+
def remember(content, tags: [])
|
|
290
|
+
# 1. Compute SHA-256 hash of content
|
|
291
|
+
content_hash = Digest::SHA256.hexdigest(content)
|
|
292
|
+
|
|
293
|
+
# 2. Check if node with same hash exists
|
|
294
|
+
existing_node = HTM::Models::Node.find_by(content_hash: content_hash)
|
|
295
|
+
|
|
296
|
+
if existing_node
|
|
297
|
+
# 3a. Link robot to existing node (or update remember_count)
|
|
298
|
+
link_robot_to_node(robot_id: @robot_id, node: existing_node)
|
|
299
|
+
return existing_node.id
|
|
300
|
+
else
|
|
301
|
+
# 3b. Create new node and link robot
|
|
302
|
+
node = create_new_node(content, content_hash)
|
|
303
|
+
link_robot_to_node(robot_id: @robot_id, node: node)
|
|
304
|
+
return node.id
|
|
305
|
+
end
|
|
285
306
|
end
|
|
286
307
|
```
|
|
287
308
|
|
|
288
309
|
### Attribution Queries
|
|
289
310
|
|
|
290
|
-
#### Which
|
|
311
|
+
#### Which robots remember this content?
|
|
291
312
|
|
|
292
313
|
```ruby
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
314
|
+
# Find all robots that have remembered a specific node
|
|
315
|
+
def robots_for_node(node_id)
|
|
316
|
+
HTM::Models::RobotNode
|
|
317
|
+
.where(node_id: node_id)
|
|
318
|
+
.includes(:robot)
|
|
319
|
+
.map do |rn|
|
|
320
|
+
{
|
|
321
|
+
robot_name: rn.robot.name,
|
|
322
|
+
first_remembered_at: rn.first_remembered_at,
|
|
323
|
+
remember_count: rn.remember_count
|
|
324
|
+
}
|
|
325
|
+
end
|
|
302
326
|
end
|
|
303
327
|
|
|
304
|
-
# Example
|
|
305
|
-
|
|
306
|
-
# =>
|
|
307
|
-
|
|
308
|
-
#
|
|
309
|
-
|
|
310
|
-
robot = db.query("SELECT name FROM robots WHERE id = $1", [robot_id]).first
|
|
311
|
-
"#{robot['name']}: #{count} mentions"
|
|
312
|
-
end
|
|
313
|
-
# => ["Code Helper: 15 mentions", "Research Bot: 8 mentions"]
|
|
328
|
+
# Example
|
|
329
|
+
robots_for_node(123)
|
|
330
|
+
# => [
|
|
331
|
+
# { robot_name: "Code Helper", first_remembered_at: "2025-01-15", remember_count: 3 },
|
|
332
|
+
# { robot_name: "Research Bot", first_remembered_at: "2025-01-16", remember_count: 1 }
|
|
333
|
+
# ]
|
|
314
334
|
```
|
|
315
335
|
|
|
316
|
-
####
|
|
336
|
+
#### Nodes shared by multiple robots
|
|
317
337
|
|
|
318
338
|
```ruby
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
timestamp: n['created_at'],
|
|
329
|
-
robot: n['robot_id'],
|
|
330
|
-
content: n['value'],
|
|
331
|
-
type: n['type']
|
|
332
|
-
}}
|
|
339
|
+
# Find content that multiple robots have remembered
|
|
340
|
+
def shared_memories(min_robots: 2, limit: 50)
|
|
341
|
+
HTM::Models::Node
|
|
342
|
+
.joins(:robot_nodes)
|
|
343
|
+
.group('nodes.id')
|
|
344
|
+
.having('COUNT(DISTINCT robot_nodes.robot_id) >= ?', min_robots)
|
|
345
|
+
.order('COUNT(DISTINCT robot_nodes.robot_id) DESC')
|
|
346
|
+
.limit(limit)
|
|
347
|
+
.map(&:attributes)
|
|
333
348
|
end
|
|
334
349
|
|
|
335
|
-
# Example
|
|
336
|
-
|
|
337
|
-
# =>
|
|
338
|
-
# { timestamp: "2025-10-20 10:00:00", robot: "robot-abc123", content: "Let's use PostgreSQL", ... },
|
|
339
|
-
# { timestamp: "2025-10-20 10:15:00", robot: "robot-xyz789", content: "I agree, TimescaleDB is perfect", ... },
|
|
340
|
-
# ...
|
|
341
|
-
# ]
|
|
350
|
+
# Example
|
|
351
|
+
shared_memories(min_robots: 2)
|
|
352
|
+
# => Nodes that 2+ robots have remembered
|
|
342
353
|
```
|
|
343
354
|
|
|
344
355
|
#### Robot activity
|
|
@@ -349,18 +360,19 @@ SELECT id, name, last_active
|
|
|
349
360
|
FROM robots
|
|
350
361
|
ORDER BY last_active DESC;
|
|
351
362
|
|
|
352
|
-
-- Which robot
|
|
353
|
-
SELECT r.name, COUNT(
|
|
363
|
+
-- Which robot has remembered the most nodes?
|
|
364
|
+
SELECT r.name, COUNT(rn.node_id) as memory_count
|
|
354
365
|
FROM robots r
|
|
355
|
-
LEFT JOIN
|
|
366
|
+
LEFT JOIN robot_nodes rn ON rn.robot_id = r.id
|
|
356
367
|
GROUP BY r.id, r.name
|
|
357
368
|
ORDER BY memory_count DESC;
|
|
358
369
|
|
|
359
|
-
-- What has a specific robot
|
|
360
|
-
SELECT
|
|
361
|
-
FROM
|
|
362
|
-
|
|
363
|
-
|
|
370
|
+
-- What has a specific robot remembered recently?
|
|
371
|
+
SELECT n.content, rn.first_remembered_at, rn.remember_count
|
|
372
|
+
FROM robot_nodes rn
|
|
373
|
+
JOIN nodes n ON n.id = rn.node_id
|
|
374
|
+
WHERE rn.robot_id = 1
|
|
375
|
+
ORDER BY rn.last_remembered_at DESC
|
|
364
376
|
LIMIT 50;
|
|
365
377
|
```
|
|
366
378
|
|
|
@@ -374,80 +386,74 @@ A user works with Robot A in one session, then Robot B in another session. Robot
|
|
|
374
386
|
|
|
375
387
|
```ruby
|
|
376
388
|
# Session 1 - Robot A (Code Helper)
|
|
377
|
-
htm_a = HTM.new(
|
|
378
|
-
htm_a.
|
|
379
|
-
|
|
380
|
-
"User prefers debug_me over puts for debugging",
|
|
381
|
-
type: :preference,
|
|
382
|
-
importance: 9.0
|
|
383
|
-
)
|
|
384
|
-
# Stored in long-term memory with robot_id: "robot-abc123"
|
|
389
|
+
htm_a = HTM.new(robot_name: "Code Helper A")
|
|
390
|
+
htm_a.remember("User prefers debug_me over puts for debugging")
|
|
391
|
+
# Stored in long-term memory, linked to robot A via robot_nodes
|
|
385
392
|
|
|
386
393
|
# === User logs out, logs in next day ===
|
|
387
394
|
|
|
388
395
|
# Session 2 - Robot B (different process, same or different machine)
|
|
389
|
-
htm_b = HTM.new(
|
|
396
|
+
htm_b = HTM.new(robot_name: "Code Helper B")
|
|
390
397
|
|
|
391
398
|
# Robot B recalls preferences
|
|
392
|
-
memories = htm_b.recall(
|
|
399
|
+
memories = htm_b.recall("debugging preference", timeframe: "last week")
|
|
393
400
|
# => Finds preference from Robot A!
|
|
394
|
-
# => [{ "key" => "user_pref_001", "robot_id" => "robot-abc123", ... }]
|
|
395
401
|
|
|
396
402
|
# Robot B knows user preference without being told
|
|
397
403
|
```
|
|
398
404
|
|
|
399
|
-
### Use Case 2: Collaborative Development
|
|
405
|
+
### Use Case 2: Collaborative Development with Deduplication
|
|
400
406
|
|
|
401
|
-
Different robots working on
|
|
407
|
+
Different robots working on the same content automatically share nodes.
|
|
402
408
|
|
|
403
409
|
```ruby
|
|
404
410
|
# Robot A (Architecture discussion)
|
|
405
411
|
htm_a = HTM.new(robot_name: "Architect Bot")
|
|
406
|
-
htm_a.
|
|
407
|
-
"
|
|
408
|
-
"We decided to use PostgreSQL with TimescaleDB for HTM storage",
|
|
409
|
-
type: :decision,
|
|
410
|
-
importance: 10.0,
|
|
412
|
+
node_id = htm_a.remember(
|
|
413
|
+
"We decided to use PostgreSQL with pgvector for HTM storage",
|
|
411
414
|
tags: ["architecture", "database"]
|
|
412
415
|
)
|
|
416
|
+
# => node_id: 123 (new node created)
|
|
413
417
|
|
|
414
|
-
# Robot B
|
|
418
|
+
# Robot B learns the same fact independently
|
|
415
419
|
htm_b = HTM.new(robot_name: "Code Bot")
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
# Robot B implements based on Robot A's decision
|
|
420
|
-
htm_b.add_node(
|
|
421
|
-
"implementation_001",
|
|
422
|
-
"Implemented Database class with ConnectionPool for PostgreSQL",
|
|
423
|
-
type: :code,
|
|
424
|
-
importance: 7.0,
|
|
425
|
-
related_to: ["decision_001"], # Link to Robot A's decision
|
|
426
|
-
tags: ["implementation", "database"]
|
|
420
|
+
node_id = htm_b.remember(
|
|
421
|
+
"We decided to use PostgreSQL with pgvector for HTM storage"
|
|
427
422
|
)
|
|
423
|
+
# => node_id: 123 (same node! Content hash matched)
|
|
424
|
+
|
|
425
|
+
# Both robots now linked to the same node
|
|
426
|
+
# Robot A: remember_count = 1
|
|
427
|
+
# Robot B: remember_count = 1
|
|
428
|
+
|
|
429
|
+
# Check shared ownership
|
|
430
|
+
rns = HTM::Models::RobotNode.where(node_id: 123)
|
|
431
|
+
rns.each { |rn| puts "Robot #{rn.robot_id}: #{rn.remember_count} times" }
|
|
432
|
+
# => Robot 1: 1 times
|
|
433
|
+
# => Robot 2: 1 times
|
|
428
434
|
```
|
|
429
435
|
|
|
430
|
-
### Use Case 3:
|
|
436
|
+
### Use Case 3: Finding Shared Knowledge
|
|
431
437
|
|
|
432
|
-
Analyze
|
|
438
|
+
Analyze what content is shared across robots:
|
|
433
439
|
|
|
434
440
|
```ruby
|
|
435
|
-
#
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
#
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
#
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
441
|
+
# Find nodes remembered by multiple robots
|
|
442
|
+
shared_nodes = HTM::Models::Node
|
|
443
|
+
.joins(:robot_nodes)
|
|
444
|
+
.group('nodes.id')
|
|
445
|
+
.having('COUNT(DISTINCT robot_nodes.robot_id) >= 2')
|
|
446
|
+
.select('nodes.*, COUNT(DISTINCT robot_nodes.robot_id) as robot_count')
|
|
447
|
+
|
|
448
|
+
shared_nodes.each do |node|
|
|
449
|
+
puts "Node #{node.id}: #{node.robot_count} robots"
|
|
450
|
+
puts " Content: #{node.content[0..80]}..."
|
|
451
|
+
|
|
452
|
+
# Show which robots
|
|
453
|
+
node.robot_nodes.each do |rn|
|
|
454
|
+
puts " - Robot #{rn.robot.name}: remembered #{rn.remember_count}x"
|
|
455
|
+
end
|
|
456
|
+
end
|
|
451
457
|
```
|
|
452
458
|
|
|
453
459
|
## Robot Activity Tracking
|
|
@@ -639,47 +645,47 @@ WHERE rtm.team_id = $1;
|
|
|
639
645
|
|
|
640
646
|
## Code Examples
|
|
641
647
|
|
|
642
|
-
### Example 1: Persistent Robot with
|
|
648
|
+
### Example 1: Persistent Robot with Named Identity
|
|
643
649
|
|
|
644
650
|
```ruby
|
|
645
|
-
#
|
|
646
|
-
ROBOT_ID = ENV.fetch('ROBOT_ID', 'code-helper-001')
|
|
647
|
-
|
|
648
|
-
# Initialize with persistent identity
|
|
651
|
+
# Initialize with persistent name
|
|
649
652
|
htm = HTM.new(
|
|
650
|
-
robot_id: ROBOT_ID,
|
|
651
653
|
robot_name: "Code Helper",
|
|
652
654
|
working_memory_size: 128_000
|
|
653
655
|
)
|
|
654
656
|
|
|
655
|
-
# Add memories (
|
|
656
|
-
htm.
|
|
657
|
+
# Add memories (linked to this robot via robot_nodes)
|
|
658
|
+
htm.remember("Use PostgreSQL for ACID guarantees and pgvector support")
|
|
657
659
|
|
|
658
|
-
#
|
|
660
|
+
# Robot ID is stored in database, robot_name is human-readable
|
|
661
|
+
puts "Robot ID: #{htm.robot_id}"
|
|
662
|
+
puts "Robot name: #{htm.robot_name}"
|
|
659
663
|
```
|
|
660
664
|
|
|
661
|
-
### Example 2: Multi-Robot Collaboration
|
|
665
|
+
### Example 2: Multi-Robot Collaboration with Deduplication
|
|
662
666
|
|
|
663
667
|
```ruby
|
|
664
668
|
# Robot A: Architecture discussion
|
|
665
|
-
robot_a = HTM.new(
|
|
666
|
-
robot_a.
|
|
667
|
-
"db_choice",
|
|
669
|
+
robot_a = HTM.new(robot_name: "Architect")
|
|
670
|
+
node_id = robot_a.remember(
|
|
668
671
|
"PostgreSQL chosen for ACID guarantees and pgvector support",
|
|
669
|
-
|
|
670
|
-
importance: 10.0
|
|
672
|
+
tags: ["architecture:database", "decision"]
|
|
671
673
|
)
|
|
672
674
|
|
|
673
675
|
# Robot B: Implementation (different process, accesses same LTM)
|
|
674
|
-
robot_b = HTM.new(
|
|
675
|
-
decisions = robot_b.recall(
|
|
676
|
+
robot_b = HTM.new(robot_name: "Coder")
|
|
677
|
+
decisions = robot_b.recall("database decision", timeframe: "today")
|
|
676
678
|
# => Finds Robot A's decision automatically
|
|
677
679
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
+
# If Robot B remembers the same content, it links to existing node
|
|
681
|
+
same_node_id = robot_b.remember(
|
|
682
|
+
"PostgreSQL chosen for ACID guarantees and pgvector support"
|
|
683
|
+
)
|
|
684
|
+
# => same_node_id == node_id (deduplication!)
|
|
685
|
+
|
|
686
|
+
robot_b.remember(
|
|
680
687
|
"Implemented Database class with connection pooling",
|
|
681
|
-
|
|
682
|
-
related_to: ["db_choice"] # Link to Robot A's decision
|
|
688
|
+
tags: ["implementation:database", "code:ruby"]
|
|
683
689
|
)
|
|
684
690
|
```
|
|
685
691
|
|
|
@@ -687,41 +693,34 @@ robot_b.add_node(
|
|
|
687
693
|
|
|
688
694
|
```ruby
|
|
689
695
|
# Get all robots and their activity
|
|
690
|
-
stats =
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
SQL
|
|
707
|
-
|
|
708
|
-
stats[robot['name']] = {
|
|
709
|
-
id: robot['id'],
|
|
710
|
-
last_active: robot['last_active'],
|
|
711
|
-
total_memories: memory_count,
|
|
712
|
-
recent_operations: recent_ops
|
|
696
|
+
stats = []
|
|
697
|
+
|
|
698
|
+
HTM::Models::Robot.order(last_active: :desc).each do |robot|
|
|
699
|
+
# Count memories via robot_nodes
|
|
700
|
+
memory_count = robot.robot_nodes.count
|
|
701
|
+
|
|
702
|
+
# Get remember statistics
|
|
703
|
+
remember_stats = robot.robot_nodes.group(:node_id).count.size # Unique nodes
|
|
704
|
+
total_remembers = robot.robot_nodes.sum(:remember_count) # Total remembers
|
|
705
|
+
|
|
706
|
+
stats << {
|
|
707
|
+
name: robot.name,
|
|
708
|
+
id: robot.id,
|
|
709
|
+
last_active: robot.last_active,
|
|
710
|
+
unique_memories: memory_count,
|
|
711
|
+
total_remembers: total_remembers
|
|
713
712
|
}
|
|
714
713
|
end
|
|
715
714
|
|
|
716
715
|
# Display dashboard
|
|
717
|
-
|
|
718
|
-
|
|
716
|
+
puts "=" * 60
|
|
717
|
+
puts "Robot Activity Dashboard"
|
|
718
|
+
puts "=" * 60
|
|
719
|
+
stats.each do |data|
|
|
720
|
+
puts "#{data[:name]} (ID: #{data[:id]})"
|
|
719
721
|
puts " Last active: #{data[:last_active]}"
|
|
720
|
-
puts "
|
|
721
|
-
puts "
|
|
722
|
-
data[:recent_operations].each do |op|
|
|
723
|
-
puts " #{op['operation']}: #{op['count']}"
|
|
724
|
-
end
|
|
722
|
+
puts " Unique memories: #{data[:unique_memories]}"
|
|
723
|
+
puts " Total remembers: #{data[:total_remembers]}"
|
|
725
724
|
puts
|
|
726
725
|
end
|
|
727
726
|
```
|
data/docs/architecture/index.md
CHANGED
|
@@ -332,8 +332,8 @@ Explore detailed architecture documentation:
|
|
|
332
332
|
|
|
333
333
|
## Related Documentation
|
|
334
334
|
|
|
335
|
-
- [Installation Guide](../installation.md) - Setup PostgreSQL, TimescaleDB, and dependencies
|
|
336
|
-
- [Quick Start](../quick-start.md) - Get started with HTM in 5 minutes
|
|
335
|
+
- [Installation Guide](../getting-started/installation.md) - Setup PostgreSQL, TimescaleDB, and dependencies
|
|
336
|
+
- [Quick Start](../getting-started/quick-start.md) - Get started with HTM in 5 minutes
|
|
337
337
|
- [API Reference](../api/htm.md) - Complete API documentation
|
|
338
338
|
- [Architecture Decision Records](adrs/index.md) - Detailed decision history
|
|
339
339
|
|