htm 0.0.11 → 0.0.15

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.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/.dictate.toml +46 -0
  3. data/.envrc +2 -0
  4. data/CHANGELOG.md +85 -2
  5. data/README.md +348 -79
  6. data/Rakefile +14 -2
  7. data/bin/htm_mcp.rb +94 -0
  8. data/config/database.yml +20 -13
  9. data/db/migrate/00003_create_file_sources.rb +5 -0
  10. data/db/migrate/00004_create_nodes.rb +17 -0
  11. data/db/migrate/00005_create_tags.rb +7 -0
  12. data/db/migrate/00006_create_node_tags.rb +2 -0
  13. data/db/migrate/00007_create_robot_nodes.rb +7 -0
  14. data/db/schema.sql +69 -100
  15. data/docs/api/index.md +1 -1
  16. data/docs/api/yard/HTM/Configuration.md +54 -0
  17. data/docs/api/yard/HTM/Database.md +13 -10
  18. data/docs/api/yard/HTM/EmbeddingService.md +5 -1
  19. data/docs/api/yard/HTM/LongTermMemory.md +18 -277
  20. data/docs/api/yard/HTM/PropositionError.md +18 -0
  21. data/docs/api/yard/HTM/PropositionService.md +66 -0
  22. data/docs/api/yard/HTM/QueryCache.md +88 -0
  23. data/docs/api/yard/HTM/RobotGroup.md +481 -0
  24. data/docs/api/yard/HTM/SqlBuilder.md +108 -0
  25. data/docs/api/yard/HTM/TagService.md +4 -0
  26. data/docs/api/yard/HTM/Telemetry/NullInstrument.md +13 -0
  27. data/docs/api/yard/HTM/Telemetry/NullMeter.md +15 -0
  28. data/docs/api/yard/HTM/Telemetry.md +109 -0
  29. data/docs/api/yard/HTM/WorkingMemoryChannel.md +176 -0
  30. data/docs/api/yard/HTM.md +8 -22
  31. data/docs/api/yard/index.csv +102 -25
  32. data/docs/api/yard-reference.md +8 -0
  33. data/docs/architecture/index.md +1 -1
  34. data/docs/assets/images/multi-provider-failover.svg +51 -0
  35. data/docs/assets/images/robot-group-architecture.svg +65 -0
  36. data/docs/database/README.md +3 -3
  37. data/docs/database/public.file_sources.svg +29 -21
  38. data/docs/database/public.node_tags.md +2 -0
  39. data/docs/database/public.node_tags.svg +53 -41
  40. data/docs/database/public.nodes.md +2 -0
  41. data/docs/database/public.nodes.svg +52 -40
  42. data/docs/database/public.robot_nodes.md +2 -0
  43. data/docs/database/public.robot_nodes.svg +30 -22
  44. data/docs/database/public.robots.svg +16 -12
  45. data/docs/database/public.tags.md +3 -0
  46. data/docs/database/public.tags.svg +41 -33
  47. data/docs/database/schema.json +66 -0
  48. data/docs/database/schema.svg +60 -48
  49. data/docs/development/index.md +14 -1
  50. data/docs/development/rake-tasks.md +1068 -0
  51. data/docs/getting-started/index.md +1 -1
  52. data/docs/getting-started/quick-start.md +144 -155
  53. data/docs/guides/adding-memories.md +2 -3
  54. data/docs/guides/context-assembly.md +185 -184
  55. data/docs/guides/getting-started.md +154 -148
  56. data/docs/guides/index.md +8 -1
  57. data/docs/guides/long-term-memory.md +60 -92
  58. data/docs/guides/mcp-server.md +617 -0
  59. data/docs/guides/multi-robot.md +249 -345
  60. data/docs/guides/recalling-memories.md +153 -163
  61. data/docs/guides/robot-groups.md +604 -0
  62. data/docs/guides/search-strategies.md +61 -58
  63. data/docs/guides/working-memory.md +103 -136
  64. data/docs/images/telemetry-architecture.svg +153 -0
  65. data/docs/index.md +30 -26
  66. data/docs/telemetry.md +391 -0
  67. data/examples/README.md +46 -1
  68. data/examples/cli_app/README.md +1 -1
  69. data/examples/cli_app/htm_cli.rb +1 -1
  70. data/examples/robot_groups/robot_worker.rb +1 -2
  71. data/examples/robot_groups/same_process.rb +1 -4
  72. data/examples/sinatra_app/app.rb +1 -1
  73. data/examples/telemetry/README.md +147 -0
  74. data/examples/telemetry/SETUP_README.md +169 -0
  75. data/examples/telemetry/demo.rb +498 -0
  76. data/examples/telemetry/grafana/dashboards/htm-metrics.json +457 -0
  77. data/lib/htm/configuration.rb +261 -70
  78. data/lib/htm/database.rb +46 -22
  79. data/lib/htm/embedding_service.rb +24 -14
  80. data/lib/htm/errors.rb +15 -1
  81. data/lib/htm/jobs/generate_embedding_job.rb +19 -0
  82. data/lib/htm/jobs/generate_propositions_job.rb +103 -0
  83. data/lib/htm/jobs/generate_tags_job.rb +24 -0
  84. data/lib/htm/loaders/markdown_chunker.rb +79 -0
  85. data/lib/htm/loaders/markdown_loader.rb +41 -15
  86. data/lib/htm/long_term_memory/fulltext_search.rb +138 -0
  87. data/lib/htm/long_term_memory/hybrid_search.rb +324 -0
  88. data/lib/htm/long_term_memory/node_operations.rb +209 -0
  89. data/lib/htm/long_term_memory/relevance_scorer.rb +355 -0
  90. data/lib/htm/long_term_memory/robot_operations.rb +34 -0
  91. data/lib/htm/long_term_memory/tag_operations.rb +428 -0
  92. data/lib/htm/long_term_memory/vector_search.rb +109 -0
  93. data/lib/htm/long_term_memory.rb +51 -1153
  94. data/lib/htm/models/node.rb +35 -2
  95. data/lib/htm/models/node_tag.rb +31 -0
  96. data/lib/htm/models/robot_node.rb +31 -0
  97. data/lib/htm/models/tag.rb +44 -0
  98. data/lib/htm/proposition_service.rb +169 -0
  99. data/lib/htm/query_cache.rb +214 -0
  100. data/lib/htm/robot_group.rb +721 -0
  101. data/lib/htm/sql_builder.rb +178 -0
  102. data/lib/htm/tag_service.rb +16 -6
  103. data/lib/htm/tasks.rb +8 -2
  104. data/lib/htm/telemetry.rb +224 -0
  105. data/lib/htm/version.rb +1 -1
  106. data/lib/htm/working_memory_channel.rb +250 -0
  107. data/lib/htm.rb +66 -3
  108. data/lib/tasks/doc.rake +1 -1
  109. data/lib/tasks/htm.rake +259 -13
  110. data/mkdocs.yml +98 -96
  111. metadata +55 -20
  112. data/.aigcm_msg +0 -1
  113. data/.claude/settings.local.json +0 -95
  114. data/CLAUDE.md +0 -603
  115. data/db/migrate/00009_add_working_memory_to_robot_nodes.rb +0 -12
  116. data/examples/cli_app/temp.log +0 -93
  117. data/examples/robot_groups/lib/robot_group.rb +0 -419
  118. data/examples/robot_groups/lib/working_memory_channel.rb +0 -140
  119. data/lib/htm/loaders/paragraph_chunker.rb +0 -112
  120. data/notes/ARCHITECTURE_REVIEW.md +0 -1167
  121. data/notes/IMPLEMENTATION_SUMMARY.md +0 -606
  122. data/notes/MULTI_FRAMEWORK_IMPLEMENTATION.md +0 -451
  123. data/notes/next_steps.md +0 -100
  124. data/notes/plan.md +0 -627
  125. data/notes/tag_ontology_enhancement_ideas.md +0 -222
  126. 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