htm 0.0.1 → 0.0.10

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 (184) hide show
  1. checksums.yaml +4 -4
  2. data/.aigcm_msg +1 -0
  3. data/.architecture/reviews/comprehensive-codebase-review.md +577 -0
  4. data/.claude/settings.local.json +92 -0
  5. data/.envrc +1 -0
  6. data/.irbrc +283 -80
  7. data/.tbls.yml +31 -0
  8. data/CHANGELOG.md +314 -16
  9. data/CLAUDE.md +603 -0
  10. data/README.md +76 -5
  11. data/Rakefile +5 -0
  12. data/SETUP.md +132 -101
  13. data/db/migrate/{20250101000001_enable_extensions.rb → 00001_enable_extensions.rb} +0 -1
  14. data/db/migrate/00002_create_robots.rb +11 -0
  15. data/db/migrate/00003_create_file_sources.rb +20 -0
  16. data/db/migrate/00004_create_nodes.rb +65 -0
  17. data/db/migrate/00005_create_tags.rb +13 -0
  18. data/db/migrate/00006_create_node_tags.rb +18 -0
  19. data/db/migrate/00007_create_robot_nodes.rb +26 -0
  20. data/db/migrate/00009_add_working_memory_to_robot_nodes.rb +12 -0
  21. data/db/schema.sql +390 -36
  22. data/docs/api/database.md +19 -232
  23. data/docs/api/embedding-service.md +1 -7
  24. data/docs/api/htm.md +305 -364
  25. data/docs/api/index.md +1 -7
  26. data/docs/api/long-term-memory.md +342 -590
  27. data/docs/api/yard/HTM/ActiveRecordConfig.md +23 -0
  28. data/docs/api/yard/HTM/AuthorizationError.md +11 -0
  29. data/docs/api/yard/HTM/CircuitBreaker.md +92 -0
  30. data/docs/api/yard/HTM/CircuitBreakerOpenError.md +34 -0
  31. data/docs/api/yard/HTM/Configuration.md +175 -0
  32. data/docs/api/yard/HTM/Database.md +99 -0
  33. data/docs/api/yard/HTM/DatabaseError.md +14 -0
  34. data/docs/api/yard/HTM/EmbeddingError.md +18 -0
  35. data/docs/api/yard/HTM/EmbeddingService.md +58 -0
  36. data/docs/api/yard/HTM/Error.md +11 -0
  37. data/docs/api/yard/HTM/JobAdapter.md +39 -0
  38. data/docs/api/yard/HTM/LongTermMemory.md +342 -0
  39. data/docs/api/yard/HTM/NotFoundError.md +17 -0
  40. data/docs/api/yard/HTM/Observability.md +107 -0
  41. data/docs/api/yard/HTM/QueryTimeoutError.md +19 -0
  42. data/docs/api/yard/HTM/Railtie.md +27 -0
  43. data/docs/api/yard/HTM/ResourceExhaustedError.md +13 -0
  44. data/docs/api/yard/HTM/TagError.md +18 -0
  45. data/docs/api/yard/HTM/TagService.md +67 -0
  46. data/docs/api/yard/HTM/Timeframe/Result.md +24 -0
  47. data/docs/api/yard/HTM/Timeframe.md +40 -0
  48. data/docs/api/yard/HTM/TimeframeExtractor/Result.md +24 -0
  49. data/docs/api/yard/HTM/TimeframeExtractor.md +45 -0
  50. data/docs/api/yard/HTM/ValidationError.md +20 -0
  51. data/docs/api/yard/HTM/WorkingMemory.md +131 -0
  52. data/docs/api/yard/HTM.md +80 -0
  53. data/docs/api/yard/index.csv +179 -0
  54. data/docs/api/yard-reference.md +51 -0
  55. data/docs/architecture/adrs/001-postgresql-timescaledb.md +1 -1
  56. data/docs/architecture/adrs/003-ollama-embeddings.md +1 -1
  57. data/docs/architecture/adrs/010-redis-working-memory-rejected.md +2 -27
  58. data/docs/architecture/adrs/index.md +2 -13
  59. data/docs/architecture/hive-mind.md +165 -166
  60. data/docs/architecture/index.md +2 -2
  61. data/docs/architecture/overview.md +5 -171
  62. data/docs/architecture/two-tier-memory.md +1 -35
  63. data/docs/assets/images/adr-010-current-architecture.svg +37 -0
  64. data/docs/assets/images/adr-010-proposed-architecture.svg +48 -0
  65. data/docs/assets/images/adr-dependency-tree.svg +93 -0
  66. data/docs/assets/images/class-hierarchy.svg +55 -0
  67. data/docs/assets/images/exception-hierarchy.svg +45 -0
  68. data/docs/assets/images/htm-architecture-overview.svg +83 -0
  69. data/docs/assets/images/htm-complete-memory-flow.svg +160 -0
  70. data/docs/assets/images/htm-context-assembly-flow.svg +148 -0
  71. data/docs/assets/images/htm-eviction-process.svg +141 -0
  72. data/docs/assets/images/htm-memory-addition-flow.svg +138 -0
  73. data/docs/assets/images/htm-memory-recall-flow.svg +152 -0
  74. data/docs/assets/images/htm-node-states.svg +123 -0
  75. data/docs/assets/images/project-structure.svg +78 -0
  76. data/docs/assets/images/test-directory-structure.svg +38 -0
  77. data/{dbdoc → docs/database}/README.md +127 -125
  78. data/docs/database/public.file_sources.md +42 -0
  79. data/docs/database/public.file_sources.svg +211 -0
  80. data/{dbdoc → docs/database}/public.node_tags.md +7 -8
  81. data/docs/database/public.node_tags.svg +239 -0
  82. data/{dbdoc → docs/database}/public.nodes.md +22 -17
  83. data/docs/database/public.nodes.svg +271 -0
  84. data/docs/database/public.robot_nodes.md +46 -0
  85. data/docs/database/public.robot_nodes.svg +243 -0
  86. data/{dbdoc → docs/database}/public.robots.md +2 -3
  87. data/docs/database/public.robots.svg +161 -0
  88. data/docs/database/public.tags.svg +139 -0
  89. data/{dbdoc → docs/database}/schema.json +941 -630
  90. data/docs/database/schema.svg +282 -0
  91. data/docs/development/index.md +1 -29
  92. data/docs/development/schema.md +134 -309
  93. data/docs/development/testing.md +1 -9
  94. data/docs/getting-started/index.md +47 -0
  95. data/docs/{installation.md → getting-started/installation.md} +2 -2
  96. data/docs/{quick-start.md → getting-started/quick-start.md} +5 -5
  97. data/docs/guides/adding-memories.md +295 -643
  98. data/docs/guides/recalling-memories.md +36 -1
  99. data/docs/guides/search-strategies.md +85 -51
  100. data/docs/images/htm-er-diagram.svg +156 -0
  101. data/docs/index.md +16 -31
  102. data/docs/multi_framework_support.md +4 -4
  103. data/examples/README.md +280 -0
  104. data/examples/basic_usage.rb +18 -16
  105. data/examples/cli_app/htm_cli.rb +146 -8
  106. data/examples/cli_app/temp.log +93 -0
  107. data/examples/custom_llm_configuration.rb +1 -2
  108. data/examples/example_app/app.rb +11 -14
  109. data/examples/file_loader_usage.rb +177 -0
  110. data/examples/robot_groups/lib/robot_group.rb +419 -0
  111. data/examples/robot_groups/lib/working_memory_channel.rb +140 -0
  112. data/examples/robot_groups/multi_process.rb +286 -0
  113. data/examples/robot_groups/robot_worker.rb +136 -0
  114. data/examples/robot_groups/same_process.rb +229 -0
  115. data/examples/sinatra_app/Gemfile +1 -0
  116. data/examples/sinatra_app/Gemfile.lock +166 -0
  117. data/examples/sinatra_app/app.rb +219 -24
  118. data/examples/timeframe_demo.rb +276 -0
  119. data/lib/htm/active_record_config.rb +10 -3
  120. data/lib/htm/circuit_breaker.rb +202 -0
  121. data/lib/htm/configuration.rb +313 -80
  122. data/lib/htm/database.rb +67 -36
  123. data/lib/htm/embedding_service.rb +39 -2
  124. data/lib/htm/errors.rb +131 -11
  125. data/lib/htm/{sinatra.rb → integrations/sinatra.rb} +87 -12
  126. data/lib/htm/job_adapter.rb +10 -3
  127. data/lib/htm/jobs/generate_embedding_job.rb +5 -4
  128. data/lib/htm/jobs/generate_tags_job.rb +4 -0
  129. data/lib/htm/loaders/markdown_loader.rb +263 -0
  130. data/lib/htm/loaders/paragraph_chunker.rb +112 -0
  131. data/lib/htm/long_term_memory.rb +601 -321
  132. data/lib/htm/models/file_source.rb +99 -0
  133. data/lib/htm/models/node.rb +116 -12
  134. data/lib/htm/models/robot.rb +53 -4
  135. data/lib/htm/models/robot_node.rb +51 -0
  136. data/lib/htm/models/tag.rb +302 -0
  137. data/lib/htm/observability.rb +395 -0
  138. data/lib/htm/tag_service.rb +60 -3
  139. data/lib/htm/tasks.rb +29 -0
  140. data/lib/htm/timeframe.rb +194 -0
  141. data/lib/htm/timeframe_extractor.rb +307 -0
  142. data/lib/htm/version.rb +1 -1
  143. data/lib/htm/working_memory.rb +165 -70
  144. data/lib/htm.rb +352 -133
  145. data/lib/tasks/doc.rake +300 -0
  146. data/lib/tasks/files.rake +299 -0
  147. data/lib/tasks/htm.rake +188 -2
  148. data/lib/tasks/jobs.rake +10 -12
  149. data/lib/tasks/tags.rake +194 -0
  150. data/mkdocs.yml +91 -9
  151. data/notes/ARCHITECTURE_REVIEW.md +1167 -0
  152. data/notes/IMPLEMENTATION_SUMMARY.md +606 -0
  153. data/notes/MULTI_FRAMEWORK_IMPLEMENTATION.md +451 -0
  154. data/notes/next_steps.md +100 -0
  155. data/notes/plan.md +627 -0
  156. data/notes/tag_ontology_enhancement_ideas.md +222 -0
  157. data/notes/timescaledb_removal_summary.md +200 -0
  158. metadata +177 -37
  159. data/db/migrate/20250101000002_create_robots.rb +0 -14
  160. data/db/migrate/20250101000003_create_nodes.rb +0 -42
  161. data/db/migrate/20250101000005_create_tags.rb +0 -38
  162. data/db/migrate/20250101000007_add_node_vector_indexes.rb +0 -30
  163. data/dbdoc/public.node_tags.svg +0 -112
  164. data/dbdoc/public.nodes.svg +0 -118
  165. data/dbdoc/public.robots.svg +0 -90
  166. data/dbdoc/public.tags.svg +0 -60
  167. data/dbdoc/schema.svg +0 -154
  168. data/{dbdoc → docs/database}/public.node_stats.md +0 -0
  169. data/{dbdoc → docs/database}/public.node_stats.svg +0 -0
  170. data/{dbdoc → docs/database}/public.nodes_tags.md +0 -0
  171. data/{dbdoc → docs/database}/public.nodes_tags.svg +0 -0
  172. data/{dbdoc → docs/database}/public.ontology_structure.md +0 -0
  173. data/{dbdoc → docs/database}/public.ontology_structure.svg +0 -0
  174. data/{dbdoc → docs/database}/public.operations_log.md +0 -0
  175. data/{dbdoc → docs/database}/public.operations_log.svg +0 -0
  176. data/{dbdoc → docs/database}/public.relationships.md +0 -0
  177. data/{dbdoc → docs/database}/public.relationships.svg +0 -0
  178. data/{dbdoc → docs/database}/public.robot_activity.md +0 -0
  179. data/{dbdoc → docs/database}/public.robot_activity.svg +0 -0
  180. data/{dbdoc → docs/database}/public.schema_migrations.md +0 -0
  181. data/{dbdoc → docs/database}/public.schema_migrations.svg +0 -0
  182. data/{dbdoc → docs/database}/public.tags.md +3 -3
  183. /data/{dbdoc → docs/database}/public.topic_relationships.md +0 -0
  184. /data/{dbdoc → docs/database}/public.topic_relationships.svg +0 -0
@@ -10,12 +10,43 @@ class HTM
10
10
  # - Dimension handling (padding/truncation)
11
11
  # - Error handling and logging
12
12
  # - Storage formatting
13
+ # - Circuit breaker protection for external LLM failures
13
14
  #
14
15
  # The actual LLM call is delegated to HTM.configuration.embedding_generator
15
16
  #
16
17
  class EmbeddingService
17
18
  MAX_DIMENSION = 2000 # Maximum dimension for pgvector HNSW index
18
19
 
20
+ # Circuit breaker for embedding API calls
21
+ @circuit_breaker = nil
22
+ @circuit_breaker_mutex = Mutex.new
23
+
24
+ class << self
25
+ # Get or create the circuit breaker for embedding service
26
+ #
27
+ # @return [HTM::CircuitBreaker] The circuit breaker instance
28
+ #
29
+ def circuit_breaker
30
+ @circuit_breaker_mutex.synchronize do
31
+ @circuit_breaker ||= HTM::CircuitBreaker.new(
32
+ name: 'embedding_service',
33
+ failure_threshold: 5,
34
+ reset_timeout: 60
35
+ )
36
+ end
37
+ end
38
+
39
+ # Reset the circuit breaker (useful for testing)
40
+ #
41
+ # @return [void]
42
+ #
43
+ def reset_circuit_breaker!
44
+ @circuit_breaker_mutex.synchronize do
45
+ @circuit_breaker&.reset!
46
+ end
47
+ end
48
+ end
49
+
19
50
  # Generate embedding with validation and processing
20
51
  #
21
52
  # @param text [String] Text to embed
@@ -26,12 +57,15 @@ class HTM
26
57
  # storage_embedding: String, # Formatted for database storage
27
58
  # storage_dimension: Integer # Padded dimension (2000)
28
59
  # }
60
+ # @raise [CircuitBreakerOpenError] If circuit breaker is open
29
61
  #
30
62
  def self.generate(text)
31
63
  HTM.logger.debug "EmbeddingService: Generating embedding for #{text.length} chars"
32
64
 
33
- # Call configured embedding generator
34
- raw_embedding = HTM.configuration.embedding_generator.call(text)
65
+ # Use circuit breaker to protect against cascading failures
66
+ raw_embedding = circuit_breaker.call do
67
+ HTM.configuration.embedding_generator.call(text)
68
+ end
35
69
 
36
70
  # Validate response
37
71
  validate_embedding!(raw_embedding)
@@ -61,6 +95,9 @@ class HTM
61
95
  storage_dimension: MAX_DIMENSION
62
96
  }
63
97
 
98
+ rescue HTM::CircuitBreakerOpenError
99
+ # Re-raise circuit breaker errors without wrapping
100
+ raise
64
101
  rescue HTM::EmbeddingError
65
102
  raise
66
103
  rescue StandardError => e
data/lib/htm/errors.rb CHANGED
@@ -1,34 +1,154 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # HTM error classes
3
+ # HTM (Hierarchical Temporary Memory) error classes
4
+ #
5
+ # All HTM errors inherit from HTM::Error, allowing you to catch
6
+ # all HTM-related errors with a single rescue clause.
7
+ #
8
+ # @example Catching all HTM errors
9
+ # begin
10
+ # htm.remember("some content")
11
+ # rescue HTM::Error => e
12
+ # logger.error "HTM error: #{e.message}"
13
+ # end
14
+ #
15
+ # @example Catching specific errors
16
+ # begin
17
+ # htm.forget(node_id, soft: false)
18
+ # rescue HTM::NotFoundError
19
+ # puts "Node not found"
20
+ # rescue HTM::ValidationError
21
+ # puts "Invalid input"
22
+ # end
23
+ #
4
24
  class HTM
5
25
  # Base error class for all HTM errors
26
+ #
27
+ # All custom HTM errors inherit from this class, providing a common
28
+ # ancestor for error handling.
29
+ #
6
30
  class Error < StandardError; end
7
31
 
8
- # Validation errors
32
+ # Raised when input validation fails
33
+ #
34
+ # Common causes:
35
+ # - Empty or nil content for remember()
36
+ # - Content exceeding maximum size limit
37
+ # - Invalid tag format
38
+ # - Invalid recall strategy
39
+ # - Invalid timeframe format
40
+ #
41
+ # @example
42
+ # htm.remember("") # => raises ValidationError
43
+ # htm.remember("x", tags: ["INVALID!"]) # => raises ValidationError
44
+ #
9
45
  class ValidationError < Error; end
10
46
 
11
- # Resource exhausted errors (memory, tokens, etc.)
47
+ # Raised when system resources are exhausted
48
+ #
49
+ # Common causes:
50
+ # - Working memory token limit exceeded
51
+ # - Database connection pool exhausted
52
+ # - Memory allocation failures
53
+ #
12
54
  class ResourceExhaustedError < Error; end
13
55
 
14
- # Resource not found errors
56
+ # Raised when a requested resource cannot be found
57
+ #
58
+ # Common causes:
59
+ # - Node ID does not exist
60
+ # - Robot not registered
61
+ # - File source not found
62
+ #
63
+ # @example
64
+ # htm.forget(999999) # => raises NotFoundError if node doesn't exist
65
+ #
15
66
  class NotFoundError < Error; end
16
67
 
17
- # Embedding service errors
68
+ # Raised when embedding generation fails
69
+ #
70
+ # Common causes:
71
+ # - LLM provider API errors
72
+ # - Invalid embedding response format
73
+ # - Network connectivity issues
74
+ # - Model not available
75
+ #
76
+ # Note: This error is distinct from CircuitBreakerOpenError.
77
+ # EmbeddingError indicates a single failure, while CircuitBreakerOpenError
78
+ # indicates repeated failures have triggered protective circuit breaking.
79
+ #
18
80
  class EmbeddingError < Error; end
19
81
 
20
- # Tag service errors
82
+ # Raised when tag extraction fails
83
+ #
84
+ # Common causes:
85
+ # - LLM provider API errors
86
+ # - Invalid tag response format
87
+ # - Network connectivity issues
88
+ # - Model not available
89
+ #
90
+ # Note: This error is distinct from CircuitBreakerOpenError.
91
+ # TagError indicates a single failure, while CircuitBreakerOpenError
92
+ # indicates repeated failures have triggered protective circuit breaking.
93
+ #
21
94
  class TagError < Error; end
22
95
 
23
- # Database operation errors
96
+ # Raised when database operations fail
97
+ #
98
+ # Common causes:
99
+ # - Connection failures
100
+ # - Query syntax errors
101
+ # - Constraint violations
102
+ # - Extension not installed (pgvector, pg_trgm)
103
+ #
24
104
  class DatabaseError < Error; end
25
105
 
26
- # Query timeout errors
106
+ # Raised when a database query exceeds the configured timeout
107
+ #
108
+ # Default timeout is 30 seconds. Configure via db_query_timeout parameter
109
+ # when initializing HTM.
110
+ #
111
+ # @example Handling timeout
112
+ # begin
113
+ # htm.recall("complex query", strategy: :hybrid)
114
+ # rescue HTM::QueryTimeoutError
115
+ # # Retry with simpler query or smaller limit
116
+ # end
117
+ #
27
118
  class QueryTimeoutError < DatabaseError; end
28
119
 
29
- # Authorization errors
120
+ # Raised when an operation is not authorized
121
+ #
122
+ # Reserved for future multi-tenant scenarios where access control
123
+ # may restrict certain operations.
124
+ #
30
125
  class AuthorizationError < Error; end
31
126
 
32
- # Circuit breaker errors
33
- class CircuitBreakerOpenError < EmbeddingError; end
127
+ # Raised when circuit breaker is open due to repeated failures
128
+ #
129
+ # The circuit breaker pattern protects against cascading failures when
130
+ # external LLM services are unavailable. When too many consecutive
131
+ # failures occur, the circuit "opens" and subsequent calls fail fast
132
+ # without attempting the operation.
133
+ #
134
+ # Circuit states:
135
+ # - :closed - Normal operation, requests flow through
136
+ # - :open - Too many failures, requests fail immediately
137
+ # - :half_open - Testing if service recovered
138
+ #
139
+ # After a reset timeout (default: 60 seconds), the circuit transitions
140
+ # to half-open and tests if the service has recovered.
141
+ #
142
+ # @example Handling circuit breaker
143
+ # begin
144
+ # htm.remember("new content")
145
+ # rescue HTM::CircuitBreakerOpenError
146
+ # # LLM service unavailable, but node is still saved
147
+ # # Embeddings/tags will be generated later when service recovers
148
+ # end
149
+ #
150
+ # @see HTM::CircuitBreaker
151
+ # @see HTM::Observability.circuit_breaker_stats
152
+ #
153
+ class CircuitBreakerOpenError < Error; end
34
154
  end
@@ -1,5 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # HTM Sinatra Integration
4
+ #
5
+ # Optional integration for using HTM with Sinatra web applications.
6
+ # This file is NOT loaded automatically - require it explicitly:
7
+ #
8
+ # require 'htm'
9
+ # require 'htm/integrations/sinatra'
10
+ #
11
+ # Provides:
12
+ # - HTM::Sinatra::Helpers - Request helpers (init_htm, htm, remember, recall)
13
+ # - HTM::Sinatra::Middleware - Connection pool management
14
+ # - Sinatra::Base.register_htm - One-line setup
15
+ #
16
+
3
17
  require 'sinatra/base'
4
18
 
5
19
  class HTM
@@ -8,20 +22,22 @@ class HTM
8
22
  # Provides convenient helper methods for using HTM in Sinatra applications.
9
23
  #
10
24
  # @example Basic usage
25
+ # require 'htm/integrations/sinatra'
26
+ #
11
27
  # class MyApp < Sinatra::Base
12
- # helpers HTM::Sinatra::Helpers
28
+ # register_htm
13
29
  #
14
30
  # before do
15
31
  # init_htm(robot_name: session[:user_id] || 'guest')
16
32
  # end
17
33
  #
18
34
  # post '/remember' do
19
- # node_id = htm.remember(params[:content], source: 'user')
35
+ # node_id = remember(params[:content])
20
36
  # json status: 'ok', node_id: node_id
21
37
  # end
22
38
  #
23
39
  # get '/recall' do
24
- # memories = htm.recall(params[:topic], limit: 10)
40
+ # memories = recall(params[:topic], limit: 10)
25
41
  # json memories: memories
26
42
  # end
27
43
  # end
@@ -53,11 +69,11 @@ class HTM
53
69
  # Remember information (convenience method)
54
70
  #
55
71
  # @param content [String] Content to remember
56
- # @param source [String] Source identifier (default: 'user')
72
+ # @param tags [Array<String>] Optional tags to assign
57
73
  # @return [Integer] Node ID
58
74
  #
59
- def remember(content, source: 'user')
60
- htm.remember(content, source: source)
75
+ def remember(content, tags: [])
76
+ htm.remember(content, tags: tags)
61
77
  end
62
78
 
63
79
  # Recall memories (convenience method)
@@ -91,16 +107,18 @@ class HTM
91
107
  # end
92
108
  #
93
109
  class Middleware
110
+ # Class-level storage for connection configuration (shared across threads)
111
+ @@db_config = nil
112
+ @@config_mutex = Mutex.new
113
+
94
114
  def initialize(app, options = {})
95
115
  @app = app
96
116
  @options = options
97
117
  end
98
118
 
99
119
  def call(env)
100
- # Establish connection if needed
101
- unless HTM::ActiveRecordConfig.connected?
102
- HTM::ActiveRecordConfig.establish_connection!
103
- end
120
+ # Ensure connection is available in this thread
121
+ ensure_thread_connection!
104
122
 
105
123
  # Process request
106
124
  status, headers, body = @app.call(env)
@@ -108,8 +126,55 @@ class HTM
108
126
  # Return response
109
127
  [status, headers, body]
110
128
  ensure
111
- # Return connections to pool
112
- ActiveRecord::Base.clear_active_connections! if defined?(ActiveRecord)
129
+ # Return connections to pool after request completes
130
+ if defined?(ActiveRecord::Base) && ActiveRecord::Base.respond_to?(:connection_handler)
131
+ ActiveRecord::Base.connection_handler.clear_active_connections!
132
+ end
133
+ end
134
+
135
+ # Store the connection config at startup (called from register_htm)
136
+ def self.store_config!
137
+ @@config_mutex.synchronize do
138
+ return if @@db_config
139
+
140
+ @@db_config = HTM::ActiveRecordConfig.load_database_config
141
+ HTM.logger.debug "HTM database config stored for thread-safe access"
142
+ end
143
+ end
144
+
145
+ private
146
+
147
+ def ensure_thread_connection!
148
+ # Check if connection pool exists and has an active connection
149
+ pool_exists = begin
150
+ ActiveRecord::Base.connection_pool
151
+ true
152
+ rescue ActiveRecord::ConnectionNotDefined
153
+ false
154
+ end
155
+
156
+ if pool_exists
157
+ return if ActiveRecord::Base.connection_pool.active_connection?
158
+ end
159
+
160
+ # Re-establish connection using stored config
161
+ if @@db_config
162
+ ActiveRecord::Base.establish_connection(@@db_config)
163
+ HTM.logger.debug "HTM database connection established for request thread"
164
+ else
165
+ raise "HTM database config not stored - call register_htm at app startup"
166
+ end
167
+ rescue ActiveRecord::ConnectionNotDefined, ActiveRecord::ConnectionNotEstablished => e
168
+ # Pool doesn't exist, establish connection
169
+ if @@db_config
170
+ ActiveRecord::Base.establish_connection(@@db_config)
171
+ HTM.logger.debug "HTM database connection established for request thread"
172
+ else
173
+ raise "HTM database config not stored - call register_htm at app startup"
174
+ end
175
+ rescue StandardError => e
176
+ HTM.logger.error "Failed to ensure thread connection: #{e.class} - #{e.message}"
177
+ raise
113
178
  end
114
179
  end
115
180
  end
@@ -150,6 +215,16 @@ module ::Sinatra
150
215
  end
151
216
  end
152
217
 
218
+ # Store database config for thread-safe access and establish initial connection
219
+ begin
220
+ HTM::Sinatra::Middleware.store_config!
221
+ HTM::ActiveRecordConfig.establish_connection!
222
+ HTM.logger.info "HTM database connection established"
223
+ rescue StandardError => e
224
+ HTM.logger.error "Failed to establish HTM database connection: #{e.message}"
225
+ raise
226
+ end
227
+
153
228
  HTM.logger.info "HTM registered with Sinatra application"
154
229
  HTM.logger.debug "HTM job backend: #{HTM.configuration.job_backend}"
155
230
  end
@@ -72,7 +72,10 @@ class HTM
72
72
 
73
73
  # Convert job class to Sidekiq worker if needed
74
74
  sidekiq_class = to_sidekiq_worker(job_class)
75
- sidekiq_class.perform_async(**params)
75
+
76
+ # Sidekiq 7.x requires native JSON types - convert symbol keys to strings
77
+ json_params = params.transform_keys(&:to_s)
78
+ sidekiq_class.perform_async(json_params)
76
79
 
77
80
  HTM.logger.debug "Enqueued #{job_class.name} via Sidekiq with params: #{params.inspect}"
78
81
  end
@@ -135,12 +138,16 @@ class HTM
135
138
  return job_class if job_class.included_modules.include?(Sidekiq::Worker)
136
139
 
137
140
  # Create wrapper Sidekiq worker
141
+ # Note: Sidekiq 7.x requires JSON-compatible args, so we accept a hash
142
+ # and convert string keys back to symbols for the underlying job
138
143
  Class.new do
139
144
  include Sidekiq::Worker
140
145
  sidekiq_options queue: :htm, retry: 3
141
146
 
142
- define_method(:perform) do |**params|
143
- job_class.perform(**params)
147
+ define_method(:perform) do |params|
148
+ # Convert string keys back to symbols for the job class
149
+ symbolized_params = params.transform_keys(&:to_sym)
150
+ job_class.perform(**symbolized_params)
144
151
  end
145
152
 
146
153
  # Set descriptive name
@@ -43,13 +43,14 @@ class HTM
43
43
  result = HTM::EmbeddingService.generate(node.content)
44
44
 
45
45
  # Update node with processed embedding
46
- node.update!(
47
- embedding: result[:storage_embedding],
48
- embedding_dimension: result[:dimension]
49
- )
46
+ node.update!(embedding: result[:storage_embedding])
50
47
 
51
48
  HTM.logger.info "GenerateEmbeddingJob: Successfully generated embedding for node #{node_id} (#{result[:dimension]} dimensions)"
52
49
 
50
+ rescue HTM::CircuitBreakerOpenError => e
51
+ # Circuit breaker is open - service is unavailable, will retry later
52
+ HTM.logger.warn "GenerateEmbeddingJob: Circuit breaker open for node #{node_id}, will retry when service recovers"
53
+
53
54
  rescue HTM::EmbeddingError => e
54
55
  # Log embedding-specific errors
55
56
  HTM.logger.error "GenerateEmbeddingJob: Embedding generation failed for node #{node_id}: #{e.message}"
@@ -63,6 +63,10 @@ class HTM
63
63
 
64
64
  HTM.logger.info "GenerateTagsJob: Successfully generated #{tag_names.length} tags for node #{node_id}: #{tag_names.join(', ')}"
65
65
 
66
+ rescue HTM::CircuitBreakerOpenError => e
67
+ # Circuit breaker is open - service is unavailable, will retry later
68
+ HTM.logger.warn "GenerateTagsJob: Circuit breaker open for node #{node_id}, will retry when service recovers"
69
+
66
70
  rescue HTM::TagError => e
67
71
  # Log tag-specific errors
68
72
  HTM.logger.error "GenerateTagsJob: Tag generation failed for node #{node_id}: #{e.message}"