htm 0.0.10 → 0.0.14
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/.dictate.toml +46 -0
- data/.envrc +2 -0
- data/CHANGELOG.md +86 -3
- data/README.md +86 -7
- data/Rakefile +14 -2
- data/bin/htm_mcp.rb +621 -0
- data/config/database.yml +20 -13
- data/db/migrate/00010_add_soft_delete_to_associations.rb +29 -0
- data/db/migrate/00011_add_performance_indexes.rb +21 -0
- data/db/migrate/00012_add_tags_trigram_index.rb +18 -0
- data/db/migrate/00013_enable_lz4_compression.rb +43 -0
- data/db/schema.sql +49 -92
- data/docs/api/index.md +1 -1
- data/docs/api/yard/HTM.md +2 -4
- data/docs/architecture/index.md +1 -1
- data/docs/development/index.md +1 -1
- data/docs/getting-started/index.md +1 -1
- data/docs/guides/index.md +1 -1
- data/docs/images/telemetry-architecture.svg +153 -0
- data/docs/telemetry.md +391 -0
- data/examples/README.md +171 -1
- data/examples/cli_app/README.md +1 -1
- data/examples/cli_app/htm_cli.rb +1 -1
- data/examples/mcp_client.rb +529 -0
- data/examples/sinatra_app/app.rb +1 -1
- data/examples/telemetry/README.md +147 -0
- data/examples/telemetry/SETUP_README.md +169 -0
- data/examples/telemetry/demo.rb +498 -0
- data/examples/telemetry/grafana/dashboards/htm-metrics.json +457 -0
- data/lib/htm/configuration.rb +261 -70
- data/lib/htm/database.rb +46 -22
- data/lib/htm/embedding_service.rb +24 -14
- data/lib/htm/errors.rb +15 -1
- data/lib/htm/jobs/generate_embedding_job.rb +19 -0
- data/lib/htm/jobs/generate_propositions_job.rb +103 -0
- data/lib/htm/jobs/generate_tags_job.rb +24 -0
- data/lib/htm/loaders/markdown_chunker.rb +79 -0
- data/lib/htm/loaders/markdown_loader.rb +41 -15
- data/lib/htm/long_term_memory/fulltext_search.rb +138 -0
- data/lib/htm/long_term_memory/hybrid_search.rb +324 -0
- data/lib/htm/long_term_memory/node_operations.rb +209 -0
- data/lib/htm/long_term_memory/relevance_scorer.rb +355 -0
- data/lib/htm/long_term_memory/robot_operations.rb +34 -0
- data/lib/htm/long_term_memory/tag_operations.rb +428 -0
- data/lib/htm/long_term_memory/vector_search.rb +109 -0
- data/lib/htm/long_term_memory.rb +51 -1153
- data/lib/htm/models/node.rb +35 -2
- data/lib/htm/models/node_tag.rb +31 -0
- data/lib/htm/models/robot_node.rb +31 -0
- data/lib/htm/models/tag.rb +44 -0
- data/lib/htm/proposition_service.rb +169 -0
- data/lib/htm/query_cache.rb +214 -0
- data/lib/htm/sql_builder.rb +178 -0
- data/lib/htm/tag_service.rb +16 -6
- data/lib/htm/tasks.rb +8 -2
- data/lib/htm/telemetry.rb +224 -0
- data/lib/htm/version.rb +1 -1
- data/lib/htm.rb +64 -3
- data/lib/tasks/doc.rake +1 -1
- data/lib/tasks/htm.rake +259 -13
- data/mkdocs.yml +96 -96
- metadata +75 -18
- data/.aigcm_msg +0 -1
- data/.claude/settings.local.json +0 -92
- data/CLAUDE.md +0 -603
- data/examples/cli_app/temp.log +0 -93
- data/lib/htm/loaders/paragraph_chunker.rb +0 -112
- data/notes/ARCHITECTURE_REVIEW.md +0 -1167
- data/notes/IMPLEMENTATION_SUMMARY.md +0 -606
- data/notes/MULTI_FRAMEWORK_IMPLEMENTATION.md +0 -451
- data/notes/next_steps.md +0 -100
- data/notes/plan.md +0 -627
- data/notes/tag_ontology_enhancement_ideas.md +0 -222
- data/notes/timescaledb_removal_summary.md +0 -200
|
@@ -1,1167 +0,0 @@
|
|
|
1
|
-
# HTM Architecture Review
|
|
2
|
-
|
|
3
|
-
**Review Date**: 2025-10-25
|
|
4
|
-
**HTM Version**: 0.1.0
|
|
5
|
-
**Reviewers**: Multi-disciplinary architecture team
|
|
6
|
-
|
|
7
|
-
## Executive Summary
|
|
8
|
-
|
|
9
|
-
HTM (Hierarchical Temporary Memory) implements a sophisticated two-tier memory system for LLM-based applications. This review evaluates the architecture from six specialist perspectives, identifying strengths, weaknesses, and recommendations for improvement.
|
|
10
|
-
|
|
11
|
-
**Overall Assessment**: ⭐⭐⭐⭐ (4/5)
|
|
12
|
-
|
|
13
|
-
The architecture demonstrates solid design fundamentals with well-documented decisions via ADRs. Key strengths include the two-tier memory model, RAG-based retrieval, and hive mind capabilities. Primary areas for improvement include connection pooling, error handling, and thread safety.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## 1. Systems Architect Perspective
|
|
18
|
-
|
|
19
|
-
**Reviewer**: Systems Architecture Specialist
|
|
20
|
-
|
|
21
|
-
### Strengths
|
|
22
|
-
|
|
23
|
-
#### ✅ Two-Tier Memory Model (ADR-002)
|
|
24
|
-
The separation of hot working memory and cold long-term storage is architecturally sound:
|
|
25
|
-
- Clear responsibility separation (`lib/htm.rb:41-363`, `lib/htm/working_memory.rb`, `lib/htm/long_term_memory.rb`)
|
|
26
|
-
- Appropriate technology choices for each tier
|
|
27
|
-
- Well-defined eviction strategy
|
|
28
|
-
|
|
29
|
-
#### ✅ Component Cohesion
|
|
30
|
-
Each class has a single, clear responsibility:
|
|
31
|
-
- `HTM` - Coordination layer (`lib/htm.rb:41`)
|
|
32
|
-
- `WorkingMemory` - Token-limited active context (`lib/htm/working_memory.rb:9`)
|
|
33
|
-
- `LongTermMemory` - Persistent storage (`lib/htm/long_term_memory.rb:17`)
|
|
34
|
-
- `EmbeddingService` - Vector generation (`lib/htm/embedding_service.rb:15`)
|
|
35
|
-
- `Database` - Schema management (`lib/htm/database.rb:9`)
|
|
36
|
-
|
|
37
|
-
#### ✅ Hive Mind Architecture (ADR-004)
|
|
38
|
-
Shared global memory with robot attribution enables cross-robot learning:
|
|
39
|
-
- Robot registration (`lib/htm.rb:294-296`)
|
|
40
|
-
- Activity tracking (`lib/htm.rb:298-300`)
|
|
41
|
-
- Multi-robot queries (`lib/htm.rb:253-290`)
|
|
42
|
-
|
|
43
|
-
### Concerns
|
|
44
|
-
|
|
45
|
-
#### ⚠️ No Connection Pooling
|
|
46
|
-
**Location**: `lib/htm/long_term_memory.rb:315-325`
|
|
47
|
-
|
|
48
|
-
```ruby
|
|
49
|
-
def with_connection
|
|
50
|
-
conn = PG.connect(@config)
|
|
51
|
-
result = yield(conn)
|
|
52
|
-
conn.close
|
|
53
|
-
result
|
|
54
|
-
end
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
**Issue**: Creates a new database connection for every operation. Under load, this will:
|
|
58
|
-
- Exhaust connection limits
|
|
59
|
-
- Introduce latency (connection handshake overhead)
|
|
60
|
-
- Waste resources (connection creation/teardown)
|
|
61
|
-
|
|
62
|
-
**Recommendation**: Implement connection pooling
|
|
63
|
-
|
|
64
|
-
```ruby
|
|
65
|
-
require 'connection_pool'
|
|
66
|
-
|
|
67
|
-
class LongTermMemory
|
|
68
|
-
def initialize(config)
|
|
69
|
-
@pool = ConnectionPool.new(size: 5, timeout: 5) do
|
|
70
|
-
PG.connect(config)
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def with_connection
|
|
75
|
-
@pool.with { |conn| yield(conn) }
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
#### ⚠️ Synchronous Embedding Generation
|
|
81
|
-
**Location**: `lib/htm.rb:84-126`
|
|
82
|
-
|
|
83
|
-
Every `add_node` call blocks on embedding generation:
|
|
84
|
-
```ruby
|
|
85
|
-
def add_node(key, value, ...)
|
|
86
|
-
embedding = @embedding_service.embed(value) # Blocks here
|
|
87
|
-
# ...
|
|
88
|
-
end
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
For large text or slow embedding services, this creates latency.
|
|
92
|
-
|
|
93
|
-
**Recommendation**: Consider async embedding with job queue for non-critical paths
|
|
94
|
-
|
|
95
|
-
#### ⚠️ No Circuit Breaker Pattern
|
|
96
|
-
**Location**: `lib/htm/embedding_service.rb:70-108`
|
|
97
|
-
|
|
98
|
-
Ollama failures fall back to random vectors with only a warning:
|
|
99
|
-
```ruby
|
|
100
|
-
rescue => e
|
|
101
|
-
warn "Error generating embedding with Ollama: #{e.message}"
|
|
102
|
-
Array.new(1536) { rand(-1.0..1.0) } # Random fallback
|
|
103
|
-
end
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
**Issue**: Silent degradation - the system continues with meaningless embeddings.
|
|
107
|
-
|
|
108
|
-
**Recommendation**: Implement circuit breaker or explicit failure mode
|
|
109
|
-
|
|
110
|
-
### Architecture Scalability
|
|
111
|
-
|
|
112
|
-
| Aspect | Current State | Scalability Limit | Recommendation |
|
|
113
|
-
|--------|---------------|-------------------|----------------|
|
|
114
|
-
| **Working Memory** | Per-process in-memory | ~2GB RAM | ✅ Acceptable (process-local is intentional) |
|
|
115
|
-
| **Database Reads** | No connection pool | ~100 concurrent connections | ⚠️ Add connection pooling |
|
|
116
|
-
| **Database Writes** | No batching | ~1000 ops/sec | ⚠️ Consider batch inserts for bulk operations |
|
|
117
|
-
| **Embeddings** | Synchronous, per-request | ~10 req/sec (Ollama bottleneck) | ⚠️ Add async queue for high-throughput scenarios |
|
|
118
|
-
|
|
119
|
-
### Recommendations
|
|
120
|
-
|
|
121
|
-
1. **High Priority**: Implement connection pooling
|
|
122
|
-
2. **Medium Priority**: Add circuit breaker for embedding service
|
|
123
|
-
3. **Low Priority**: Consider async embedding for high-throughput scenarios
|
|
124
|
-
4. **Documentation**: Add deployment architecture diagram
|
|
125
|
-
|
|
126
|
-
---
|
|
127
|
-
|
|
128
|
-
## 2. Database Architect Perspective
|
|
129
|
-
|
|
130
|
-
**Reviewer**: PostgreSQL/TimescaleDB Specialist
|
|
131
|
-
|
|
132
|
-
### Strengths
|
|
133
|
-
|
|
134
|
-
#### ✅ TimescaleDB Integration (ADR-001)
|
|
135
|
-
Excellent choice for time-series workloads:
|
|
136
|
-
- Hypertable partitioning (`lib/htm/database.rb:143-153`)
|
|
137
|
-
- Automatic compression (`lib/htm/database.rb:165-177`)
|
|
138
|
-
- Time-range query optimization
|
|
139
|
-
|
|
140
|
-
**Schema** (`sql/schema.sql:8-22`):
|
|
141
|
-
```sql
|
|
142
|
-
CREATE TABLE nodes (
|
|
143
|
-
id BIGSERIAL PRIMARY KEY,
|
|
144
|
-
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
145
|
-
embedding vector(1536),
|
|
146
|
-
...
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
-- Hypertable conversion
|
|
150
|
-
SELECT create_hypertable('nodes', 'created_at', if_not_exists => TRUE);
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
#### ✅ Comprehensive Indexing
|
|
154
|
-
Well-thought-out index strategy (`sql/schema.sql:64-95`):
|
|
155
|
-
- **B-tree**: Standard queries (created_at, robot_id, type)
|
|
156
|
-
- **HNSW**: Vector similarity (`idx_nodes_embedding`)
|
|
157
|
-
- **GIN**: Full-text search (`idx_nodes_value_gin`)
|
|
158
|
-
- **GIN Trigram**: Fuzzy matching (`idx_nodes_value_trgm`)
|
|
159
|
-
|
|
160
|
-
The HNSW index configuration is appropriate:
|
|
161
|
-
```sql
|
|
162
|
-
CREATE INDEX idx_nodes_embedding ON nodes
|
|
163
|
-
USING hnsw (embedding vector_cosine_ops)
|
|
164
|
-
WITH (m = 16, ef_construction = 64);
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
#### ✅ Referential Integrity
|
|
168
|
-
Proper foreign key constraints with cascade rules:
|
|
169
|
-
```sql
|
|
170
|
-
CREATE TABLE relationships (
|
|
171
|
-
from_node_id BIGINT REFERENCES nodes(id) ON DELETE CASCADE,
|
|
172
|
-
to_node_id BIGINT REFERENCES nodes(id) ON DELETE CASCADE,
|
|
173
|
-
...
|
|
174
|
-
);
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
### Concerns
|
|
178
|
-
|
|
179
|
-
#### ⚠️ Missing Index on `in_working_memory`
|
|
180
|
-
**Location**: `sql/schema.sql:70`
|
|
181
|
-
|
|
182
|
-
```sql
|
|
183
|
-
CREATE INDEX idx_nodes_in_working_memory ON nodes(in_working_memory);
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
**Issue**: This index exists but the column is never queried in the codebase. Either:
|
|
187
|
-
1. The index is unused (wasted space/write overhead)
|
|
188
|
-
2. There's a missing query that should use it
|
|
189
|
-
|
|
190
|
-
**Recommendation**: Audit index usage or remove if unused
|
|
191
|
-
|
|
192
|
-
#### ⚠️ No Partitioning Strategy for Large Datasets
|
|
193
|
-
**Location**: Hypertable setup doesn't specify chunk interval
|
|
194
|
-
|
|
195
|
-
```sql
|
|
196
|
-
SELECT create_hypertable('nodes', 'created_at',
|
|
197
|
-
if_not_exists => TRUE,
|
|
198
|
-
migrate_data => TRUE
|
|
199
|
-
);
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
**Missing**: `chunk_time_interval` parameter. Default is 7 days, which may not be optimal.
|
|
203
|
-
|
|
204
|
-
**Recommendation**: Explicitly set chunk interval based on query patterns:
|
|
205
|
-
|
|
206
|
-
```sql
|
|
207
|
-
SELECT create_hypertable('nodes', 'created_at',
|
|
208
|
-
chunk_time_interval => INTERVAL '30 days', -- Explicit
|
|
209
|
-
if_not_exists => TRUE
|
|
210
|
-
);
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
#### ⚠️ Hybrid Search Performance
|
|
214
|
-
**Location**: `lib/htm/long_term_memory.rb:159-182`
|
|
215
|
-
|
|
216
|
-
The hybrid search uses a CTE (Common Table Expression):
|
|
217
|
-
```sql
|
|
218
|
-
WITH candidates AS (
|
|
219
|
-
SELECT * FROM nodes
|
|
220
|
-
WHERE to_tsvector(...) @@ plainto_tsquery(...)
|
|
221
|
-
LIMIT $5 -- prefilter_limit
|
|
222
|
-
)
|
|
223
|
-
SELECT ... FROM candidates
|
|
224
|
-
ORDER BY embedding <=> $4::vector
|
|
225
|
-
LIMIT $6
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
**Concern**: For large result sets, the `prefilter_limit` parameter (default 100) may:
|
|
229
|
-
- Miss relevant results if full-text search returns many matches
|
|
230
|
-
- Perform poorly if full-text returns few matches (unnecessary CTE overhead)
|
|
231
|
-
|
|
232
|
-
**Recommendation**: Add EXPLAIN ANALYZE tests for different `prefilter_limit` values
|
|
233
|
-
|
|
234
|
-
#### ⚠️ No Query Timeout
|
|
235
|
-
**Location**: All database queries lack timeouts
|
|
236
|
-
|
|
237
|
-
Long-running queries can block the system indefinitely.
|
|
238
|
-
|
|
239
|
-
**Recommendation**: Add statement timeout:
|
|
240
|
-
|
|
241
|
-
```ruby
|
|
242
|
-
def with_connection
|
|
243
|
-
@pool.with do |conn|
|
|
244
|
-
conn.exec("SET statement_timeout = '30s'") # Global timeout
|
|
245
|
-
yield(conn)
|
|
246
|
-
end
|
|
247
|
-
end
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
### Schema Normalization Analysis
|
|
251
|
-
|
|
252
|
-
| Table | Normal Form | Notes |
|
|
253
|
-
|-------|-------------|-------|
|
|
254
|
-
| `nodes` | 3NF | ✅ Well-normalized |
|
|
255
|
-
| `relationships` | 3NF | ✅ Properly normalized |
|
|
256
|
-
| `tags` | 3NF | ✅ Many-to-many via junction table |
|
|
257
|
-
| `operations_log` | 3NF | ✅ JSONB used appropriately for flexible metadata |
|
|
258
|
-
|
|
259
|
-
### Recommendations
|
|
260
|
-
|
|
261
|
-
1. **High Priority**: Add query timeouts
|
|
262
|
-
2. **High Priority**: Audit and optimize hybrid search `prefilter_limit`
|
|
263
|
-
3. **Medium Priority**: Explicitly set TimescaleDB chunk intervals
|
|
264
|
-
4. **Low Priority**: Review `in_working_memory` index usage
|
|
265
|
-
5. **Monitoring**: Add slow query logging and EXPLAIN ANALYZE for critical paths
|
|
266
|
-
|
|
267
|
-
---
|
|
268
|
-
|
|
269
|
-
## 3. AI Engineer Perspective
|
|
270
|
-
|
|
271
|
-
**Reviewer**: Machine Learning Infrastructure Specialist
|
|
272
|
-
|
|
273
|
-
### Strengths
|
|
274
|
-
|
|
275
|
-
#### ✅ RAG Pattern Implementation (ADR-005)
|
|
276
|
-
Solid implementation of retrieval-augmented generation:
|
|
277
|
-
- Temporal filtering (time-range queries)
|
|
278
|
-
- Semantic search (vector similarity)
|
|
279
|
-
- Full-text search (keyword matching)
|
|
280
|
-
- Hybrid approach (combines both)
|
|
281
|
-
|
|
282
|
-
**Hybrid Search** (`lib/htm/long_term_memory.rb:159-182`):
|
|
283
|
-
```ruby
|
|
284
|
-
def search_hybrid(timeframe:, query:, limit:, embedding_service:)
|
|
285
|
-
# 1. Full-text prefilter (narrows search space)
|
|
286
|
-
# 2. Vector similarity on candidates
|
|
287
|
-
# = Best of both worlds
|
|
288
|
-
end
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
#### ✅ Embedding Abstraction (ADR-003)
|
|
292
|
-
Provider-agnostic design supports multiple embedding services:
|
|
293
|
-
- Ollama (default, local)
|
|
294
|
-
- OpenAI (cloud, production)
|
|
295
|
-
- Cohere (cloud alternative)
|
|
296
|
-
- Local transformers (edge deployment)
|
|
297
|
-
|
|
298
|
-
**Interface** (`lib/htm/embedding_service.rb:41-54`):
|
|
299
|
-
```ruby
|
|
300
|
-
def embed(text)
|
|
301
|
-
case @provider
|
|
302
|
-
when :ollama then embed_ollama(text)
|
|
303
|
-
when :openai then embed_openai(text)
|
|
304
|
-
# ...
|
|
305
|
-
end
|
|
306
|
-
end
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
#### ✅ Importance Scoring
|
|
310
|
-
Explicit importance weighting enables better context assembly:
|
|
311
|
-
```ruby
|
|
312
|
-
def assemble_context(strategy:)
|
|
313
|
-
case strategy
|
|
314
|
-
when :balanced
|
|
315
|
-
# Hybrid: importance × time-decay
|
|
316
|
-
@nodes.sort_by { |k, v|
|
|
317
|
-
recency = Time.now - v[:added_at]
|
|
318
|
-
-(v[:importance] * (1.0 / (1 + recency / 3600.0)))
|
|
319
|
-
}
|
|
320
|
-
end
|
|
321
|
-
end
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
### Concerns
|
|
325
|
-
|
|
326
|
-
#### ⚠️ Fixed Embedding Dimensions
|
|
327
|
-
**Location**: `sql/schema.sql:21`, `lib/htm/embedding_service.rb:107,126,132,138`
|
|
328
|
-
|
|
329
|
-
All embeddings assumed to be 1536 dimensions (OpenAI text-embedding-3-small):
|
|
330
|
-
```sql
|
|
331
|
-
embedding vector(1536) -- Hard-coded
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
**Issue**: Other models use different dimensions:
|
|
335
|
-
- `nomic-embed-text`: 768 dimensions
|
|
336
|
-
- `text-embedding-3-large`: 3072 dimensions
|
|
337
|
-
- `gpt-oss`: **Dimension unknown** - defaults to 1536 but may be incorrect
|
|
338
|
-
|
|
339
|
-
**Recommendation**: Make embedding dimensions configurable:
|
|
340
|
-
|
|
341
|
-
```ruby
|
|
342
|
-
class EmbeddingService
|
|
343
|
-
DIMENSIONS = {
|
|
344
|
-
'gpt-oss' => 768, # Verify this!
|
|
345
|
-
'text-embedding-3-small' => 1536,
|
|
346
|
-
'text-embedding-3-large' => 3072,
|
|
347
|
-
'nomic-embed-text' => 768
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
def initialize(provider, model:)
|
|
351
|
-
@dimensions = DIMENSIONS.fetch(model) do
|
|
352
|
-
raise "Unknown embedding dimensions for model: #{model}"
|
|
353
|
-
end
|
|
354
|
-
end
|
|
355
|
-
end
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
#### ⚠️ No Embedding Caching
|
|
359
|
-
**Location**: `lib/htm.rb:84-126`
|
|
360
|
-
|
|
361
|
-
Every `add_node` call generates a new embedding, even for duplicate text.
|
|
362
|
-
|
|
363
|
-
**Recommendation**: Add embedding cache:
|
|
364
|
-
|
|
365
|
-
```ruby
|
|
366
|
-
class EmbeddingService
|
|
367
|
-
def initialize(...)
|
|
368
|
-
@cache = {} # or Redis for distributed cache
|
|
369
|
-
end
|
|
370
|
-
|
|
371
|
-
def embed(text)
|
|
372
|
-
cache_key = Digest::SHA256.hexdigest(text)
|
|
373
|
-
@cache[cache_key] ||= embed_uncached(text)
|
|
374
|
-
end
|
|
375
|
-
end
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
#### ⚠️ Similarity Score Not Returned
|
|
379
|
-
**Location**: `lib/htm.rb:136-178`
|
|
380
|
-
|
|
381
|
-
The `recall` method doesn't return similarity scores:
|
|
382
|
-
```ruby
|
|
383
|
-
def recall(timeframe:, topic:, limit:)
|
|
384
|
-
nodes = @long_term_memory.search(...)
|
|
385
|
-
# nodes contain similarity scores, but they're not exposed to caller
|
|
386
|
-
nodes
|
|
387
|
-
end
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
**Issue**: Caller can't:
|
|
391
|
-
- Filter low-relevance results
|
|
392
|
-
- Implement confidence thresholds
|
|
393
|
-
- Debug retrieval quality
|
|
394
|
-
|
|
395
|
-
**Recommendation**: Return structured results with scores:
|
|
396
|
-
|
|
397
|
-
```ruby
|
|
398
|
-
def recall(...)
|
|
399
|
-
nodes.map do |node|
|
|
400
|
-
{
|
|
401
|
-
node: node,
|
|
402
|
-
similarity: node['similarity'], # Expose score
|
|
403
|
-
retrieval_method: strategy
|
|
404
|
-
}
|
|
405
|
-
end
|
|
406
|
-
end
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
#### ⚠️ No Re-ranking
|
|
410
|
-
**Location**: Hybrid search uses simple approaches without re-ranking
|
|
411
|
-
|
|
412
|
-
Modern RAG systems often use cross-encoder re-ranking for better precision.
|
|
413
|
-
|
|
414
|
-
**Recommendation**: Consider adding optional re-ranking stage for production use
|
|
415
|
-
|
|
416
|
-
### Embedding Quality Analysis
|
|
417
|
-
|
|
418
|
-
| Aspect | Current State | Recommendation |
|
|
419
|
-
|--------|---------------|----------------|
|
|
420
|
-
| **Model Choice** | gpt-oss (Ollama) | ⚠️ Verify dimensions and quality vs alternatives |
|
|
421
|
-
| **Dimension Handling** | Fixed 1536 | ⚠️ Make configurable per model |
|
|
422
|
-
| **Caching** | None | ⚠️ Add for duplicate text |
|
|
423
|
-
| **Normalization** | Handled by pgvector | ✅ Good |
|
|
424
|
-
| **Distance Metric** | Cosine similarity | ✅ Appropriate for semantic search |
|
|
425
|
-
|
|
426
|
-
### Recommendations
|
|
427
|
-
|
|
428
|
-
1. **High Priority**: Fix embedding dimension handling
|
|
429
|
-
2. **High Priority**: Return similarity scores from `recall`
|
|
430
|
-
3. **Medium Priority**: Add embedding caching
|
|
431
|
-
4. **Low Priority**: Benchmark gpt-oss vs other models
|
|
432
|
-
5. **Low Priority**: Consider re-ranking for production
|
|
433
|
-
|
|
434
|
-
---
|
|
435
|
-
|
|
436
|
-
## 4. Performance Specialist Perspective
|
|
437
|
-
|
|
438
|
-
**Reviewer**: System Performance Engineer
|
|
439
|
-
|
|
440
|
-
### Strengths
|
|
441
|
-
|
|
442
|
-
#### ✅ Working Memory Performance
|
|
443
|
-
O(1) hash-based lookups and efficient token counting:
|
|
444
|
-
```ruby
|
|
445
|
-
class WorkingMemory
|
|
446
|
-
def initialize(max_tokens:)
|
|
447
|
-
@nodes = {} # O(1) access
|
|
448
|
-
@access_order = [] # LRU tracking
|
|
449
|
-
end
|
|
450
|
-
end
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
#### ✅ HNSW Index for Vector Search
|
|
454
|
-
Approximate nearest neighbor search with good recall/speed tradeoff:
|
|
455
|
-
```sql
|
|
456
|
-
USING hnsw (embedding vector_cosine_ops)
|
|
457
|
-
WITH (m = 16, ef_construction = 64)
|
|
458
|
-
```
|
|
459
|
-
|
|
460
|
-
**Expected Performance**:
|
|
461
|
-
- m=16: Good balance (16 neighbors per layer)
|
|
462
|
-
- ef_construction=64: Moderate build time, good quality
|
|
463
|
-
|
|
464
|
-
### Concerns
|
|
465
|
-
|
|
466
|
-
#### ⚠️ Eviction Algorithm Complexity
|
|
467
|
-
**Location**: `lib/htm/working_memory.rb:66-86`
|
|
468
|
-
|
|
469
|
-
```ruby
|
|
470
|
-
def evict_to_make_space(needed_tokens)
|
|
471
|
-
candidates = @nodes.sort_by do |key, node|
|
|
472
|
-
recency = Time.now - node[:added_at]
|
|
473
|
-
[node[:importance], -recency]
|
|
474
|
-
end
|
|
475
|
-
# ...
|
|
476
|
-
end
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
**Complexity**: O(n log n) where n = number of nodes in working memory
|
|
480
|
-
|
|
481
|
-
**Issue**: For large working memories (>1000 nodes), sorting on every eviction is expensive.
|
|
482
|
-
|
|
483
|
-
**Recommendation**: Maintain a priority queue (min-heap) for O(log n) evictions:
|
|
484
|
-
|
|
485
|
-
```ruby
|
|
486
|
-
require 'priority_queue'
|
|
487
|
-
|
|
488
|
-
class WorkingMemory
|
|
489
|
-
def initialize(max_tokens:)
|
|
490
|
-
@eviction_queue = PriorityQueue.new # Sorted by eviction score
|
|
491
|
-
end
|
|
492
|
-
|
|
493
|
-
def evict_to_make_space(needed_tokens)
|
|
494
|
-
# Pop from heap: O(log n) per eviction
|
|
495
|
-
evicted = []
|
|
496
|
-
tokens_freed = 0
|
|
497
|
-
while tokens_freed < needed_tokens
|
|
498
|
-
key, score = @eviction_queue.pop
|
|
499
|
-
# ...
|
|
500
|
-
end
|
|
501
|
-
end
|
|
502
|
-
end
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
#### ⚠️ Context Assembly Sorts Every Time
|
|
506
|
-
**Location**: `lib/htm/working_memory.rb:94-122`
|
|
507
|
-
|
|
508
|
-
```ruby
|
|
509
|
-
def assemble_context(strategy:, max_tokens:)
|
|
510
|
-
nodes = case strategy
|
|
511
|
-
when :balanced
|
|
512
|
-
@nodes.sort_by { ... } # Full sort every time
|
|
513
|
-
end
|
|
514
|
-
end
|
|
515
|
-
```
|
|
516
|
-
|
|
517
|
-
**Issue**: If context is assembled frequently (every LLM call), this is O(n log n) overhead.
|
|
518
|
-
|
|
519
|
-
**Recommendation**: Cache sorted results and invalidate on modification
|
|
520
|
-
|
|
521
|
-
#### ⚠️ No Query Result Caching
|
|
522
|
-
**Location**: `lib/htm/long_term_memory.rb:106-123`
|
|
523
|
-
|
|
524
|
-
Identical queries hit the database every time:
|
|
525
|
-
```ruby
|
|
526
|
-
def search(timeframe:, query:, limit:, embedding_service:)
|
|
527
|
-
query_embedding = embedding_service.embed(query) # Expensive
|
|
528
|
-
with_connection do |conn|
|
|
529
|
-
conn.exec_params(...) # Database round-trip
|
|
530
|
-
end
|
|
531
|
-
end
|
|
532
|
-
```
|
|
533
|
-
|
|
534
|
-
**Recommendation**: Add LRU cache for recent queries:
|
|
535
|
-
|
|
536
|
-
```ruby
|
|
537
|
-
require 'lru_redux'
|
|
538
|
-
|
|
539
|
-
class LongTermMemory
|
|
540
|
-
def initialize(config)
|
|
541
|
-
@query_cache = LruRedux::ThreadSafeCache.new(1000)
|
|
542
|
-
end
|
|
543
|
-
|
|
544
|
-
def search(...)
|
|
545
|
-
cache_key = "#{timeframe}/#{query}/#{limit}"
|
|
546
|
-
@query_cache.getset(cache_key) do
|
|
547
|
-
search_uncached(...)
|
|
548
|
-
end
|
|
549
|
-
end
|
|
550
|
-
end
|
|
551
|
-
```
|
|
552
|
-
|
|
553
|
-
### Performance Benchmark Estimates
|
|
554
|
-
|
|
555
|
-
| Operation | Current Performance | Optimized Performance | Notes |
|
|
556
|
-
|-----------|---------------------|----------------------|-------|
|
|
557
|
-
| **add_node** | ~100ms | ~50ms | With connection pooling |
|
|
558
|
-
| **recall (vector)** | ~200ms | ~100ms | With query caching |
|
|
559
|
-
| **recall (hybrid)** | ~250ms | ~150ms | With query caching |
|
|
560
|
-
| **evict** | ~10ms (100 nodes) | ~2ms | With priority queue |
|
|
561
|
-
| **assemble_context** | ~5ms (100 nodes) | ~0.5ms | With caching |
|
|
562
|
-
|
|
563
|
-
### Recommendations
|
|
564
|
-
|
|
565
|
-
1. **High Priority**: Add connection pooling (5x improvement on DB ops)
|
|
566
|
-
2. **High Priority**: Implement query result caching
|
|
567
|
-
3. **Medium Priority**: Optimize eviction with priority queue
|
|
568
|
-
4. **Medium Priority**: Cache sorted context assembly results
|
|
569
|
-
5. **Monitoring**: Add performance instrumentation (StatsD/Prometheus)
|
|
570
|
-
|
|
571
|
-
---
|
|
572
|
-
|
|
573
|
-
## 5. Ruby Expert Perspective
|
|
574
|
-
|
|
575
|
-
**Reviewer**: Ruby Best Practices Specialist
|
|
576
|
-
|
|
577
|
-
### Strengths
|
|
578
|
-
|
|
579
|
-
#### ✅ Idiomatic Ruby Style
|
|
580
|
-
Code follows Ruby community conventions:
|
|
581
|
-
- Keyword arguments for clarity
|
|
582
|
-
- Symbols for enums
|
|
583
|
-
- Proper attr_reader usage
|
|
584
|
-
- Frozen string literals
|
|
585
|
-
|
|
586
|
-
**Example** (`lib/htm.rb:53-71`):
|
|
587
|
-
```ruby
|
|
588
|
-
def initialize(
|
|
589
|
-
working_memory_size: 128_000, # Keyword arg with default
|
|
590
|
-
robot_id: nil,
|
|
591
|
-
robot_name: nil,
|
|
592
|
-
embedding_service: :ollama # Symbol for enum
|
|
593
|
-
)
|
|
594
|
-
@robot_id = robot_id || SecureRandom.uuid # Idiomatic fallback
|
|
595
|
-
# ...
|
|
596
|
-
end
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
#### ✅ Clear Method Signatures
|
|
600
|
-
Methods have descriptive names and appropriate arity:
|
|
601
|
-
```ruby
|
|
602
|
-
def add_node(key, value, type: nil, category: nil, importance: 1.0, ...)
|
|
603
|
-
def recall(timeframe:, topic:, limit: 20, strategy: :vector)
|
|
604
|
-
def forget(key, confirm: false) # Safety through explicit confirmation
|
|
605
|
-
```
|
|
606
|
-
|
|
607
|
-
#### ✅ Proper Encapsulation
|
|
608
|
-
Private methods used appropriately:
|
|
609
|
-
```ruby
|
|
610
|
-
private
|
|
611
|
-
|
|
612
|
-
def register_robot
|
|
613
|
-
# ...
|
|
614
|
-
end
|
|
615
|
-
|
|
616
|
-
def add_to_working_memory(node)
|
|
617
|
-
# ...
|
|
618
|
-
end
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
### Concerns
|
|
622
|
-
|
|
623
|
-
#### ⚠️ No Thread Safety
|
|
624
|
-
**Location**: All classes lack synchronization
|
|
625
|
-
|
|
626
|
-
```ruby
|
|
627
|
-
class WorkingMemory
|
|
628
|
-
def add(key, value, ...)
|
|
629
|
-
@nodes[key] = { ... } # Not thread-safe
|
|
630
|
-
update_access(key) # Modifies @access_order
|
|
631
|
-
end
|
|
632
|
-
end
|
|
633
|
-
```
|
|
634
|
-
|
|
635
|
-
**Issue**: Concurrent access from multiple threads will cause:
|
|
636
|
-
- Race conditions in `@nodes` hash
|
|
637
|
-
- Corrupted `@access_order` array
|
|
638
|
-
- Incorrect token counts
|
|
639
|
-
|
|
640
|
-
**Recommendation**: Add explicit documentation or thread-safety:
|
|
641
|
-
|
|
642
|
-
```ruby
|
|
643
|
-
# Option 1: Document thread-unsafety
|
|
644
|
-
class WorkingMemory
|
|
645
|
-
# NOT THREAD-SAFE: Use one instance per thread or add external synchronization
|
|
646
|
-
def initialize(max_tokens:)
|
|
647
|
-
# ...
|
|
648
|
-
end
|
|
649
|
-
end
|
|
650
|
-
|
|
651
|
-
# Option 2: Add mutex for thread-safety
|
|
652
|
-
class WorkingMemory
|
|
653
|
-
def initialize(max_tokens:)
|
|
654
|
-
@mutex = Mutex.new
|
|
655
|
-
@nodes = {}
|
|
656
|
-
end
|
|
657
|
-
|
|
658
|
-
def add(key, value, ...)
|
|
659
|
-
@mutex.synchronize do
|
|
660
|
-
@nodes[key] = { ... }
|
|
661
|
-
end
|
|
662
|
-
end
|
|
663
|
-
end
|
|
664
|
-
```
|
|
665
|
-
|
|
666
|
-
#### ⚠️ Exception Handling Inconsistency
|
|
667
|
-
**Location**: Different error handling strategies across classes
|
|
668
|
-
|
|
669
|
-
**EmbeddingService** (`lib/htm/embedding_service.rb:103-108`):
|
|
670
|
-
```ruby
|
|
671
|
-
rescue => e
|
|
672
|
-
warn "Error: #{e.message}"
|
|
673
|
-
Array.new(1536) { rand } # Silent fallback
|
|
674
|
-
end
|
|
675
|
-
```
|
|
676
|
-
|
|
677
|
-
**LongTermMemory** (`lib/htm/long_term_memory.rb:322-325`):
|
|
678
|
-
```ruby
|
|
679
|
-
rescue => e
|
|
680
|
-
conn&.close
|
|
681
|
-
raise e # Re-raises
|
|
682
|
-
end
|
|
683
|
-
```
|
|
684
|
-
|
|
685
|
-
**Inconsistency**: Some methods fail silently, others raise exceptions.
|
|
686
|
-
|
|
687
|
-
**Recommendation**: Define error handling policy:
|
|
688
|
-
- Critical operations: Raise custom exceptions
|
|
689
|
-
- Optional features: Return nil or Result monad
|
|
690
|
-
- Never: Silent fallback with random data
|
|
691
|
-
|
|
692
|
-
#### ⚠️ Mutable Default Arguments
|
|
693
|
-
**Location**: No instances found, but worth noting for future development
|
|
694
|
-
|
|
695
|
-
Ruby gotcha to avoid:
|
|
696
|
-
```ruby
|
|
697
|
-
# BAD
|
|
698
|
-
def add_tags(node_id, tags = [])
|
|
699
|
-
tags << "default" # Mutates shared default!
|
|
700
|
-
end
|
|
701
|
-
|
|
702
|
-
# GOOD
|
|
703
|
-
def add_tags(node_id, tags: [])
|
|
704
|
-
tags = tags.dup # Defensive copy
|
|
705
|
-
# ...
|
|
706
|
-
end
|
|
707
|
-
```
|
|
708
|
-
|
|
709
|
-
Current code doesn't have this issue ✅
|
|
710
|
-
|
|
711
|
-
#### ⚠️ Missing Return Value Documentation
|
|
712
|
-
**Location**: Several methods unclear about return values
|
|
713
|
-
|
|
714
|
-
```ruby
|
|
715
|
-
def mark_evicted(keys)
|
|
716
|
-
return if keys.empty?
|
|
717
|
-
# ...
|
|
718
|
-
# Returns what? nil? true? count of rows updated?
|
|
719
|
-
end
|
|
720
|
-
```
|
|
721
|
-
|
|
722
|
-
**Recommendation**: Use YARD comments consistently:
|
|
723
|
-
|
|
724
|
-
```ruby
|
|
725
|
-
# Mark nodes as evicted from working memory
|
|
726
|
-
#
|
|
727
|
-
# @param keys [Array<String>] Node keys
|
|
728
|
-
# @return [void]
|
|
729
|
-
def mark_evicted(keys)
|
|
730
|
-
# ...
|
|
731
|
-
end
|
|
732
|
-
```
|
|
733
|
-
|
|
734
|
-
### Ruby Patterns Analysis
|
|
735
|
-
|
|
736
|
-
| Pattern | Usage | Recommendation |
|
|
737
|
-
|---------|-------|----------------|
|
|
738
|
-
| **Dependency Injection** | ✅ Used (embedding_service, db_config) | Good |
|
|
739
|
-
| **Factory Pattern** | ⚠️ Not used | Consider for embedding service creation |
|
|
740
|
-
| **Observer Pattern** | ❌ Not used | Could be useful for memory events |
|
|
741
|
-
| **Result Objects** | ❌ Not used | Would improve error handling |
|
|
742
|
-
| **Value Objects** | ❌ Not used | Could wrap timeframes, embeddings |
|
|
743
|
-
|
|
744
|
-
### Recommendations
|
|
745
|
-
|
|
746
|
-
1. **High Priority**: Document thread-safety guarantees (or lack thereof)
|
|
747
|
-
2. **High Priority**: Standardize error handling approach
|
|
748
|
-
3. **Medium Priority**: Add comprehensive YARD documentation
|
|
749
|
-
4. **Low Priority**: Consider introducing Result objects for operations that can fail
|
|
750
|
-
5. **Code Quality**: Add RuboCop configuration for consistent style
|
|
751
|
-
|
|
752
|
-
---
|
|
753
|
-
|
|
754
|
-
## 6. Security Specialist Perspective
|
|
755
|
-
|
|
756
|
-
**Reviewer**: Application Security Engineer
|
|
757
|
-
|
|
758
|
-
### Strengths
|
|
759
|
-
|
|
760
|
-
#### ✅ SQL Injection Protection
|
|
761
|
-
All queries use parameterized statements:
|
|
762
|
-
```ruby
|
|
763
|
-
conn.exec_params(
|
|
764
|
-
"INSERT INTO nodes (...) VALUES ($1, $2, $3, ...)",
|
|
765
|
-
[key, value, type, ...] # ✅ Parameters, not string interpolation
|
|
766
|
-
)
|
|
767
|
-
```
|
|
768
|
-
|
|
769
|
-
✅ **No instances of string interpolation in SQL queries found**
|
|
770
|
-
|
|
771
|
-
#### ✅ Explicit Deletion Confirmation (ADR-009)
|
|
772
|
-
The `forget` method requires explicit confirmation:
|
|
773
|
-
```ruby
|
|
774
|
-
def forget(key, confirm: false)
|
|
775
|
-
raise ArgumentError, "Must pass confirm: :confirmed" unless confirm == :confirmed
|
|
776
|
-
# ...
|
|
777
|
-
end
|
|
778
|
-
```
|
|
779
|
-
|
|
780
|
-
Prevents accidental data loss ✅
|
|
781
|
-
|
|
782
|
-
#### ✅ SSL/TLS Configuration
|
|
783
|
-
Database connection supports SSL:
|
|
784
|
-
```ruby
|
|
785
|
-
def parse_connection_url(url)
|
|
786
|
-
{
|
|
787
|
-
sslmode: params['sslmode'] || 'prefer' # SSL by default
|
|
788
|
-
}
|
|
789
|
-
end
|
|
790
|
-
```
|
|
791
|
-
|
|
792
|
-
### Concerns
|
|
793
|
-
|
|
794
|
-
#### ⚠️ Database Credentials in Environment Variables
|
|
795
|
-
**Location**: `lib/htm/database.rb:79-87`
|
|
796
|
-
|
|
797
|
-
```ruby
|
|
798
|
-
def default_config
|
|
799
|
-
if ENV['HTM_DBURL']
|
|
800
|
-
parse_connection_url(ENV['HTM_DBURL'])
|
|
801
|
-
elsif ENV['HTM_DBNAME']
|
|
802
|
-
{
|
|
803
|
-
password: ENV['HTM_DBPASS'], # Password in env var
|
|
804
|
-
# ...
|
|
805
|
-
}
|
|
806
|
-
end
|
|
807
|
-
end
|
|
808
|
-
```
|
|
809
|
-
|
|
810
|
-
**Issue**: Environment variables are:
|
|
811
|
-
- Visible in process listings (`ps aux`)
|
|
812
|
-
- Logged in various places
|
|
813
|
-
- Inherited by child processes
|
|
814
|
-
|
|
815
|
-
**Recommendation**: Support external secret management:
|
|
816
|
-
|
|
817
|
-
```ruby
|
|
818
|
-
def default_config
|
|
819
|
-
# Option 1: Read from file
|
|
820
|
-
if File.exist?('/run/secrets/db_password')
|
|
821
|
-
password = File.read('/run/secrets/db_password').strip
|
|
822
|
-
end
|
|
823
|
-
|
|
824
|
-
# Option 2: Use secret management service
|
|
825
|
-
if ENV['USE_AWS_SECRETS_MANAGER']
|
|
826
|
-
password = AwsSecretsManager.get('htm/db_password')
|
|
827
|
-
end
|
|
828
|
-
|
|
829
|
-
# Option 3: Fall back to env var for development
|
|
830
|
-
password ||= ENV['HTM_DBPASS']
|
|
831
|
-
end
|
|
832
|
-
```
|
|
833
|
-
|
|
834
|
-
#### ⚠️ No Input Validation
|
|
835
|
-
**Location**: Public methods lack input sanitization
|
|
836
|
-
|
|
837
|
-
```ruby
|
|
838
|
-
def add_node(key, value, type: nil, ...)
|
|
839
|
-
# No validation:
|
|
840
|
-
# - key length? (could exceed column size)
|
|
841
|
-
# - value length? (could be gigabytes)
|
|
842
|
-
# - type enum? (any string accepted)
|
|
843
|
-
# - importance range? (could be negative or huge)
|
|
844
|
-
|
|
845
|
-
@long_term_memory.add(key: key, value: value, ...)
|
|
846
|
-
end
|
|
847
|
-
```
|
|
848
|
-
|
|
849
|
-
**Risks**:
|
|
850
|
-
- DoS via large inputs
|
|
851
|
-
- Database errors from invalid types
|
|
852
|
-
- Undefined behavior from out-of-range values
|
|
853
|
-
|
|
854
|
-
**Recommendation**: Add input validation:
|
|
855
|
-
|
|
856
|
-
```ruby
|
|
857
|
-
class HTM
|
|
858
|
-
MAX_KEY_LENGTH = 255
|
|
859
|
-
MAX_VALUE_LENGTH = 1_000_000 # 1MB
|
|
860
|
-
VALID_TYPES = [:fact, :context, :code, :preference, :decision, :question]
|
|
861
|
-
|
|
862
|
-
def add_node(key, value, type: nil, importance: 1.0, ...)
|
|
863
|
-
validate_key!(key)
|
|
864
|
-
validate_value!(value)
|
|
865
|
-
validate_type!(type) if type
|
|
866
|
-
validate_importance!(importance)
|
|
867
|
-
|
|
868
|
-
# ...
|
|
869
|
-
end
|
|
870
|
-
|
|
871
|
-
private
|
|
872
|
-
|
|
873
|
-
def validate_key!(key)
|
|
874
|
-
raise ArgumentError, "Key cannot be nil" if key.nil?
|
|
875
|
-
raise ArgumentError, "Key too long (max #{MAX_KEY_LENGTH})" if key.length > MAX_KEY_LENGTH
|
|
876
|
-
raise ArgumentError, "Key cannot be empty" if key.empty?
|
|
877
|
-
end
|
|
878
|
-
|
|
879
|
-
def validate_importance!(importance)
|
|
880
|
-
raise ArgumentError, "Importance must be 0.0-10.0" unless (0.0..10.0).cover?(importance)
|
|
881
|
-
end
|
|
882
|
-
end
|
|
883
|
-
```
|
|
884
|
-
|
|
885
|
-
#### ⚠️ Embedding Service Failures Silently Degrade
|
|
886
|
-
**Location**: `lib/htm/embedding_service.rb:103-108`
|
|
887
|
-
|
|
888
|
-
```ruby
|
|
889
|
-
rescue => e
|
|
890
|
-
warn "Error generating embedding with Ollama: #{e.message}"
|
|
891
|
-
warn "Falling back to stub embeddings (random vectors)"
|
|
892
|
-
Array.new(1536) { rand(-1.0..1.0) } # ⚠️ Security/reliability issue
|
|
893
|
-
end
|
|
894
|
-
```
|
|
895
|
-
|
|
896
|
-
**Security Issue**: Random embeddings break semantic search, allowing:
|
|
897
|
-
- Incorrect memory retrieval
|
|
898
|
-
- Potential information leakage (wrong context returned)
|
|
899
|
-
- Silent data corruption
|
|
900
|
-
|
|
901
|
-
**Recommendation**: Fail loudly or implement proper fallback:
|
|
902
|
-
|
|
903
|
-
```ruby
|
|
904
|
-
rescue => e
|
|
905
|
-
if @allow_fallback
|
|
906
|
-
warn "CRITICAL: Embedding service failure, using fallback"
|
|
907
|
-
Array.new(1536) { 0.0 } # Zero vector (at least deterministic)
|
|
908
|
-
else
|
|
909
|
-
raise EmbeddingServiceError, "Failed to generate embedding: #{e.message}"
|
|
910
|
-
end
|
|
911
|
-
end
|
|
912
|
-
```
|
|
913
|
-
|
|
914
|
-
#### ⚠️ No Rate Limiting
|
|
915
|
-
**Location**: All public methods lack rate limiting
|
|
916
|
-
|
|
917
|
-
In a multi-robot environment, a compromised or misconfigured robot could:
|
|
918
|
-
- Flood the system with `add_node` calls
|
|
919
|
-
- Execute expensive searches continuously
|
|
920
|
-
- Exhaust database resources
|
|
921
|
-
|
|
922
|
-
**Recommendation**: Add rate limiting:
|
|
923
|
-
|
|
924
|
-
```ruby
|
|
925
|
-
require 'redis'
|
|
926
|
-
require 'redis-throttle'
|
|
927
|
-
|
|
928
|
-
class HTM
|
|
929
|
-
def initialize(...)
|
|
930
|
-
@throttle = Redis::Throttle.new(
|
|
931
|
-
key: "htm:#{@robot_id}",
|
|
932
|
-
limit: 100, # 100 operations
|
|
933
|
-
period: 60 # per minute
|
|
934
|
-
)
|
|
935
|
-
end
|
|
936
|
-
|
|
937
|
-
def add_node(key, value, ...)
|
|
938
|
-
@throttle.exceeded? and raise RateLimitExceeded
|
|
939
|
-
|
|
940
|
-
# ...
|
|
941
|
-
end
|
|
942
|
-
end
|
|
943
|
-
```
|
|
944
|
-
|
|
945
|
-
#### ⚠️ Operations Log Contains Potentially Sensitive Data
|
|
946
|
-
**Location**: `lib/htm.rb:116-122`
|
|
947
|
-
|
|
948
|
-
```ruby
|
|
949
|
-
@long_term_memory.log_operation(
|
|
950
|
-
operation: 'add',
|
|
951
|
-
node_id: node_id,
|
|
952
|
-
robot_id: @robot_id,
|
|
953
|
-
details: { key: key, type: type } # What if key contains sensitive data?
|
|
954
|
-
)
|
|
955
|
-
```
|
|
956
|
-
|
|
957
|
-
**Issue**: The `operations_log` table stores details in JSONB, which might include:
|
|
958
|
-
- Sensitive keys
|
|
959
|
-
- Query terms revealing confidential information
|
|
960
|
-
- User identifiers
|
|
961
|
-
|
|
962
|
-
**Recommendation**: Add PII scrubbing for audit logs
|
|
963
|
-
|
|
964
|
-
### Security Checklist
|
|
965
|
-
|
|
966
|
-
| Concern | Status | Priority |
|
|
967
|
-
|---------|--------|----------|
|
|
968
|
-
| SQL Injection | ✅ Protected | - |
|
|
969
|
-
| XSS | N/A (no web interface) | - |
|
|
970
|
-
| CSRF | N/A (no web interface) | - |
|
|
971
|
-
| Authentication | ❌ Not implemented | Low (out of scope) |
|
|
972
|
-
| Authorization | ❌ Not implemented | Medium (robot access control) |
|
|
973
|
-
| Input Validation | ⚠️ Missing | High |
|
|
974
|
-
| Output Encoding | ✅ Handled by pg gem | - |
|
|
975
|
-
| Secret Management | ⚠️ Env vars only | Medium |
|
|
976
|
-
| Rate Limiting | ❌ Not implemented | Medium |
|
|
977
|
-
| Audit Logging | ✅ Implemented | - |
|
|
978
|
-
| PII Handling | ⚠️ Not scrubbed | Medium |
|
|
979
|
-
|
|
980
|
-
### Recommendations
|
|
981
|
-
|
|
982
|
-
1. **High Priority**: Add input validation for all public methods
|
|
983
|
-
2. **High Priority**: Fail loudly on embedding service failures (no random fallbacks)
|
|
984
|
-
3. **Medium Priority**: Implement robot-level authorization
|
|
985
|
-
4. **Medium Priority**: Add rate limiting
|
|
986
|
-
5. **Medium Priority**: Support external secret management (Docker secrets, Vault, etc.)
|
|
987
|
-
6. **Low Priority**: Add PII scrubbing for audit logs
|
|
988
|
-
7. **Documentation**: Add security considerations section to docs
|
|
989
|
-
|
|
990
|
-
---
|
|
991
|
-
|
|
992
|
-
## Cross-Cutting Concerns
|
|
993
|
-
|
|
994
|
-
### Observability
|
|
995
|
-
|
|
996
|
-
**Current State**: Minimal observability
|
|
997
|
-
|
|
998
|
-
**Issues**:
|
|
999
|
-
- No structured logging
|
|
1000
|
-
- No metrics collection (request rates, latencies, errors)
|
|
1001
|
-
- No distributed tracing
|
|
1002
|
-
- No health checks
|
|
1003
|
-
|
|
1004
|
-
**Recommendations**:
|
|
1005
|
-
|
|
1006
|
-
```ruby
|
|
1007
|
-
require 'logger'
|
|
1008
|
-
require 'statsd'
|
|
1009
|
-
|
|
1010
|
-
class HTM
|
|
1011
|
-
def initialize(...)
|
|
1012
|
-
@logger = Logger.new(STDOUT)
|
|
1013
|
-
@logger.formatter = JsonFormatter.new # Structured logging
|
|
1014
|
-
|
|
1015
|
-
@metrics = Statsd.new('localhost', 8125)
|
|
1016
|
-
end
|
|
1017
|
-
|
|
1018
|
-
def add_node(key, value, ...)
|
|
1019
|
-
start_time = Time.now
|
|
1020
|
-
|
|
1021
|
-
@logger.info("Adding node", {
|
|
1022
|
-
robot_id: @robot_id,
|
|
1023
|
-
key: key,
|
|
1024
|
-
type: type
|
|
1025
|
-
})
|
|
1026
|
-
|
|
1027
|
-
begin
|
|
1028
|
-
# ... operation ...
|
|
1029
|
-
|
|
1030
|
-
@metrics.increment('htm.add_node.success')
|
|
1031
|
-
@metrics.histogram('htm.add_node.duration', Time.now - start_time)
|
|
1032
|
-
rescue => e
|
|
1033
|
-
@logger.error("Failed to add node", {
|
|
1034
|
-
error: e.class.name,
|
|
1035
|
-
message: e.message,
|
|
1036
|
-
backtrace: e.backtrace[0..5]
|
|
1037
|
-
})
|
|
1038
|
-
@metrics.increment('htm.add_node.error')
|
|
1039
|
-
raise
|
|
1040
|
-
end
|
|
1041
|
-
end
|
|
1042
|
-
end
|
|
1043
|
-
```
|
|
1044
|
-
|
|
1045
|
-
### Testing
|
|
1046
|
-
|
|
1047
|
-
**Current State**: Basic test coverage (from conversation summary: "2 errors, 3 failures")
|
|
1048
|
-
|
|
1049
|
-
**Recommendations**:
|
|
1050
|
-
1. Add integration tests for database operations
|
|
1051
|
-
2. Add property-based tests for eviction algorithm
|
|
1052
|
-
3. Add benchmark suite for performance regression detection
|
|
1053
|
-
4. Mock embedding service to avoid network dependency in tests
|
|
1054
|
-
5. Add load tests for concurrent access patterns
|
|
1055
|
-
|
|
1056
|
-
### Documentation
|
|
1057
|
-
|
|
1058
|
-
**Current State**: Excellent ADR documentation, comprehensive guides
|
|
1059
|
-
|
|
1060
|
-
**Strengths**:
|
|
1061
|
-
- 9 ADRs covering major decisions
|
|
1062
|
-
- 37 documentation files
|
|
1063
|
-
- API documentation with examples
|
|
1064
|
-
|
|
1065
|
-
**Gaps**:
|
|
1066
|
-
- No performance tuning guide
|
|
1067
|
-
- No security hardening guide
|
|
1068
|
-
- No troubleshooting guide
|
|
1069
|
-
- No operational runbook
|
|
1070
|
-
|
|
1071
|
-
---
|
|
1072
|
-
|
|
1073
|
-
## Priority Matrix
|
|
1074
|
-
|
|
1075
|
-
### High Priority (Address in next release)
|
|
1076
|
-
|
|
1077
|
-
1. **Connection Pooling** (Performance + Scalability)
|
|
1078
|
-
- Implement in `LongTermMemory`
|
|
1079
|
-
- Add configuration for pool size
|
|
1080
|
-
- Estimated effort: 4 hours
|
|
1081
|
-
|
|
1082
|
-
2. **Input Validation** (Security)
|
|
1083
|
-
- Add validators for all public methods
|
|
1084
|
-
- Define clear constraints
|
|
1085
|
-
- Estimated effort: 8 hours
|
|
1086
|
-
|
|
1087
|
-
3. **Embedding Dimension Configuration** (AI/Correctness)
|
|
1088
|
-
- Make dimensions configurable
|
|
1089
|
-
- Verify gpt-oss actual dimensions
|
|
1090
|
-
- Estimated effort: 4 hours
|
|
1091
|
-
|
|
1092
|
-
4. **Query Timeouts** (Database Reliability)
|
|
1093
|
-
- Add statement timeout
|
|
1094
|
-
- Make configurable
|
|
1095
|
-
- Estimated effort: 2 hours
|
|
1096
|
-
|
|
1097
|
-
5. **Error Handling Standardization** (Ruby Best Practices)
|
|
1098
|
-
- Define error handling policy
|
|
1099
|
-
- Remove random embedding fallback
|
|
1100
|
-
- Estimated effort: 6 hours
|
|
1101
|
-
|
|
1102
|
-
**Total High Priority Effort**: ~24 hours (3 days)
|
|
1103
|
-
|
|
1104
|
-
### Medium Priority (Next milestone)
|
|
1105
|
-
|
|
1106
|
-
1. **Query Result Caching** (Performance)
|
|
1107
|
-
2. **Circuit Breaker for Embeddings** (Systems)
|
|
1108
|
-
3. **Robot Authorization** (Security)
|
|
1109
|
-
4. **Observability Infrastructure** (Operations)
|
|
1110
|
-
5. **Eviction Algorithm Optimization** (Performance)
|
|
1111
|
-
|
|
1112
|
-
### Low Priority (Future consideration)
|
|
1113
|
-
|
|
1114
|
-
1. **Async Embedding Generation** (Scalability)
|
|
1115
|
-
2. **Re-ranking for RAG** (AI Quality)
|
|
1116
|
-
3. **Thread-Safety** (If multi-threaded use case emerges)
|
|
1117
|
-
4. **Factory Pattern for Services** (Ruby Design Patterns)
|
|
1118
|
-
|
|
1119
|
-
---
|
|
1120
|
-
|
|
1121
|
-
## Conclusion
|
|
1122
|
-
|
|
1123
|
-
HTM demonstrates a solid architectural foundation with well-documented decisions and appropriate technology choices. The two-tier memory model, RAG-based retrieval, and hive mind capabilities are particularly strong.
|
|
1124
|
-
|
|
1125
|
-
**Key Strengths**:
|
|
1126
|
-
- Excellent architecture documentation (ADRs)
|
|
1127
|
-
- Sound database design with TimescaleDB
|
|
1128
|
-
- Clean separation of concerns
|
|
1129
|
-
- Idiomatic Ruby code
|
|
1130
|
-
|
|
1131
|
-
**Primary Gaps**:
|
|
1132
|
-
- Connection pooling for database operations
|
|
1133
|
-
- Input validation and error handling
|
|
1134
|
-
- Embedding dimension configuration
|
|
1135
|
-
- Observability and monitoring
|
|
1136
|
-
|
|
1137
|
-
**Recommendation**: Address high-priority items in the next release to improve production-readiness, then systematically work through medium and low priority improvements.
|
|
1138
|
-
|
|
1139
|
-
**Overall Architecture Grade**: **A- (Strong foundation with clear improvement path)**
|
|
1140
|
-
|
|
1141
|
-
---
|
|
1142
|
-
|
|
1143
|
-
## Appendix: Code Quality Metrics
|
|
1144
|
-
|
|
1145
|
-
### Complexity Analysis
|
|
1146
|
-
|
|
1147
|
-
| Class | Lines | Methods | Avg Method Length | Complexity |
|
|
1148
|
-
|-------|-------|---------|-------------------|------------|
|
|
1149
|
-
| `HTM` | 363 | 15 | 24 | Medium |
|
|
1150
|
-
| `WorkingMemory` | 159 | 10 | 16 | Low |
|
|
1151
|
-
| `LongTermMemory` | 327 | 20 | 16 | Medium |
|
|
1152
|
-
| `EmbeddingService` | 141 | 7 | 20 | Low |
|
|
1153
|
-
| `Database` | 184 | 7 | 26 | Medium |
|
|
1154
|
-
|
|
1155
|
-
### Test Coverage
|
|
1156
|
-
- Current: Unknown (tests exist but coverage not measured)
|
|
1157
|
-
- Recommendation: Add SimpleCov, target 80%+ coverage
|
|
1158
|
-
|
|
1159
|
-
### Dependencies
|
|
1160
|
-
- Core: `pg`, `pgvector`, `connection_pool`, `tiktoken_ruby`, `ruby_llm`
|
|
1161
|
-
- All dependencies are actively maintained ✅
|
|
1162
|
-
- No known security vulnerabilities ✅
|
|
1163
|
-
|
|
1164
|
-
---
|
|
1165
|
-
|
|
1166
|
-
**Review Complete**: 2025-10-25
|
|
1167
|
-
**Next Review**: After high-priority improvements implemented
|