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.
- checksums.yaml +4 -4
- data/.aigcm_msg +1 -0
- data/.architecture/reviews/comprehensive-codebase-review.md +577 -0
- data/.claude/settings.local.json +92 -0
- data/.envrc +1 -0
- data/.irbrc +283 -80
- data/.tbls.yml +31 -0
- data/CHANGELOG.md +314 -16
- data/CLAUDE.md +603 -0
- data/README.md +76 -5
- data/Rakefile +5 -0
- data/SETUP.md +132 -101
- data/db/migrate/{20250101000001_enable_extensions.rb → 00001_enable_extensions.rb} +0 -1
- data/db/migrate/00002_create_robots.rb +11 -0
- data/db/migrate/00003_create_file_sources.rb +20 -0
- data/db/migrate/00004_create_nodes.rb +65 -0
- data/db/migrate/00005_create_tags.rb +13 -0
- data/db/migrate/00006_create_node_tags.rb +18 -0
- data/db/migrate/00007_create_robot_nodes.rb +26 -0
- data/db/migrate/00009_add_working_memory_to_robot_nodes.rb +12 -0
- data/db/schema.sql +390 -36
- data/docs/api/database.md +19 -232
- data/docs/api/embedding-service.md +1 -7
- data/docs/api/htm.md +305 -364
- data/docs/api/index.md +1 -7
- data/docs/api/long-term-memory.md +342 -590
- data/docs/api/yard/HTM/ActiveRecordConfig.md +23 -0
- data/docs/api/yard/HTM/AuthorizationError.md +11 -0
- data/docs/api/yard/HTM/CircuitBreaker.md +92 -0
- data/docs/api/yard/HTM/CircuitBreakerOpenError.md +34 -0
- data/docs/api/yard/HTM/Configuration.md +175 -0
- data/docs/api/yard/HTM/Database.md +99 -0
- data/docs/api/yard/HTM/DatabaseError.md +14 -0
- data/docs/api/yard/HTM/EmbeddingError.md +18 -0
- data/docs/api/yard/HTM/EmbeddingService.md +58 -0
- data/docs/api/yard/HTM/Error.md +11 -0
- data/docs/api/yard/HTM/JobAdapter.md +39 -0
- data/docs/api/yard/HTM/LongTermMemory.md +342 -0
- data/docs/api/yard/HTM/NotFoundError.md +17 -0
- data/docs/api/yard/HTM/Observability.md +107 -0
- data/docs/api/yard/HTM/QueryTimeoutError.md +19 -0
- data/docs/api/yard/HTM/Railtie.md +27 -0
- data/docs/api/yard/HTM/ResourceExhaustedError.md +13 -0
- data/docs/api/yard/HTM/TagError.md +18 -0
- data/docs/api/yard/HTM/TagService.md +67 -0
- data/docs/api/yard/HTM/Timeframe/Result.md +24 -0
- data/docs/api/yard/HTM/Timeframe.md +40 -0
- data/docs/api/yard/HTM/TimeframeExtractor/Result.md +24 -0
- data/docs/api/yard/HTM/TimeframeExtractor.md +45 -0
- data/docs/api/yard/HTM/ValidationError.md +20 -0
- data/docs/api/yard/HTM/WorkingMemory.md +131 -0
- data/docs/api/yard/HTM.md +80 -0
- data/docs/api/yard/index.csv +179 -0
- data/docs/api/yard-reference.md +51 -0
- data/docs/architecture/adrs/001-postgresql-timescaledb.md +1 -1
- data/docs/architecture/adrs/003-ollama-embeddings.md +1 -1
- data/docs/architecture/adrs/010-redis-working-memory-rejected.md +2 -27
- data/docs/architecture/adrs/index.md +2 -13
- data/docs/architecture/hive-mind.md +165 -166
- data/docs/architecture/index.md +2 -2
- data/docs/architecture/overview.md +5 -171
- data/docs/architecture/two-tier-memory.md +1 -35
- data/docs/assets/images/adr-010-current-architecture.svg +37 -0
- data/docs/assets/images/adr-010-proposed-architecture.svg +48 -0
- data/docs/assets/images/adr-dependency-tree.svg +93 -0
- data/docs/assets/images/class-hierarchy.svg +55 -0
- data/docs/assets/images/exception-hierarchy.svg +45 -0
- data/docs/assets/images/htm-architecture-overview.svg +83 -0
- data/docs/assets/images/htm-complete-memory-flow.svg +160 -0
- data/docs/assets/images/htm-context-assembly-flow.svg +148 -0
- data/docs/assets/images/htm-eviction-process.svg +141 -0
- data/docs/assets/images/htm-memory-addition-flow.svg +138 -0
- data/docs/assets/images/htm-memory-recall-flow.svg +152 -0
- data/docs/assets/images/htm-node-states.svg +123 -0
- data/docs/assets/images/project-structure.svg +78 -0
- data/docs/assets/images/test-directory-structure.svg +38 -0
- data/{dbdoc → docs/database}/README.md +127 -125
- data/docs/database/public.file_sources.md +42 -0
- data/docs/database/public.file_sources.svg +211 -0
- data/{dbdoc → docs/database}/public.node_tags.md +7 -8
- data/docs/database/public.node_tags.svg +239 -0
- data/{dbdoc → docs/database}/public.nodes.md +22 -17
- data/docs/database/public.nodes.svg +271 -0
- data/docs/database/public.robot_nodes.md +46 -0
- data/docs/database/public.robot_nodes.svg +243 -0
- data/{dbdoc → docs/database}/public.robots.md +2 -3
- data/docs/database/public.robots.svg +161 -0
- data/docs/database/public.tags.svg +139 -0
- data/{dbdoc → docs/database}/schema.json +941 -630
- data/docs/database/schema.svg +282 -0
- data/docs/development/index.md +1 -29
- data/docs/development/schema.md +134 -309
- data/docs/development/testing.md +1 -9
- data/docs/getting-started/index.md +47 -0
- data/docs/{installation.md → getting-started/installation.md} +2 -2
- data/docs/{quick-start.md → getting-started/quick-start.md} +5 -5
- data/docs/guides/adding-memories.md +295 -643
- data/docs/guides/recalling-memories.md +36 -1
- data/docs/guides/search-strategies.md +85 -51
- data/docs/images/htm-er-diagram.svg +156 -0
- data/docs/index.md +16 -31
- data/docs/multi_framework_support.md +4 -4
- data/examples/README.md +280 -0
- data/examples/basic_usage.rb +18 -16
- data/examples/cli_app/htm_cli.rb +146 -8
- data/examples/cli_app/temp.log +93 -0
- data/examples/custom_llm_configuration.rb +1 -2
- data/examples/example_app/app.rb +11 -14
- data/examples/file_loader_usage.rb +177 -0
- data/examples/robot_groups/lib/robot_group.rb +419 -0
- data/examples/robot_groups/lib/working_memory_channel.rb +140 -0
- data/examples/robot_groups/multi_process.rb +286 -0
- data/examples/robot_groups/robot_worker.rb +136 -0
- data/examples/robot_groups/same_process.rb +229 -0
- data/examples/sinatra_app/Gemfile +1 -0
- data/examples/sinatra_app/Gemfile.lock +166 -0
- data/examples/sinatra_app/app.rb +219 -24
- data/examples/timeframe_demo.rb +276 -0
- data/lib/htm/active_record_config.rb +10 -3
- data/lib/htm/circuit_breaker.rb +202 -0
- data/lib/htm/configuration.rb +313 -80
- data/lib/htm/database.rb +67 -36
- data/lib/htm/embedding_service.rb +39 -2
- data/lib/htm/errors.rb +131 -11
- data/lib/htm/{sinatra.rb → integrations/sinatra.rb} +87 -12
- data/lib/htm/job_adapter.rb +10 -3
- data/lib/htm/jobs/generate_embedding_job.rb +5 -4
- data/lib/htm/jobs/generate_tags_job.rb +4 -0
- data/lib/htm/loaders/markdown_loader.rb +263 -0
- data/lib/htm/loaders/paragraph_chunker.rb +112 -0
- data/lib/htm/long_term_memory.rb +601 -321
- data/lib/htm/models/file_source.rb +99 -0
- data/lib/htm/models/node.rb +116 -12
- data/lib/htm/models/robot.rb +53 -4
- data/lib/htm/models/robot_node.rb +51 -0
- data/lib/htm/models/tag.rb +302 -0
- data/lib/htm/observability.rb +395 -0
- data/lib/htm/tag_service.rb +60 -3
- data/lib/htm/tasks.rb +29 -0
- data/lib/htm/timeframe.rb +194 -0
- data/lib/htm/timeframe_extractor.rb +307 -0
- data/lib/htm/version.rb +1 -1
- data/lib/htm/working_memory.rb +165 -70
- data/lib/htm.rb +352 -133
- data/lib/tasks/doc.rake +300 -0
- data/lib/tasks/files.rake +299 -0
- data/lib/tasks/htm.rake +188 -2
- data/lib/tasks/jobs.rake +10 -12
- data/lib/tasks/tags.rake +194 -0
- data/mkdocs.yml +91 -9
- data/notes/ARCHITECTURE_REVIEW.md +1167 -0
- data/notes/IMPLEMENTATION_SUMMARY.md +606 -0
- data/notes/MULTI_FRAMEWORK_IMPLEMENTATION.md +451 -0
- data/notes/next_steps.md +100 -0
- data/notes/plan.md +627 -0
- data/notes/tag_ontology_enhancement_ideas.md +222 -0
- data/notes/timescaledb_removal_summary.md +200 -0
- metadata +177 -37
- data/db/migrate/20250101000002_create_robots.rb +0 -14
- data/db/migrate/20250101000003_create_nodes.rb +0 -42
- data/db/migrate/20250101000005_create_tags.rb +0 -38
- data/db/migrate/20250101000007_add_node_vector_indexes.rb +0 -30
- data/dbdoc/public.node_tags.svg +0 -112
- data/dbdoc/public.nodes.svg +0 -118
- data/dbdoc/public.robots.svg +0 -90
- data/dbdoc/public.tags.svg +0 -60
- data/dbdoc/schema.svg +0 -154
- data/{dbdoc → docs/database}/public.node_stats.md +0 -0
- data/{dbdoc → docs/database}/public.node_stats.svg +0 -0
- data/{dbdoc → docs/database}/public.nodes_tags.md +0 -0
- data/{dbdoc → docs/database}/public.nodes_tags.svg +0 -0
- data/{dbdoc → docs/database}/public.ontology_structure.md +0 -0
- data/{dbdoc → docs/database}/public.ontology_structure.svg +0 -0
- data/{dbdoc → docs/database}/public.operations_log.md +0 -0
- data/{dbdoc → docs/database}/public.operations_log.svg +0 -0
- data/{dbdoc → docs/database}/public.relationships.md +0 -0
- data/{dbdoc → docs/database}/public.relationships.svg +0 -0
- data/{dbdoc → docs/database}/public.robot_activity.md +0 -0
- data/{dbdoc → docs/database}/public.robot_activity.svg +0 -0
- data/{dbdoc → docs/database}/public.schema_migrations.md +0 -0
- data/{dbdoc → docs/database}/public.schema_migrations.svg +0 -0
- data/{dbdoc → docs/database}/public.tags.md +3 -3
- /data/{dbdoc → docs/database}/public.topic_relationships.md +0 -0
- /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
|
-
#
|
|
34
|
-
raw_embedding =
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
33
|
-
|
|
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
|
-
#
|
|
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 =
|
|
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 =
|
|
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
|
|
72
|
+
# @param tags [Array<String>] Optional tags to assign
|
|
57
73
|
# @return [Integer] Node ID
|
|
58
74
|
#
|
|
59
|
-
def remember(content,
|
|
60
|
-
htm.remember(content,
|
|
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
|
-
#
|
|
101
|
-
|
|
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
|
|
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
|
data/lib/htm/job_adapter.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
|
143
|
-
|
|
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}"
|