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
data/lib/htm/errors.rb ADDED
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ # HTM error classes
4
+ class HTM
5
+ # Base error class for all HTM errors
6
+ class Error < StandardError; end
7
+
8
+ # Validation errors
9
+ class ValidationError < Error; end
10
+
11
+ # Resource exhausted errors (memory, tokens, etc.)
12
+ class ResourceExhaustedError < Error; end
13
+
14
+ # Resource not found errors
15
+ class NotFoundError < Error; end
16
+
17
+ # Embedding service errors
18
+ class EmbeddingError < Error; end
19
+
20
+ # Tag service errors
21
+ class TagError < Error; end
22
+
23
+ # Database operation errors
24
+ class DatabaseError < Error; end
25
+
26
+ # Query timeout errors
27
+ class QueryTimeoutError < DatabaseError; end
28
+
29
+ # Authorization errors
30
+ class AuthorizationError < Error; end
31
+
32
+ # Circuit breaker errors
33
+ class CircuitBreakerOpenError < EmbeddingError; end
34
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ class HTM
4
+ # Job adapter for pluggable background job backends
5
+ #
6
+ # Supports multiple job backends to work seamlessly across different
7
+ # application types (CLI, Sinatra, Rails).
8
+ #
9
+ # Supported backends:
10
+ # - :active_job - Rails ActiveJob (recommended for Rails apps)
11
+ # - :sidekiq - Direct Sidekiq integration (recommended for Sinatra apps)
12
+ # - :inline - Synchronous execution (recommended for CLI and tests)
13
+ # - :thread - Background thread (legacy, for standalone apps)
14
+ #
15
+ # @example Configure job backend
16
+ # HTM.configure do |config|
17
+ # config.job_backend = :active_job
18
+ # end
19
+ #
20
+ # @example Enqueue a job
21
+ # HTM::JobAdapter.enqueue(HTM::Jobs::GenerateEmbeddingJob, node_id: 123)
22
+ #
23
+ # @see ADR-016: Async Embedding and Tag Generation
24
+ #
25
+ module JobAdapter
26
+ class << self
27
+ # Enqueue a background job using the configured backend
28
+ #
29
+ # @param job_class [Class] Job class to enqueue (must respond to :perform)
30
+ # @param params [Hash] Parameters to pass to the job
31
+ # @return [void]
32
+ #
33
+ # @raise [HTM::Error] If job backend is unknown
34
+ #
35
+ def enqueue(job_class, **params)
36
+ backend = HTM.configuration.job_backend
37
+
38
+ case backend
39
+ when :active_job
40
+ enqueue_active_job(job_class, **params)
41
+ when :sidekiq
42
+ enqueue_sidekiq(job_class, **params)
43
+ when :inline
44
+ enqueue_inline(job_class, **params)
45
+ when :thread
46
+ enqueue_thread(job_class, **params)
47
+ else
48
+ raise HTM::Error, "Unknown job backend: #{backend}. Supported backends: :active_job, :sidekiq, :inline, :thread"
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ # Enqueue job using ActiveJob (Rails)
55
+ def enqueue_active_job(job_class, **params)
56
+ unless defined?(ActiveJob)
57
+ raise HTM::Error, "ActiveJob is not available. Add 'activejob' gem or use a different backend."
58
+ end
59
+
60
+ # Convert job class to ActiveJob if needed
61
+ active_job_class = to_active_job_class(job_class)
62
+ active_job_class.perform_later(**params)
63
+
64
+ HTM.logger.debug "Enqueued #{job_class.name} via ActiveJob with params: #{params.inspect}"
65
+ end
66
+
67
+ # Enqueue job using Sidekiq directly
68
+ def enqueue_sidekiq(job_class, **params)
69
+ unless defined?(Sidekiq)
70
+ raise HTM::Error, "Sidekiq is not available. Add 'sidekiq' gem or use a different backend."
71
+ end
72
+
73
+ # Convert job class to Sidekiq worker if needed
74
+ sidekiq_class = to_sidekiq_worker(job_class)
75
+ sidekiq_class.perform_async(**params)
76
+
77
+ HTM.logger.debug "Enqueued #{job_class.name} via Sidekiq with params: #{params.inspect}"
78
+ end
79
+
80
+ # Execute job inline (synchronously)
81
+ def enqueue_inline(job_class, **params)
82
+ HTM.logger.debug "Executing #{job_class.name} inline with params: #{params.inspect}"
83
+
84
+ begin
85
+ job_class.perform(**params)
86
+ HTM.logger.debug "Completed #{job_class.name} inline execution"
87
+ rescue StandardError => e
88
+ HTM.logger.error "Inline job #{job_class.name} failed: #{e.class.name} - #{e.message}"
89
+ HTM.logger.debug e.backtrace.first(5).join("\n")
90
+ end
91
+ end
92
+
93
+ # Execute job in background thread (legacy)
94
+ def enqueue_thread(job_class, **params)
95
+ Thread.new do
96
+ HTM.logger.debug "Executing #{job_class.name} in thread with params: #{params.inspect}"
97
+
98
+ begin
99
+ job_class.perform(**params)
100
+ HTM.logger.debug "Completed #{job_class.name} thread execution"
101
+ rescue StandardError => e
102
+ HTM.logger.error "Thread job #{job_class.name} failed: #{e.class.name} - #{e.message}"
103
+ HTM.logger.debug e.backtrace.first(5).join("\n")
104
+ end
105
+ end
106
+
107
+ HTM.logger.debug "Started thread for #{job_class.name}"
108
+ rescue StandardError => e
109
+ HTM.logger.error "Failed to start thread for #{job_class.name}: #{e.message}"
110
+ end
111
+
112
+ # Convert HTM job class to ActiveJob class
113
+ def to_active_job_class(job_class)
114
+ # If it's already an ActiveJob, return it
115
+ return job_class if job_class < ActiveJob::Base
116
+
117
+ # Create wrapper ActiveJob class
118
+ Class.new(ActiveJob::Base) do
119
+ queue_as :htm
120
+
121
+ define_method(:perform) do |**params|
122
+ job_class.perform(**params)
123
+ end
124
+
125
+ # Set descriptive name
126
+ define_singleton_method(:name) do
127
+ "#{job_class.name}ActiveJobWrapper"
128
+ end
129
+ end
130
+ end
131
+
132
+ # Convert HTM job class to Sidekiq worker
133
+ def to_sidekiq_worker(job_class)
134
+ # If it's already a Sidekiq worker, return it
135
+ return job_class if job_class.included_modules.include?(Sidekiq::Worker)
136
+
137
+ # Create wrapper Sidekiq worker
138
+ Class.new do
139
+ include Sidekiq::Worker
140
+ sidekiq_options queue: :htm, retry: 3
141
+
142
+ define_method(:perform) do |**params|
143
+ job_class.perform(**params)
144
+ end
145
+
146
+ # Set descriptive name
147
+ define_singleton_method(:name) do
148
+ "#{job_class.name}SidekiqWrapper"
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../errors'
4
+ require_relative '../models/node'
5
+ require_relative '../embedding_service'
6
+
7
+ class HTM
8
+ module Jobs
9
+ # Background job to generate and store vector embeddings for nodes
10
+ #
11
+ # This job is enqueued after a node is saved to avoid blocking the
12
+ # main request path. It generates embeddings asynchronously and updates
13
+ # the node record with the embedding vector.
14
+ #
15
+ # @see ADR-016: Async Embedding and Tag Generation
16
+ #
17
+ class GenerateEmbeddingJob
18
+ # Generate embedding for a node
19
+ #
20
+ # Uses the configured embedding generator (HTM.embed) which delegates
21
+ # to the application-provided or default RubyLLM implementation.
22
+ #
23
+ # @param node_id [Integer] ID of the node to process
24
+ #
25
+ def self.perform(node_id:)
26
+ node = HTM::Models::Node.find_by(id: node_id)
27
+
28
+ unless node
29
+ HTM.logger.warn "GenerateEmbeddingJob: Node #{node_id} not found"
30
+ return
31
+ end
32
+
33
+ # Skip if already has embedding
34
+ if node.embedding.present?
35
+ HTM.logger.debug "GenerateEmbeddingJob: Node #{node_id} already has embedding, skipping"
36
+ return
37
+ end
38
+
39
+ begin
40
+ HTM.logger.debug "GenerateEmbeddingJob: Generating embedding for node #{node_id}"
41
+
42
+ # Generate and process embedding using EmbeddingService
43
+ result = HTM::EmbeddingService.generate(node.content)
44
+
45
+ # Update node with processed embedding
46
+ node.update!(
47
+ embedding: result[:storage_embedding],
48
+ embedding_dimension: result[:dimension]
49
+ )
50
+
51
+ HTM.logger.info "GenerateEmbeddingJob: Successfully generated embedding for node #{node_id} (#{result[:dimension]} dimensions)"
52
+
53
+ rescue HTM::EmbeddingError => e
54
+ # Log embedding-specific errors
55
+ HTM.logger.error "GenerateEmbeddingJob: Embedding generation failed for node #{node_id}: #{e.message}"
56
+
57
+ rescue StandardError => e
58
+ # Log unexpected errors
59
+ HTM.logger.error "GenerateEmbeddingJob: Unexpected error for node #{node_id}: #{e.class.name} - #{e.message}"
60
+ HTM.logger.debug e.backtrace.first(5).join("\n")
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../errors'
4
+ require_relative '../models/node'
5
+ require_relative '../models/tag'
6
+ require_relative '../models/node_tag'
7
+ require_relative '../tag_service'
8
+
9
+ class HTM
10
+ module Jobs
11
+ # Background job to generate and associate tags for nodes
12
+ #
13
+ # This job is enqueued after a node is saved to avoid blocking the
14
+ # main request path. It uses LLM to extract hierarchical tags from
15
+ # node content and creates the necessary database associations.
16
+ #
17
+ # @see ADR-016: Async Embedding and Tag Generation
18
+ # @see ADR-015: Hierarchical Tag Ontology and LLM Extraction
19
+ #
20
+ class GenerateTagsJob
21
+ # Generate tags for a node
22
+ #
23
+ # Uses the configured tag extractor (HTM.extract_tags) which delegates
24
+ # to the application-provided or default RubyLLM implementation.
25
+ #
26
+ # @param node_id [Integer] ID of the node to process
27
+ #
28
+ def self.perform(node_id:)
29
+ node = HTM::Models::Node.find_by(id: node_id)
30
+
31
+ unless node
32
+ HTM.logger.warn "GenerateTagsJob: Node #{node_id} not found"
33
+ return
34
+ end
35
+
36
+ begin
37
+ HTM.logger.debug "GenerateTagsJob: Extracting tags for node #{node_id}"
38
+
39
+ # Get existing ontology for context (sample of recent tags)
40
+ existing_ontology = HTM::Models::Tag
41
+ .order(created_at: :desc)
42
+ .limit(100)
43
+ .pluck(:name)
44
+
45
+ # Extract and validate tags using TagService
46
+ tag_names = HTM::TagService.extract(node.content, existing_ontology: existing_ontology)
47
+
48
+ if tag_names.empty?
49
+ HTM.logger.debug "GenerateTagsJob: No tags extracted for node #{node_id}"
50
+ return
51
+ end
52
+
53
+ # Create or find tags and associate with node
54
+ tag_names.each do |tag_name|
55
+ tag = HTM::Models::Tag.find_or_create_by!(name: tag_name)
56
+
57
+ # Create association if it doesn't exist
58
+ HTM::Models::NodeTag.find_or_create_by!(
59
+ node_id: node.id,
60
+ tag_id: tag.id
61
+ )
62
+ end
63
+
64
+ HTM.logger.info "GenerateTagsJob: Successfully generated #{tag_names.length} tags for node #{node_id}: #{tag_names.join(', ')}"
65
+
66
+ rescue HTM::TagError => e
67
+ # Log tag-specific errors
68
+ HTM.logger.error "GenerateTagsJob: Tag generation failed for node #{node_id}: #{e.message}"
69
+
70
+ rescue ActiveRecord::RecordInvalid => e
71
+ # Log validation errors
72
+ HTM.logger.error "GenerateTagsJob: Database validation failed for node #{node_id}: #{e.message}"
73
+
74
+ rescue StandardError => e
75
+ # Log unexpected errors
76
+ HTM.logger.error "GenerateTagsJob: Unexpected error for node #{node_id}: #{e.class.name} - #{e.message}"
77
+ HTM.logger.debug e.backtrace.first(5).join("\n")
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end