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
@@ -0,0 +1,481 @@
1
+ # Class: HTM::RobotGroup
2
+ **Inherits:** Object
3
+
4
+
5
+ Coordinates multiple robots with shared working memory and automatic failover.
6
+
7
+ RobotGroup provides application-level coordination for multiple HTM robots,
8
+ enabling them to share a common working memory context. Key capabilities
9
+ include:
10
+
11
+ * **Shared Working Memory**: All group members have access to the same
12
+ context
13
+ * **Active/Passive Roles**: Active robots participate in conversations;
14
+ passive robots maintain synchronized context for instant failover
15
+ * **Real-time Sync**: PostgreSQL LISTEN/NOTIFY enables immediate
16
+ synchronization
17
+ * **Failover**: When an active robot fails, a passive robot takes over
18
+ instantly
19
+ * **Dynamic Scaling**: Add or remove robots at runtime
20
+
21
+ **`@see`** [] Low-level pub/sub mechanism
22
+
23
+
24
+ **`@example`**
25
+ ```ruby
26
+ group = HTM::RobotGroup.new(
27
+ name: 'customer-support',
28
+ active: ['primary-agent'],
29
+ passive: ['standby-agent'],
30
+ max_tokens: 8000
31
+ )
32
+
33
+ # Add shared context
34
+ group.remember('Customer prefers email communication.')
35
+ group.remember('Open ticket #789 regarding billing issue.')
36
+
37
+ # Query shared memory
38
+ results = group.recall('billing', limit: 5)
39
+
40
+ # Simulate failover
41
+ group.failover! # Promotes standby to active
42
+
43
+ # Cleanup
44
+ group.shutdown
45
+ ```
46
+ # Attributes
47
+ ## channel[RW] {: #attribute-i-channel }
48
+ The pub/sub channel used for real-time synchronization
49
+
50
+ **`@return`** [HTM::WorkingMemoryChannel]
51
+
52
+ ## max_tokens[RW] {: #attribute-i-max_tokens }
53
+ Maximum token budget for working memory
54
+
55
+ **`@return`** [Integer]
56
+
57
+ ## name[RW] {: #attribute-i-name }
58
+ Name of the robot group
59
+
60
+ **`@return`** [String]
61
+
62
+
63
+ # Instance Methods
64
+ ## active?(robot_name) {: #method-i-active? }
65
+ Checks if a robot is an active member of this group.
66
+
67
+ **`@param`** [String] Name of the robot to check
68
+
69
+ **`@return`** [Boolean] true if the robot is an active member
70
+
71
+
72
+ **`@example`**
73
+ ```ruby
74
+ group.active?('primary-agent') # => true
75
+ ```
76
+ ## active_robot_names() {: #method-i-active_robot_names }
77
+ Returns names of all active robots.
78
+
79
+ **`@return`** [Array<String>] Array of active robot names
80
+
81
+
82
+ **`@example`**
83
+ ```ruby
84
+ group.active_robot_names # => ['primary-agent', 'secondary-agent']
85
+ ```
86
+ ## add_active(robot_name) {: #method-i-add_active }
87
+ Adds a robot as an active member of the group.
88
+
89
+ Active robots can add memories and respond to queries. The new robot is
90
+ automatically synchronized with existing shared working memory.
91
+
92
+ **`@param`** [String] Unique name for the robot
93
+
94
+ **`@raise`** [ArgumentError] if robot_name is already a member
95
+
96
+ **`@return`** [Integer] The robot's database ID
97
+
98
+
99
+ **`@example`**
100
+ ```ruby
101
+ robot_id = group.add_active('new-agent')
102
+ puts "Added robot with ID: #{robot_id}"
103
+ ```
104
+ ## add_passive(robot_name) {: #method-i-add_passive }
105
+ Adds a robot as a passive (standby) member of the group.
106
+
107
+ Passive robots maintain synchronized working memory but don't actively
108
+ participate in conversations. They serve as warm standbys for failover.
109
+
110
+ **`@param`** [String] Unique name for the robot
111
+
112
+ **`@raise`** [ArgumentError] if robot_name is already a member
113
+
114
+ **`@return`** [Integer] The robot's database ID
115
+
116
+
117
+ **`@example`**
118
+ ```ruby
119
+ robot_id = group.add_passive('standby-agent')
120
+ ```
121
+ ## clear_working_memory() {: #method-i-clear_working_memory }
122
+ Clears shared working memory for all group members.
123
+
124
+ Updates database flags and notifies all members to clear their in-memory
125
+ caches.
126
+
127
+ **`@return`** [Integer] Number of robot_node records updated
128
+
129
+
130
+ **`@example`**
131
+ ```ruby
132
+ cleared_count = group.clear_working_memory
133
+ puts "Cleared #{cleared_count} working memory entries"
134
+ ```
135
+ ## demote(robot_name) {: #method-i-demote }
136
+ Demotes an active robot to passive status.
137
+
138
+ The robot retains its working memory but stops handling queries. Cannot demote
139
+ the last active robot.
140
+
141
+ **`@param`** [String] Name of the active robot to demote
142
+
143
+ **`@raise`** [ArgumentError] if robot_name is not an active member
144
+
145
+ **`@raise`** [ArgumentError] if this is the last active robot
146
+
147
+ **`@return`** [void]
148
+
149
+
150
+ **`@example`**
151
+ ```ruby
152
+ group.demote('primary-agent')
153
+ group.passive?('primary-agent') # => true
154
+ ```
155
+ ## failover!() {: #method-i-failover! }
156
+ Performs automatic failover to the first passive robot.
157
+
158
+ Promotes the first passive robot to active status. The promoted robot already
159
+ has synchronized working memory and can immediately handle requests.
160
+
161
+ **`@raise`** [RuntimeError] if no passive robots are available
162
+
163
+ **`@return`** [String] Name of the promoted robot
164
+
165
+
166
+ **`@example`**
167
+ ```ruby
168
+ promoted = group.failover!
169
+ puts "#{promoted} is now active"
170
+ ```
171
+ ## in_sync?() {: #method-i-in_sync? }
172
+ Checks if all members have identical working memory.
173
+
174
+ Compares the set of working memory node IDs across all members.
175
+
176
+ **`@return`** [Boolean] true if all members have the same working memory nodes
177
+
178
+
179
+ **`@example`**
180
+ ```ruby
181
+ if group.in_sync?
182
+ puts "All robots synchronized"
183
+ else
184
+ group.sync_all
185
+ end
186
+ ```
187
+ ## initialize(name:, active:[], passive:[], max_tokens:4000, db_config:nil) {: #method-i-initialize }
188
+ Creates a new robot group with optional initial members.
189
+
190
+ Initializes the group, sets up the PostgreSQL pub/sub channel for real-time
191
+ synchronization, and registers initial active and passive robots.
192
+
193
+ **`@param`** [String] Unique name for this robot group
194
+
195
+ **`@param`** [Array<String>] Names of robots to add as active members
196
+
197
+ **`@param`** [Array<String>] Names of robots to add as passive (standby) members
198
+
199
+ **`@param`** [Integer] Maximum token budget for shared working memory
200
+
201
+ **`@param`** [Hash, nil] PostgreSQL connection config (defaults to HTM::Database.default_config)
202
+
203
+ **`@return`** [RobotGroup] a new instance of RobotGroup
204
+
205
+
206
+ **`@example`**
207
+ ```ruby
208
+ group = HTM::RobotGroup.new(
209
+ name: 'support-team',
210
+ active: ['agent-1'],
211
+ passive: ['agent-2'],
212
+ max_tokens: 4000
213
+ )
214
+ ```
215
+ **`@example`**
216
+ ```ruby
217
+ group = HTM::RobotGroup.new(name: 'dynamic-team')
218
+ group.add_active('agent-1')
219
+ group.add_passive('agent-2')
220
+ ```
221
+ ## member?(robot_name) {: #method-i-member? }
222
+ Checks if a robot is a member of this group.
223
+
224
+ **`@param`** [String] Name of the robot to check
225
+
226
+ **`@return`** [Boolean] true if the robot is an active or passive member
227
+
228
+
229
+ **`@example`**
230
+ ```ruby
231
+ group.member?('agent-1') # => true
232
+ group.member?('unknown') # => false
233
+ ```
234
+ ## member_ids() {: #method-i-member_ids }
235
+ Returns database IDs of all group members.
236
+
237
+ **`@return`** [Array<Integer>] Array of robot IDs (both active and passive)
238
+
239
+
240
+ **`@example`**
241
+ ```ruby
242
+ group.member_ids # => [1, 2, 3]
243
+ ```
244
+ ## passive?(robot_name) {: #method-i-passive? }
245
+ Checks if a robot is a passive member of this group.
246
+
247
+ **`@param`** [String] Name of the robot to check
248
+
249
+ **`@return`** [Boolean] true if the robot is a passive member
250
+
251
+
252
+ **`@example`**
253
+ ```ruby
254
+ group.passive?('standby-agent') # => true
255
+ ```
256
+ ## passive_robot_names() {: #method-i-passive_robot_names }
257
+ Returns names of all passive robots.
258
+
259
+ **`@return`** [Array<String>] Array of passive robot names
260
+
261
+
262
+ **`@example`**
263
+ ```ruby
264
+ group.passive_robot_names # => ['standby-agent']
265
+ ```
266
+ ## promote(robot_name) {: #method-i-promote }
267
+ Promotes a passive robot to active status.
268
+
269
+ The robot retains its synchronized working memory and becomes eligible to
270
+ handle queries and add memories.
271
+
272
+ **`@param`** [String] Name of the passive robot to promote
273
+
274
+ **`@raise`** [ArgumentError] if robot_name is not a passive member
275
+
276
+ **`@return`** [void]
277
+
278
+
279
+ **`@example`**
280
+ ```ruby
281
+ group.promote('standby-agent')
282
+ group.active?('standby-agent') # => true
283
+ ```
284
+ ## recall(query, **options) {: #method-i-recall }
285
+ Recalls memories from shared working memory.
286
+
287
+ Uses the first active robot to perform the query against the shared working
288
+ memory context.
289
+
290
+ **`@option`** []
291
+
292
+ **`@option`** []
293
+
294
+ **`@param`** [String] The search query
295
+
296
+ **`@param`** [Hash] Additional options passed to HTM#recall
297
+
298
+ **`@raise`** [RuntimeError] if no active robots exist in the group
299
+
300
+ **`@return`** [Array] Array of matching memories
301
+
302
+
303
+ **`@example`**
304
+ ```ruby
305
+ results = group.recall('billing issue', limit: 5, strategy: :fulltext)
306
+ ```
307
+ ## remember(content, originator:nil, **options) {: #method-i-remember }
308
+ Adds content to shared working memory for all group members.
309
+
310
+ The memory is created by the specified originator (or first active robot) and
311
+ automatically synchronized to all other members via database and real-time
312
+ notifications.
313
+
314
+ **`@param`** [String] The content to remember
315
+
316
+ **`@param`** [String, nil] Name of the robot creating the memory (optional)
317
+
318
+ **`@param`** [Hash] Additional options passed to HTM#remember
319
+
320
+ **`@raise`** [RuntimeError] if no active robots exist in the group
321
+
322
+ **`@return`** [Integer] The node ID of the created memory
323
+
324
+
325
+ **`@example`**
326
+ ```ruby
327
+ node_id = group.remember('Customer prefers morning appointments.')
328
+ ```
329
+ **`@example`**
330
+ ```ruby
331
+ node_id = group.remember(
332
+ 'Escalated to billing department.',
333
+ originator: 'agent-2'
334
+ )
335
+ ```
336
+ ## remove(robot_name) {: #method-i-remove }
337
+ Removes a robot from the group.
338
+
339
+ Clears the robot's working memory flags in the database. The robot can be
340
+ either active or passive.
341
+
342
+ **`@param`** [String] Name of the robot to remove
343
+
344
+ **`@return`** [void]
345
+
346
+
347
+ **`@example`**
348
+ ```ruby
349
+ group.remove('departing-agent')
350
+ ```
351
+ ## shutdown() {: #method-i-shutdown }
352
+ Shuts down the group by stopping the listener thread.
353
+
354
+ Should be called when the group is no longer needed to release resources and
355
+ close the PostgreSQL listener connection.
356
+
357
+ **`@return`** [void]
358
+
359
+
360
+ **`@example`**
361
+ ```ruby
362
+ group.shutdown
363
+ ```
364
+ ## status() {: #method-i-status }
365
+ Returns comprehensive status information about the group.
366
+
367
+ **`@option`** []
368
+
369
+ **`@option`** []
370
+
371
+ **`@option`** []
372
+
373
+ **`@option`** []
374
+
375
+ **`@option`** []
376
+
377
+ **`@option`** []
378
+
379
+ **`@option`** []
380
+
381
+ **`@option`** []
382
+
383
+ **`@option`** []
384
+
385
+ **`@param`** [Hash] a customizable set of options
386
+
387
+ **`@return`** [Hash] Status hash with the following keys:
388
+
389
+
390
+ **`@example`**
391
+ ```ruby
392
+ status = group.status
393
+ puts "Group: #{status[:name]}"
394
+ puts "Active: #{status[:active].join(', ')}"
395
+ puts "Utilization: #{(status[:token_utilization] * 100).round(1)}%"
396
+ ```
397
+ ## sync_all() {: #method-i-sync_all }
398
+ Synchronizes all members to a consistent state.
399
+
400
+ Ensures every member has access to all shared working memory nodes.
401
+
402
+ **`@return`** [Hash] Sync results with :synced_nodes and :members_updated counts
403
+
404
+
405
+ **`@example`**
406
+ ```ruby
407
+ result = group.sync_all
408
+ puts "Synced #{result[:synced_nodes]} nodes to #{result[:members_updated]} members"
409
+ ```
410
+ ## sync_robot(robot_name) {: #method-i-sync_robot }
411
+ Synchronizes a specific robot to match the group's shared working memory.
412
+
413
+ Copies working memory flags from other members to the specified robot,
414
+ ensuring it has access to all shared context.
415
+
416
+ **`@param`** [String] Name of the robot to synchronize
417
+
418
+ **`@raise`** [ArgumentError] if robot_name is not a member
419
+
420
+ **`@return`** [Integer] Number of nodes synchronized
421
+
422
+
423
+ **`@example`**
424
+ ```ruby
425
+ synced = group.sync_robot('new-agent')
426
+ puts "Synchronized #{synced} nodes"
427
+ ```
428
+ ## sync_stats() {: #method-i-sync_stats }
429
+ Returns statistics about real-time synchronization.
430
+
431
+ **`@return`** [Hash] Stats hash with :nodes_synced and :evictions_synced counts
432
+
433
+
434
+ **`@example`**
435
+ ```ruby
436
+ stats = group.sync_stats
437
+ puts "Nodes synced: #{stats[:nodes_synced]}"
438
+ puts "Evictions synced: #{stats[:evictions_synced]}"
439
+ ```
440
+ ## transfer_working_memory(from_robot, to_robot, clear_source:true) {: #method-i-transfer_working_memory }
441
+ Transfers working memory from one robot to another.
442
+
443
+ Copies all working memory node references from the source robot to the target
444
+ robot, optionally clearing the source.
445
+
446
+ **`@param`** [String] Name of the source robot
447
+
448
+ **`@param`** [String] Name of the destination robot
449
+
450
+ **`@param`** [Boolean] Whether to clear source's working memory after transfer
451
+
452
+ **`@raise`** [ArgumentError] if either robot is not a member
453
+
454
+ **`@return`** [Integer] Number of nodes transferred
455
+
456
+
457
+ **`@example`**
458
+ ```ruby
459
+ transferred = group.transfer_working_memory('failing-agent', 'backup-agent')
460
+ ```
461
+ **`@example`**
462
+ ```ruby
463
+ transferred = group.transfer_working_memory(
464
+ 'agent-1', 'agent-2',
465
+ clear_source: false
466
+ )
467
+ ```
468
+ ## working_memory_contents() {: #method-i-working_memory_contents }
469
+ Returns all nodes currently in shared working memory.
470
+
471
+ Queries the database for the union of all members' working memory, returning
472
+ nodes sorted by creation date (newest first).
473
+
474
+ **`@return`** [ActiveRecord::Relation<HTM::Models::Node>] Collection of nodes
475
+
476
+
477
+ **`@example`**
478
+ ```ruby
479
+ nodes = group.working_memory_contents
480
+ nodes.each { |n| puts n.content }
481
+ ```
@@ -0,0 +1,108 @@
1
+ # Class: HTM::SqlBuilder
2
+ **Inherits:** Object
3
+
4
+
5
+ SQL building utilities for constructing safe, parameterized queries
6
+
7
+ Provides class methods for building SQL conditions for:
8
+ * Timeframe filtering (single range or multiple ranges)
9
+ * Metadata filtering (JSONB containment)
10
+ * Embedding sanitization and padding (SQL injection prevention)
11
+ * LIKE pattern sanitization (wildcard injection prevention)
12
+
13
+ All methods use proper escaping and parameterization to prevent SQL injection.
14
+
15
+
16
+ **`@example`**
17
+ ```ruby
18
+ HTM::SqlBuilder.timeframe_condition(1.week.ago..Time.now)
19
+ # => "(created_at BETWEEN '2024-01-01' AND '2024-01-08')"
20
+ ```
21
+ **`@example`**
22
+ ```ruby
23
+ HTM::SqlBuilder.metadata_condition({ priority: "high" })
24
+ # => "(metadata @> '{\"priority\":\"high\"}'::jsonb)"
25
+ ```
26
+ **`@example`**
27
+ ```ruby
28
+ HTM::SqlBuilder.sanitize_embedding([0.1, 0.2, 0.3])
29
+ # => "[0.1,0.2,0.3]"
30
+ ```
31
+ **`@example`**
32
+ ```ruby
33
+ HTM::SqlBuilder.sanitize_like_pattern("test%pattern")
34
+ # => "test\\%pattern"
35
+ ```
36
+ # Class Methods
37
+ ## apply_metadata(scope , metadata , column: "metadata") {: #method-c-apply_metadata }
38
+ Apply metadata filter to ActiveRecord scope
39
+ **`@param`** [ActiveRecord::Relation] Base scope
40
+
41
+ **`@param`** [Hash] Metadata to filter by
42
+
43
+ **`@param`** [String] Column name (default: "metadata")
44
+
45
+ **`@return`** [ActiveRecord::Relation] Scoped query
46
+
47
+ ## apply_timeframe(scope , timeframe , column: :created_at) {: #method-c-apply_timeframe }
48
+ Apply timeframe filter to ActiveRecord scope
49
+ **`@param`** [ActiveRecord::Relation] Base scope
50
+
51
+ **`@param`** [nil, Range, Array<Range>] Time range(s)
52
+
53
+ **`@param`** [Symbol] Column name (default: :created_at)
54
+
55
+ **`@return`** [ActiveRecord::Relation] Scoped query
56
+
57
+ ## metadata_condition(metadata , table_alias: nil, column: "metadata") {: #method-c-metadata_condition }
58
+ Build SQL condition for metadata filtering (JSONB containment)
59
+ **`@param`** [Hash] Metadata to filter by
60
+
61
+ **`@param`** [String, nil] Table alias (default: none)
62
+
63
+ **`@param`** [String] Column name (default: "metadata")
64
+
65
+ **`@return`** [String, nil] SQL condition or nil for no filter
66
+
67
+ ## pad_embedding(embedding , target_dimension: MAX_EMBEDDING_DIMENSION) {: #method-c-pad_embedding }
68
+ Pad embedding to target dimension
69
+
70
+ Pads embedding with zeros to reach the target dimension for pgvector
71
+ compatibility.
72
+ **`@param`** [Array<Numeric>] Embedding vector
73
+
74
+ **`@param`** [Integer] Target dimension (default: MAX_EMBEDDING_DIMENSION)
75
+
76
+ **`@return`** [Array<Numeric>] Padded embedding
77
+
78
+ ## sanitize_embedding(embedding ) {: #method-c-sanitize_embedding }
79
+ Sanitize embedding for SQL use
80
+
81
+ Validates that all values are numeric and converts to safe PostgreSQL vector
82
+ format. This prevents SQL injection by ensuring only valid numeric values are
83
+ included.
84
+ **`@param`** [Array<Numeric>] Embedding vector
85
+
86
+ **`@raise`** [ArgumentError] If embedding contains non-numeric values
87
+
88
+ **`@return`** [String] Sanitized vector string for PostgreSQL (e.g., "[0.1,0.2,0.3]")
89
+
90
+ ## sanitize_like_pattern(pattern ) {: #method-c-sanitize_like_pattern }
91
+ Sanitize a string for use in SQL LIKE patterns
92
+
93
+ Escapes SQL LIKE wildcards (% and _) to prevent pattern injection.
94
+ **`@param`** [String] Pattern to sanitize
95
+
96
+ **`@return`** [String] Sanitized pattern safe for LIKE queries
97
+
98
+ ## timeframe_condition(timeframe , table_alias: nil, column: "created_at") {: #method-c-timeframe_condition }
99
+ Build SQL condition for timeframe filtering
100
+ **`@param`** [nil, Range, Array<Range>] Time range(s)
101
+
102
+ **`@param`** [String, nil] Table alias (default: none)
103
+
104
+ **`@param`** [String] Column name (default: "created_at")
105
+
106
+ **`@return`** [String, nil] SQL condition or nil for no filter
107
+
108
+
@@ -29,6 +29,10 @@ Extract tags with validation and processing
29
29
 
30
30
  **`@return`** [Array<String>] Validated tag names
31
31
 
32
+ ## max_depth() {: #method-c-max_depth }
33
+ Maximum tag hierarchy depth (configurable, default 4)
34
+ **`@return`** [Integer] Max depth (3 colons max by default)
35
+
32
36
  ## parse_hierarchy(tag ) {: #method-c-parse_hierarchy }
33
37
  Parse hierarchical structure of a tag
34
38
  **`@param`** [String] Hierarchical tag (e.g., "ai:llm:embedding")
@@ -0,0 +1,13 @@
1
+ # Class: HTM::Telemetry::NullInstrument
2
+ **Inherits:** Object
3
+
4
+ **Includes:** Singleton
5
+
6
+
7
+ Null instrument that accepts but ignores all metric operations
8
+
9
+
10
+
11
+ # Instance Methods
12
+ ## add() {: #method-i-add }
13
+ ## record() {: #method-i-record }
@@ -0,0 +1,15 @@
1
+ # Class: HTM::Telemetry::NullMeter
2
+ **Inherits:** Object
3
+
4
+ **Includes:** Singleton
5
+
6
+
7
+ Null meter that creates null instruments Used when telemetry is disabled or
8
+ SDK unavailable
9
+
10
+
11
+
12
+ # Instance Methods
13
+ ## create_counter() {: #method-i-create_counter }
14
+ ## create_histogram() {: #method-i-create_histogram }
15
+ ## create_up_down_counter() {: #method-i-create_up_down_counter }