htm 0.0.1

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 (155) hide show
  1. checksums.yaml +7 -0
  2. data/.architecture/decisions/adrs/001-use-postgresql-timescaledb-storage.md +227 -0
  3. data/.architecture/decisions/adrs/002-two-tier-memory-architecture.md +322 -0
  4. data/.architecture/decisions/adrs/003-ollama-default-embedding-provider.md +339 -0
  5. data/.architecture/decisions/adrs/004-multi-robot-shared-memory-hive-mind.md +374 -0
  6. data/.architecture/decisions/adrs/005-rag-based-retrieval-with-hybrid-search.md +443 -0
  7. data/.architecture/decisions/adrs/006-context-assembly-strategies.md +444 -0
  8. data/.architecture/decisions/adrs/007-working-memory-eviction-strategy.md +461 -0
  9. data/.architecture/decisions/adrs/008-robot-identification-system.md +550 -0
  10. data/.architecture/decisions/adrs/009-never-forget-explicit-deletion-only.md +570 -0
  11. data/.architecture/decisions/adrs/010-redis-working-memory-rejected.md +323 -0
  12. data/.architecture/decisions/adrs/011-database-side-embedding-generation-with-pgai.md +585 -0
  13. data/.architecture/decisions/adrs/012-llm-driven-ontology-topic-extraction.md +583 -0
  14. data/.architecture/decisions/adrs/013-activerecord-orm-and-many-to-many-tagging.md +299 -0
  15. data/.architecture/decisions/adrs/014-client-side-embedding-generation-workflow.md +569 -0
  16. data/.architecture/decisions/adrs/015-hierarchical-tag-ontology-and-llm-extraction.md +701 -0
  17. data/.architecture/decisions/adrs/016-async-embedding-and-tag-generation.md +694 -0
  18. data/.architecture/members.yml +144 -0
  19. data/.architecture/reviews/2025-10-29-llm-configuration-and-async-processing-review.md +1137 -0
  20. data/.architecture/reviews/initial-system-analysis.md +330 -0
  21. data/.envrc +32 -0
  22. data/.irbrc +145 -0
  23. data/CHANGELOG.md +150 -0
  24. data/COMMITS.md +196 -0
  25. data/LICENSE +21 -0
  26. data/README.md +1347 -0
  27. data/Rakefile +51 -0
  28. data/SETUP.md +268 -0
  29. data/config/database.yml +67 -0
  30. data/db/migrate/20250101000001_enable_extensions.rb +14 -0
  31. data/db/migrate/20250101000002_create_robots.rb +14 -0
  32. data/db/migrate/20250101000003_create_nodes.rb +42 -0
  33. data/db/migrate/20250101000005_create_tags.rb +38 -0
  34. data/db/migrate/20250101000007_add_node_vector_indexes.rb +30 -0
  35. data/db/schema.sql +473 -0
  36. data/db/seed_data/README.md +100 -0
  37. data/db/seed_data/presidents.md +136 -0
  38. data/db/seed_data/states.md +151 -0
  39. data/db/seeds.rb +208 -0
  40. data/dbdoc/README.md +173 -0
  41. data/dbdoc/public.node_stats.md +48 -0
  42. data/dbdoc/public.node_stats.svg +41 -0
  43. data/dbdoc/public.node_tags.md +40 -0
  44. data/dbdoc/public.node_tags.svg +112 -0
  45. data/dbdoc/public.nodes.md +54 -0
  46. data/dbdoc/public.nodes.svg +118 -0
  47. data/dbdoc/public.nodes_tags.md +39 -0
  48. data/dbdoc/public.nodes_tags.svg +112 -0
  49. data/dbdoc/public.ontology_structure.md +48 -0
  50. data/dbdoc/public.ontology_structure.svg +38 -0
  51. data/dbdoc/public.operations_log.md +42 -0
  52. data/dbdoc/public.operations_log.svg +130 -0
  53. data/dbdoc/public.relationships.md +39 -0
  54. data/dbdoc/public.relationships.svg +41 -0
  55. data/dbdoc/public.robot_activity.md +46 -0
  56. data/dbdoc/public.robot_activity.svg +35 -0
  57. data/dbdoc/public.robots.md +35 -0
  58. data/dbdoc/public.robots.svg +90 -0
  59. data/dbdoc/public.schema_migrations.md +29 -0
  60. data/dbdoc/public.schema_migrations.svg +26 -0
  61. data/dbdoc/public.tags.md +35 -0
  62. data/dbdoc/public.tags.svg +60 -0
  63. data/dbdoc/public.topic_relationships.md +45 -0
  64. data/dbdoc/public.topic_relationships.svg +32 -0
  65. data/dbdoc/schema.json +1437 -0
  66. data/dbdoc/schema.svg +154 -0
  67. data/docs/api/database.md +806 -0
  68. data/docs/api/embedding-service.md +532 -0
  69. data/docs/api/htm.md +797 -0
  70. data/docs/api/index.md +259 -0
  71. data/docs/api/long-term-memory.md +1096 -0
  72. data/docs/api/working-memory.md +665 -0
  73. data/docs/architecture/adrs/001-postgresql-timescaledb.md +314 -0
  74. data/docs/architecture/adrs/002-two-tier-memory.md +411 -0
  75. data/docs/architecture/adrs/003-ollama-embeddings.md +421 -0
  76. data/docs/architecture/adrs/004-hive-mind.md +437 -0
  77. data/docs/architecture/adrs/005-rag-retrieval.md +531 -0
  78. data/docs/architecture/adrs/006-context-assembly.md +496 -0
  79. data/docs/architecture/adrs/007-eviction-strategy.md +645 -0
  80. data/docs/architecture/adrs/008-robot-identification.md +625 -0
  81. data/docs/architecture/adrs/009-never-forget.md +648 -0
  82. data/docs/architecture/adrs/010-redis-working-memory-rejected.md +323 -0
  83. data/docs/architecture/adrs/011-pgai-integration.md +494 -0
  84. data/docs/architecture/adrs/index.md +215 -0
  85. data/docs/architecture/hive-mind.md +736 -0
  86. data/docs/architecture/index.md +351 -0
  87. data/docs/architecture/overview.md +538 -0
  88. data/docs/architecture/two-tier-memory.md +873 -0
  89. data/docs/assets/css/custom.css +83 -0
  90. data/docs/assets/images/htm-core-components.svg +63 -0
  91. data/docs/assets/images/htm-database-schema.svg +93 -0
  92. data/docs/assets/images/htm-hive-mind-architecture.svg +125 -0
  93. data/docs/assets/images/htm-importance-scoring-framework.svg +83 -0
  94. data/docs/assets/images/htm-layered-architecture.svg +71 -0
  95. data/docs/assets/images/htm-long-term-memory-architecture.svg +115 -0
  96. data/docs/assets/images/htm-working-memory-architecture.svg +120 -0
  97. data/docs/assets/images/htm.jpg +0 -0
  98. data/docs/assets/images/htm_demo.gif +0 -0
  99. data/docs/assets/js/mathjax.js +18 -0
  100. data/docs/assets/videos/htm_video.mp4 +0 -0
  101. data/docs/database_rake_tasks.md +322 -0
  102. data/docs/development/contributing.md +787 -0
  103. data/docs/development/index.md +336 -0
  104. data/docs/development/schema.md +596 -0
  105. data/docs/development/setup.md +719 -0
  106. data/docs/development/testing.md +819 -0
  107. data/docs/guides/adding-memories.md +824 -0
  108. data/docs/guides/context-assembly.md +1009 -0
  109. data/docs/guides/getting-started.md +577 -0
  110. data/docs/guides/index.md +118 -0
  111. data/docs/guides/long-term-memory.md +941 -0
  112. data/docs/guides/multi-robot.md +866 -0
  113. data/docs/guides/recalling-memories.md +927 -0
  114. data/docs/guides/search-strategies.md +953 -0
  115. data/docs/guides/working-memory.md +717 -0
  116. data/docs/index.md +214 -0
  117. data/docs/installation.md +477 -0
  118. data/docs/multi_framework_support.md +519 -0
  119. data/docs/quick-start.md +655 -0
  120. data/docs/setup_local_database.md +302 -0
  121. data/docs/using_rake_tasks_in_your_app.md +383 -0
  122. data/examples/basic_usage.rb +93 -0
  123. data/examples/cli_app/README.md +317 -0
  124. data/examples/cli_app/htm_cli.rb +270 -0
  125. data/examples/custom_llm_configuration.rb +183 -0
  126. data/examples/example_app/Rakefile +71 -0
  127. data/examples/example_app/app.rb +206 -0
  128. data/examples/sinatra_app/Gemfile +21 -0
  129. data/examples/sinatra_app/app.rb +335 -0
  130. data/lib/htm/active_record_config.rb +113 -0
  131. data/lib/htm/configuration.rb +342 -0
  132. data/lib/htm/database.rb +594 -0
  133. data/lib/htm/embedding_service.rb +115 -0
  134. data/lib/htm/errors.rb +34 -0
  135. data/lib/htm/job_adapter.rb +154 -0
  136. data/lib/htm/jobs/generate_embedding_job.rb +65 -0
  137. data/lib/htm/jobs/generate_tags_job.rb +82 -0
  138. data/lib/htm/long_term_memory.rb +965 -0
  139. data/lib/htm/models/node.rb +109 -0
  140. data/lib/htm/models/node_tag.rb +33 -0
  141. data/lib/htm/models/robot.rb +52 -0
  142. data/lib/htm/models/tag.rb +76 -0
  143. data/lib/htm/railtie.rb +76 -0
  144. data/lib/htm/sinatra.rb +157 -0
  145. data/lib/htm/tag_service.rb +135 -0
  146. data/lib/htm/tasks.rb +38 -0
  147. data/lib/htm/version.rb +5 -0
  148. data/lib/htm/working_memory.rb +182 -0
  149. data/lib/htm.rb +400 -0
  150. data/lib/tasks/db.rake +19 -0
  151. data/lib/tasks/htm.rake +147 -0
  152. data/lib/tasks/jobs.rake +312 -0
  153. data/mkdocs.yml +190 -0
  154. data/scripts/install_local_database.sh +309 -0
  155. metadata +341 -0
@@ -0,0 +1,342 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'errors'
4
+ require 'logger'
5
+
6
+ class HTM
7
+ # HTM Configuration
8
+ #
9
+ # Applications using HTM should configure LLM access by providing two methods:
10
+ # 1. embedding_generator - Converts text to vector embeddings
11
+ # 2. tag_extractor - Extracts hierarchical tags from text
12
+ # 3. logger - Logger instance for HTM operations
13
+ #
14
+ # @example Configure with custom methods
15
+ # HTM.configure do |config|
16
+ # config.embedding_generator = ->(text) {
17
+ # MyApp::LLMService.embed(text) # Returns Array<Float>
18
+ # }
19
+ # config.tag_extractor = ->(text, ontology) {
20
+ # MyApp::LLMService.extract_tags(text, ontology) # Returns Array<String>
21
+ # }
22
+ # config.logger = Rails.logger # Use Rails logger
23
+ # end
24
+ #
25
+ # @example Use defaults with custom timeouts
26
+ # HTM.configure do |config|
27
+ # config.embedding_timeout = 60 # 1 minute for faster models
28
+ # config.tag_timeout = 300 # 5 minutes for larger models
29
+ # config.connection_timeout = 10 # 10 seconds connection timeout
30
+ # config.reset_to_defaults # Apply default implementations with new timeouts
31
+ # end
32
+ #
33
+ # @example Use defaults
34
+ # HTM.configure # Uses default implementations
35
+ #
36
+ class Configuration
37
+ attr_accessor :embedding_generator, :tag_extractor, :token_counter
38
+ attr_accessor :embedding_model, :embedding_provider, :embedding_dimensions
39
+ attr_accessor :tag_model, :tag_provider
40
+ attr_accessor :ollama_url
41
+ attr_accessor :embedding_timeout, :tag_timeout, :connection_timeout
42
+ attr_accessor :logger
43
+ attr_accessor :job_backend
44
+
45
+ def initialize
46
+ # Default configuration
47
+ @embedding_provider = :ollama
48
+ @embedding_model = 'nomic-embed-text'
49
+ @embedding_dimensions = 768
50
+
51
+ @tag_provider = :ollama
52
+ @tag_model = 'llama3'
53
+
54
+ @ollama_url = ENV['OLLAMA_URL'] || 'http://localhost:11434'
55
+
56
+ # Timeout settings (in seconds) - apply to all LLM providers
57
+ @embedding_timeout = 120 # 2 minutes for embedding generation
58
+ @tag_timeout = 180 # 3 minutes for tag generation (LLM inference)
59
+ @connection_timeout = 30 # 30 seconds for initial connection
60
+
61
+ # Default logger (STDOUT with INFO level)
62
+ @logger = default_logger
63
+
64
+ # Auto-detect job backend based on environment
65
+ @job_backend = detect_job_backend
66
+
67
+ # Set default implementations
68
+ reset_to_defaults
69
+ end
70
+
71
+ # Reset to default RubyLLM-based implementations
72
+ def reset_to_defaults
73
+ @embedding_generator = default_embedding_generator
74
+ @tag_extractor = default_tag_extractor
75
+ @token_counter = default_token_counter
76
+ end
77
+
78
+ # Validate configuration
79
+ def validate!
80
+ unless @embedding_generator.respond_to?(:call)
81
+ raise HTM::ValidationError, "embedding_generator must be callable (proc, lambda, or object responding to :call)"
82
+ end
83
+
84
+ unless @tag_extractor.respond_to?(:call)
85
+ raise HTM::ValidationError, "tag_extractor must be callable (proc, lambda, or object responding to :call)"
86
+ end
87
+
88
+ unless @token_counter.respond_to?(:call)
89
+ raise HTM::ValidationError, "token_counter must be callable (proc, lambda, or object responding to :call)"
90
+ end
91
+
92
+ unless @logger.respond_to?(:info) && @logger.respond_to?(:warn) && @logger.respond_to?(:error)
93
+ raise HTM::ValidationError, "logger must respond to :info, :warn, and :error"
94
+ end
95
+
96
+ unless [:active_job, :sidekiq, :inline, :thread].include?(@job_backend)
97
+ raise HTM::ValidationError, "job_backend must be one of: :active_job, :sidekiq, :inline, :thread (got #{@job_backend.inspect})"
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ # Auto-detect appropriate job backend based on environment
104
+ #
105
+ # Detection priority:
106
+ # 1. ActiveJob (if defined) - Rails applications
107
+ # 2. Sidekiq (if defined) - Sinatra and other web apps
108
+ # 3. Inline (if test environment) - Test suites
109
+ # 4. Thread (default fallback) - CLI and standalone apps
110
+ #
111
+ # @return [Symbol] Detected job backend
112
+ #
113
+ def detect_job_backend
114
+ # Check for explicit environment variable override
115
+ if ENV['HTM_JOB_BACKEND']
116
+ return ENV['HTM_JOB_BACKEND'].to_sym
117
+ end
118
+
119
+ # Detect test environment - use inline for synchronous execution
120
+ test_env = ENV['RACK_ENV'] == 'test' || ENV['RAILS_ENV'] == 'test' || ENV['APP_ENV'] == 'test'
121
+ return :inline if test_env
122
+
123
+ # Detect Rails - prefer ActiveJob
124
+ if defined?(ActiveJob)
125
+ return :active_job
126
+ end
127
+
128
+ # Detect Sidekiq - direct integration for Sinatra apps
129
+ if defined?(Sidekiq)
130
+ return :sidekiq
131
+ end
132
+
133
+ # Default fallback - simple threading for standalone/CLI apps
134
+ :thread
135
+ end
136
+
137
+ # Default logger configuration
138
+ def default_logger
139
+ logger = Logger.new($stdout)
140
+ logger.level = ENV.fetch('HTM_LOG_LEVEL', 'INFO').upcase.to_sym
141
+ logger.formatter = proc do |severity, datetime, progname, msg|
142
+ "[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] #{severity} -- HTM: #{msg}\n"
143
+ end
144
+ logger
145
+ end
146
+
147
+ # Default token counter using Tiktoken
148
+ def default_token_counter
149
+ lambda do |text|
150
+ require 'tiktoken_ruby' unless defined?(Tiktoken)
151
+ encoder = Tiktoken.encoding_for_model("gpt-3.5-turbo")
152
+ encoder.encode(text).length
153
+ end
154
+ end
155
+
156
+ # Default embedding generator using Ollama HTTP API
157
+ #
158
+ # @return [Proc] Callable that takes text and returns embedding vector
159
+ #
160
+ def default_embedding_generator
161
+ lambda do |text|
162
+ require 'net/http'
163
+ require 'json'
164
+
165
+ case @embedding_provider
166
+ when :ollama
167
+ uri = URI("#{@ollama_url}/api/embeddings")
168
+ request = Net::HTTP::Post.new(uri)
169
+ request['Content-Type'] = 'application/json'
170
+ request.body = { model: @embedding_model, prompt: text }.to_json
171
+
172
+ response = Net::HTTP.start(uri.hostname, uri.port,
173
+ read_timeout: @embedding_timeout,
174
+ open_timeout: @connection_timeout) do |http|
175
+ http.request(request)
176
+ end
177
+
178
+ data = JSON.parse(response.body)
179
+ embedding = data['embedding']
180
+
181
+ unless embedding.is_a?(Array)
182
+ raise HTM::EmbeddingError, "Invalid embedding response format"
183
+ end
184
+
185
+ embedding
186
+ else
187
+ raise HTM::EmbeddingError, "Unsupported embedding provider: #{@embedding_provider}. Only :ollama is currently supported."
188
+ end
189
+ end
190
+ end
191
+
192
+ # Default tag extractor using Ollama HTTP API
193
+ #
194
+ # @return [Proc] Callable that takes text and ontology, returns array of tags
195
+ #
196
+ def default_tag_extractor
197
+ lambda do |text, existing_ontology = []|
198
+ require 'net/http'
199
+ require 'json'
200
+
201
+ # Build prompt
202
+ ontology_context = if existing_ontology.any?
203
+ sample_tags = existing_ontology.sample([existing_ontology.size, 20].min)
204
+ "Existing ontology includes: #{sample_tags.join(', ')}\n"
205
+ else
206
+ "This is a new ontology - create appropriate hierarchical tags.\n"
207
+ end
208
+
209
+ prompt = <<~PROMPT
210
+ Extract hierarchical topic tags from the following text.
211
+
212
+ #{ontology_context}
213
+ Format: root:level1:level2:level3 (use colons to separate levels)
214
+
215
+ Rules:
216
+ - Use lowercase letters, numbers, and hyphens only
217
+ - Maximum depth: 5 levels
218
+ - Return 2-5 tags per text
219
+ - Tags should be reusable and consistent
220
+ - Prefer existing ontology tags when applicable
221
+ - Use hyphens for multi-word terms (e.g., natural-language-processing)
222
+
223
+ Text: #{text}
224
+
225
+ Return ONLY the topic tags, one per line, no explanations.
226
+ PROMPT
227
+
228
+ case @tag_provider
229
+ when :ollama
230
+ uri = URI("#{@ollama_url}/api/generate")
231
+ request = Net::HTTP::Post.new(uri)
232
+ request['Content-Type'] = 'application/json'
233
+ request.body = {
234
+ model: @tag_model,
235
+ prompt: prompt,
236
+ system: 'You are a precise topic extraction system. Output only topic tags in hierarchical format: root:subtopic:detail',
237
+ stream: false,
238
+ options: { temperature: 0 }
239
+ }.to_json
240
+
241
+ response = Net::HTTP.start(uri.hostname, uri.port,
242
+ read_timeout: @tag_timeout,
243
+ open_timeout: @connection_timeout) do |http|
244
+ http.request(request)
245
+ end
246
+
247
+ data = JSON.parse(response.body)
248
+ response_text = data['response']
249
+
250
+ # Parse and validate tags
251
+ tags = response_text.to_s.split("\n").map(&:strip).reject(&:empty?)
252
+
253
+ # Validate format: lowercase alphanumeric + hyphens + colons
254
+ valid_tags = tags.select do |tag|
255
+ tag =~ /^[a-z0-9\-]+(:[a-z0-9\-]+)*$/
256
+ end
257
+
258
+ # Limit depth to 5 levels (4 colons maximum)
259
+ valid_tags.select { |tag| tag.count(':') < 5 }
260
+ else
261
+ raise HTM::TagError, "Unsupported tag provider: #{@tag_provider}. Only :ollama is currently supported."
262
+ end
263
+ end
264
+ end
265
+ end
266
+
267
+ class << self
268
+ attr_writer :configuration
269
+
270
+ # Get current configuration
271
+ #
272
+ # @return [HTM::Configuration]
273
+ #
274
+ def configuration
275
+ @configuration ||= Configuration.new
276
+ end
277
+
278
+ # Configure HTM
279
+ #
280
+ # @yield [config] Configuration object
281
+ # @yieldparam config [HTM::Configuration]
282
+ #
283
+ # @example Custom configuration
284
+ # HTM.configure do |config|
285
+ # config.embedding_generator = ->(text) { MyEmbedder.embed(text) }
286
+ # config.tag_extractor = ->(text, ontology) { MyTagger.extract(text, ontology) }
287
+ # end
288
+ #
289
+ # @example Default configuration
290
+ # HTM.configure # Uses RubyLLM defaults
291
+ #
292
+ def configure
293
+ yield(configuration) if block_given?
294
+ configuration.validate!
295
+ configuration
296
+ end
297
+
298
+ # Reset configuration to defaults
299
+ def reset_configuration!
300
+ @configuration = Configuration.new
301
+ end
302
+
303
+ # Generate embedding using EmbeddingService
304
+ #
305
+ # @param text [String] Text to embed
306
+ # @return [Array<Float>] Embedding vector (original, not padded)
307
+ #
308
+ def embed(text)
309
+ result = HTM::EmbeddingService.generate(text)
310
+ result[:embedding]
311
+ end
312
+
313
+ # Extract tags using TagService
314
+ #
315
+ # @param text [String] Text to analyze
316
+ # @param existing_ontology [Array<String>] Sample of existing tags for context
317
+ # @return [Array<String>] Extracted and validated tag names
318
+ #
319
+ def extract_tags(text, existing_ontology: [])
320
+ HTM::TagService.extract(text, existing_ontology: existing_ontology)
321
+ end
322
+
323
+ # Count tokens using configured counter
324
+ #
325
+ # @param text [String] Text to count tokens for
326
+ # @return [Integer] Token count
327
+ #
328
+ def count_tokens(text)
329
+ configuration.token_counter.call(text)
330
+ rescue StandardError => e
331
+ raise HTM::ValidationError, "Token counting failed: #{e.message}"
332
+ end
333
+
334
+ # Get configured logger
335
+ #
336
+ # @return [Logger] Configured logger instance
337
+ #
338
+ def logger
339
+ configuration.logger
340
+ end
341
+ end
342
+ end