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,498 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # HTM Telemetry Demo with Grafana Visualization
5
+ #
6
+ # This demo shows HTM metrics in a live Grafana dashboard using
7
+ # locally installed Prometheus and Grafana (via Homebrew).
8
+ #
9
+ # Prerequisites:
10
+ # brew install grafana prometheus
11
+ # gem install prometheus-client webrick
12
+ #
13
+ # Usage:
14
+ # cd examples/telemetry
15
+ # ruby demo.rb
16
+ #
17
+ # The demo will:
18
+ # 1. Check/start Prometheus and Grafana
19
+ # 2. Clean up any previous demo data
20
+ # 3. Run HTM operations and export metrics
21
+ # 4. Open Grafana in your browser
22
+
23
+ require 'fileutils'
24
+
25
+ ROBOT_NAME = "Telemetry Demo Robot"
26
+ METRICS_PORT = 9394
27
+
28
+ class TelemetryDemo
29
+ def initialize
30
+ @metrics_server = nil
31
+ @metrics_server_thread = nil
32
+ end
33
+
34
+ def run
35
+ puts banner
36
+ check_ruby_dependencies
37
+ load_htm
38
+ check_brew_services
39
+ start_services
40
+ configure_prometheus_scrape
41
+ setup_metrics_server
42
+ cleanup_previous_data
43
+ import_grafana_dashboard
44
+ open_grafana
45
+ run_demo_loop
46
+ ensure
47
+ stop_metrics_server
48
+ end
49
+
50
+ private
51
+
52
+ def banner
53
+ <<~BANNER
54
+
55
+ ╔══════════════════════════════════════════════════════════════════╗
56
+ ║ HTM Telemetry Demo - Live Grafana Visualization ║
57
+ ╚══════════════════════════════════════════════════════════════════╝
58
+
59
+ BANNER
60
+ end
61
+
62
+ # =========================================================================
63
+ # Dependency Checks
64
+ # =========================================================================
65
+
66
+ def check_ruby_dependencies
67
+ puts "Checking Ruby dependencies..."
68
+
69
+ missing = []
70
+
71
+ # Check for prometheus-client (installed as system gem)
72
+ begin
73
+ gem 'prometheus-client'
74
+ require 'prometheus/client'
75
+ require 'prometheus/client/formats/text'
76
+ puts " [OK] prometheus-client gem"
77
+ rescue LoadError, Gem::MissingSpecError
78
+ missing << 'prometheus-client'
79
+ end
80
+
81
+ # Check for webrick (installed as system gem)
82
+ begin
83
+ gem 'webrick'
84
+ require 'webrick'
85
+ puts " [OK] webrick gem"
86
+ rescue LoadError, Gem::MissingSpecError
87
+ missing << 'webrick'
88
+ end
89
+
90
+ unless missing.empty?
91
+ puts
92
+ puts " Missing gems. Install with:"
93
+ puts " gem install #{missing.join(' ')}"
94
+ puts
95
+ exit 1
96
+ end
97
+
98
+ puts
99
+ end
100
+
101
+ def load_htm
102
+ puts "Loading HTM..."
103
+
104
+ # Use bundler for HTM and its dependencies
105
+ require 'bundler/setup'
106
+ require_relative '../../lib/htm'
107
+
108
+ puts " [OK] HTM #{HTM::VERSION}"
109
+ puts
110
+ end
111
+
112
+ def check_brew_services
113
+ puts "Checking Homebrew services..."
114
+
115
+ %w[prometheus grafana].each do |service|
116
+ print " #{service}: "
117
+
118
+ # Check if installed
119
+ result = `brew list #{service} 2>/dev/null`
120
+ if $?.success?
121
+ puts "installed"
122
+ else
123
+ puts "NOT INSTALLED"
124
+ puts
125
+ puts " Install with:"
126
+ puts " brew install #{service}"
127
+ puts
128
+ exit 1
129
+ end
130
+ end
131
+
132
+ puts
133
+ end
134
+
135
+ def start_services
136
+ puts "Starting services..."
137
+
138
+ %w[prometheus grafana].each do |service|
139
+ print " #{service}: "
140
+
141
+ if service_running?(service)
142
+ puts "already running"
143
+ else
144
+ # Start the service
145
+ `brew services start #{service} 2>/dev/null`
146
+ sleep 2
147
+
148
+ # Verify it started
149
+ if service_running?(service)
150
+ puts "started"
151
+ else
152
+ puts "FAILED TO START"
153
+ puts " Try: brew services start #{service}"
154
+ exit 1
155
+ end
156
+ end
157
+ end
158
+
159
+ # Give services a moment to fully initialize
160
+ sleep 2
161
+ puts
162
+ end
163
+
164
+ def service_running?(service)
165
+ status = `brew services info #{service} --json 2>/dev/null`
166
+ # Handle both "running": true and "running":true formats
167
+ status.include?('"running": true') || status.include?('"running":true')
168
+ end
169
+
170
+ # =========================================================================
171
+ # Prometheus Configuration
172
+ # =========================================================================
173
+
174
+ def configure_prometheus_scrape
175
+ puts "Configuring Prometheus to scrape demo metrics..."
176
+
177
+ require 'yaml'
178
+
179
+ # The actual prometheus.yml location
180
+ prometheus_yml = "/opt/homebrew/etc/prometheus.yml"
181
+ prometheus_yml = "/usr/local/etc/prometheus.yml" unless File.exist?(prometheus_yml)
182
+
183
+ unless File.exist?(prometheus_yml)
184
+ puts " [WARN] Could not find prometheus.yml"
185
+ puts " Metrics may not be scraped automatically."
186
+ puts " Add this to your prometheus.yml:"
187
+ puts
188
+ puts " scrape_configs:"
189
+ puts " - job_name: 'htm-demo'"
190
+ puts " static_configs:"
191
+ puts " - targets: ['localhost:#{METRICS_PORT}']"
192
+ puts
193
+ return
194
+ end
195
+
196
+ # Parse YAML properly
197
+ config = YAML.load_file(prometheus_yml)
198
+ config['scrape_configs'] ||= []
199
+
200
+ # Check if our job already exists
201
+ job_exists = config['scrape_configs'].any? do |job|
202
+ job['job_name'] == 'htm-demo'
203
+ end
204
+
205
+ if job_exists
206
+ puts " [OK] htm-demo job already configured"
207
+ else
208
+ # Add our scrape config
209
+ htm_job = {
210
+ 'job_name' => 'htm-demo',
211
+ 'scrape_interval' => '5s',
212
+ 'static_configs' => [
213
+ { 'targets' => ["localhost:#{METRICS_PORT}"] }
214
+ ]
215
+ }
216
+ config['scrape_configs'] << htm_job
217
+
218
+ # Write back with proper YAML formatting
219
+ File.write(prometheus_yml, YAML.dump(config))
220
+ puts " [OK] Added htm-demo scrape job"
221
+
222
+ # Restart Prometheus to pick up new config
223
+ print " Restarting Prometheus... "
224
+ `brew services restart prometheus 2>/dev/null`
225
+ sleep 3
226
+ puts "done"
227
+ end
228
+
229
+ puts
230
+ end
231
+
232
+ # =========================================================================
233
+ # Metrics Server (exposes /metrics for Prometheus to scrape)
234
+ # =========================================================================
235
+
236
+ def setup_metrics_server
237
+ puts "Starting metrics server on port #{METRICS_PORT}..."
238
+
239
+ # Create Prometheus registry and metrics
240
+ @registry = Prometheus::Client.registry
241
+
242
+ @jobs_counter = @registry.counter(
243
+ :htm_jobs_total,
244
+ docstring: 'HTM job execution counts',
245
+ labels: [:job, :status]
246
+ )
247
+
248
+ @embedding_histogram = @registry.histogram(
249
+ :htm_embedding_latency_milliseconds,
250
+ docstring: 'Embedding generation latency',
251
+ labels: [:provider, :status],
252
+ buckets: [10, 25, 50, 100, 250, 500, 1000, 2500, 5000]
253
+ )
254
+
255
+ @tag_histogram = @registry.histogram(
256
+ :htm_tag_latency_milliseconds,
257
+ docstring: 'Tag extraction latency',
258
+ labels: [:provider, :status],
259
+ buckets: [100, 250, 500, 1000, 2500, 5000, 10000]
260
+ )
261
+
262
+ @search_histogram = @registry.histogram(
263
+ :htm_search_latency_milliseconds,
264
+ docstring: 'Search operation latency',
265
+ labels: [:strategy],
266
+ buckets: [5, 10, 25, 50, 100, 250, 500, 1000]
267
+ )
268
+
269
+ @cache_counter = @registry.counter(
270
+ :htm_cache_operations_total,
271
+ docstring: 'Cache hit/miss counts',
272
+ labels: [:operation]
273
+ )
274
+
275
+ # Start WEBrick server in background thread
276
+ @metrics_server = WEBrick::HTTPServer.new(
277
+ Port: METRICS_PORT,
278
+ Logger: WEBrick::Log.new("/dev/null"),
279
+ AccessLog: []
280
+ )
281
+
282
+ @metrics_server.mount_proc '/metrics' do |req, res|
283
+ res['Content-Type'] = 'text/plain; version=0.0.4'
284
+ res.body = Prometheus::Client::Formats::Text.marshal(@registry)
285
+ end
286
+
287
+ @metrics_server_thread = Thread.new { @metrics_server.start }
288
+
289
+ puts " [OK] Metrics available at http://localhost:#{METRICS_PORT}/metrics"
290
+ puts
291
+ end
292
+
293
+ def stop_metrics_server
294
+ if @metrics_server
295
+ @metrics_server.shutdown
296
+ @metrics_server_thread&.join(2)
297
+ end
298
+ end
299
+
300
+ # =========================================================================
301
+ # Data Cleanup
302
+ # =========================================================================
303
+
304
+ def cleanup_previous_data
305
+ puts "Cleaning up previous demo data..."
306
+
307
+ begin
308
+ # Find the robot
309
+ robot = HTM::Models::Robot.find_by(name: ROBOT_NAME)
310
+
311
+ if robot
312
+ # Find all nodes associated with this robot
313
+ node_ids = HTM::Models::RobotNode.where(robot_id: robot.id).pluck(:node_id)
314
+
315
+ if node_ids.any?
316
+ # Hard delete the nodes
317
+ deleted_count = HTM::Models::Node.where(id: node_ids).delete_all
318
+
319
+ # Clean up robot_nodes join table
320
+ HTM::Models::RobotNode.where(robot_id: robot.id).delete_all
321
+
322
+ # Clean up any orphaned node_tags
323
+ HTM::Models::NodeTag.where(node_id: node_ids).delete_all
324
+
325
+ puts " [OK] Deleted #{deleted_count} previous demo nodes"
326
+ else
327
+ puts " [OK] No previous demo data found"
328
+ end
329
+ else
330
+ puts " [OK] No previous demo robot found"
331
+ end
332
+ rescue => e
333
+ puts " [WARN] Cleanup failed: #{e.message}"
334
+ end
335
+
336
+ puts
337
+ end
338
+
339
+ # =========================================================================
340
+ # Grafana Dashboard
341
+ # =========================================================================
342
+
343
+ def import_grafana_dashboard
344
+ puts "Grafana dashboard setup..."
345
+ puts " Dashboard JSON: examples/telemetry/grafana/dashboards/htm-metrics.json"
346
+ puts " To import: Grafana > Dashboards > Import > Upload JSON"
347
+ puts
348
+ end
349
+
350
+ def open_grafana
351
+ puts "Opening Grafana..."
352
+ system("open http://localhost:3000/d/htm-metrics/htm-metrics 2>/dev/null || " \
353
+ "xdg-open http://localhost:3000 2>/dev/null || " \
354
+ "echo ' Open http://localhost:3000 in your browser'")
355
+ puts " Default login: admin / admin"
356
+ puts
357
+ end
358
+
359
+ # =========================================================================
360
+ # Demo Loop
361
+ # =========================================================================
362
+
363
+ def run_demo_loop
364
+ puts "=" * 60
365
+ puts "Starting demo loop..."
366
+ puts " Metrics: http://localhost:#{METRICS_PORT}/metrics"
367
+ puts " Grafana: http://localhost:3000"
368
+ puts "=" * 60
369
+ puts
370
+ puts "Press Ctrl+C to stop"
371
+ puts
372
+
373
+ # Quiet loggers
374
+ HTM.configure do |config|
375
+ config.logger = Logger.new(File::NULL)
376
+ end
377
+ RubyLLM.logger = Logger.new(File::NULL) if defined?(RubyLLM)
378
+
379
+ htm = HTM.new(robot_name: ROBOT_NAME)
380
+ iteration = 0
381
+
382
+ loop do
383
+ iteration += 1
384
+ puts "[#{Time.now.strftime('%H:%M:%S')}] Iteration #{iteration}"
385
+
386
+ # Remember something (track embedding + tag metrics)
387
+ content = sample_content(iteration)
388
+ print " > Remember: #{content[0..45]}... "
389
+
390
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
391
+ node_id = htm.remember(content)
392
+ puts "node #{node_id}"
393
+
394
+ # Wait for background jobs and record metrics
395
+ sleep 3
396
+
397
+ # Simulate job completion metrics (in real app, jobs would record these)
398
+ record_job_metrics
399
+
400
+ # Search with different strategies
401
+ %w[fulltext vector hybrid].each do |strategy|
402
+ query = sample_query(iteration)
403
+ print " > Recall (#{strategy.ljust(8)}): '#{query.ljust(12)}' "
404
+
405
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
406
+ results = htm.recall(query, strategy: strategy.to_sym, limit: 3)
407
+ elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round
408
+
409
+ @search_histogram.observe(elapsed_ms, labels: { strategy: strategy })
410
+
411
+ # Simulate cache behavior
412
+ if rand < 0.3
413
+ @cache_counter.increment(labels: { operation: 'hit' })
414
+ else
415
+ @cache_counter.increment(labels: { operation: 'miss' })
416
+ end
417
+
418
+ puts "-> #{results.length} results (#{elapsed_ms}ms)"
419
+ end
420
+
421
+ puts " Metrics exported to Prometheus"
422
+ puts
423
+
424
+ sleep 5
425
+ end
426
+ rescue Interrupt
427
+ puts
428
+ puts "Shutting down..."
429
+ ask_to_stop_services
430
+ end
431
+
432
+ def ask_to_stop_services
433
+ print "\nStop Prometheus and Grafana services? (y/N): "
434
+ response = $stdin.gets&.strip&.downcase
435
+
436
+ if response == 'y' || response == 'yes'
437
+ print " Stopping prometheus... "
438
+ `brew services stop prometheus 2>/dev/null`
439
+ puts "done"
440
+
441
+ print " Stopping grafana... "
442
+ `brew services stop grafana 2>/dev/null`
443
+ puts "done"
444
+
445
+ puts " Services stopped."
446
+ else
447
+ puts " Services left running."
448
+ puts " To stop later: brew services stop prometheus grafana"
449
+ end
450
+ end
451
+
452
+ def record_job_metrics
453
+ # Record successful embedding job
454
+ @jobs_counter.increment(labels: { job: 'embedding', status: 'success' })
455
+ @embedding_histogram.observe(
456
+ rand(50..200),
457
+ labels: { provider: 'ollama', status: 'success' }
458
+ )
459
+
460
+ # Record successful tag job
461
+ @jobs_counter.increment(labels: { job: 'tags', status: 'success' })
462
+ @tag_histogram.observe(
463
+ rand(500..2000),
464
+ labels: { provider: 'ollama', status: 'success' }
465
+ )
466
+
467
+ # Occasionally record failures for demo variety
468
+ if rand < 0.1
469
+ @jobs_counter.increment(labels: { job: 'embedding', status: 'error' })
470
+ end
471
+ end
472
+
473
+ def sample_content(iteration)
474
+ contents = [
475
+ "PostgreSQL supports vector similarity search through the pgvector extension.",
476
+ "OpenTelemetry provides unified observability for distributed systems.",
477
+ "Ruby on Rails is a popular web application framework.",
478
+ "Machine learning models can be used for semantic search.",
479
+ "The HTM gem provides intelligent memory management for LLM applications.",
480
+ "Grafana is an open-source analytics and monitoring platform.",
481
+ "Prometheus is a systems monitoring and alerting toolkit.",
482
+ "Background job processing improves application responsiveness.",
483
+ "Hierarchical tags help organize information semantically.",
484
+ "Vector embeddings capture semantic meaning of text."
485
+ ]
486
+ contents[(iteration - 1) % contents.length]
487
+ end
488
+
489
+ def sample_query(iteration)
490
+ queries = %w[database observability ruby search memory monitoring metrics jobs tags vectors]
491
+ queries[(iteration - 1) % queries.length]
492
+ end
493
+ end
494
+
495
+ # Run the demo
496
+ if __FILE__ == $PROGRAM_NAME
497
+ TelemetryDemo.new.run
498
+ end