Class: HTM
- Inherits:
-
Object
- Object
- HTM
- Defined in:
- lib/htm.rb,
lib/htm/config.rb,
lib/htm/config.rb,
lib/htm/errors.rb,
lib/htm/mcp/cli.rb,
lib/htm/railtie.rb,
lib/htm/version.rb,
lib/htm/database.rb,
lib/htm/mcp/tools.rb,
lib/htm/migration.rb,
lib/htm/telemetry.rb,
lib/htm/timeframe.rb,
lib/htm/mcp/server.rb,
lib/htm/models/tag.rb,
lib/htm/job_adapter.rb,
lib/htm/models/node.rb,
lib/htm/query_cache.rb,
lib/htm/robot_group.rb,
lib/htm/sql_builder.rb,
lib/htm/tag_service.rb,
lib/htm/models/robot.rb,
lib/htm/mcp/resources.rb,
lib/htm/observability.rb,
lib/htm/sequel_config.rb,
lib/htm/config/builder.rb,
lib/htm/working_memory.rb,
lib/htm/circuit_breaker.rb,
lib/htm/config/database.rb,
lib/htm/mcp/group_tools.rb,
lib/htm/models/node_tag.rb,
lib/htm/config/validator.rb,
lib/htm/long_term_memory.rb,
lib/htm/embedding_service.rb,
lib/htm/models/robot_node.rb,
lib/htm/models/file_source.rb,
lib/htm/proposition_service.rb,
lib/htm/timeframe_extractor.rb,
lib/htm/integrations/sinatra.rb,
lib/htm/jobs/generate_tags_job.rb,
lib/htm/working_memory_channel.rb,
lib/htm/loaders/markdown_loader.rb,
lib/htm/loaders/markdown_chunker.rb,
lib/htm/models/node_relationship.rb,
lib/htm/jobs/generate_embedding_job.rb,
lib/htm/workflows/remember_workflow.rb,
lib/htm/jobs/generate_propositions_job.rb,
lib/htm/long_term_memory/hybrid_search.rb,
lib/htm/long_term_memory/vector_search.rb,
lib/htm/jobs/generate_relationships_job.rb,
lib/htm/long_term_memory/tag_operations.rb,
lib/htm/long_term_memory/fulltext_search.rb,
lib/htm/long_term_memory/node_operations.rb,
lib/htm/long_term_memory/relevance_scorer.rb,
lib/htm/long_term_memory/robot_operations.rb
Overview
examples/robot_groups/lib/htm/working_memory_channel.rb frozen_string_literal: true
Defined Under Namespace
Modules: JobAdapter, Jobs, Loaders, MCP, Models, Observability, Sinatra, Telemetry, Workflows Classes: AuthorizationError, CircuitBreaker, CircuitBreakerOpenError, Config, ConfigurationError, Database, DatabaseError, EmbeddingError, EmbeddingService, Error, LongTermMemory, Migration, NotFoundError, PropositionError, PropositionService, QueryCache, QueryTimeoutError, Railtie, ResourceExhaustedError, RobotGroup, SequelConfig, SqlBuilder, TagError, TagService, Timeframe, TimeframeExtractor, ValidationError, WorkingMemory, WorkingMemoryChannel
Constant Summary collapse
- MAX_KEY_LENGTH =
Validation constants
255- MAX_VALUE_LENGTH =
1MB
1_000_000- MAX_ARRAY_SIZE =
1000- VALID_RECALL_STRATEGIES =
%i[vector fulltext hybrid].freeze
- VERSION =
'0.0.32'
Instance Attribute Summary collapse
-
#long_term_memory ⇒ Object
readonly
Returns the value of attribute long_term_memory.
-
#robot_id ⇒ Object
readonly
Returns the value of attribute robot_id.
-
#robot_name ⇒ Object
readonly
Returns the value of attribute robot_name.
-
#working_memory ⇒ Object
readonly
Returns the value of attribute working_memory.
Class Method Summary collapse
-
.config ⇒ HTM::Config
(also: configuration)
Get current configuration (singleton).
-
.configure {|config| ... } ⇒ Object
Configure HTM.
-
.count_tokens(text) ⇒ Integer
Count tokens using configured counter.
-
.db ⇒ Sequel::Database
Convenience method to access the database connection.
-
.development? ⇒ Boolean
Check if running in development environment.
-
.embed(text) ⇒ Array<Float>
Generate embedding using EmbeddingService.
-
.env ⇒ String
Get current environment.
-
.extract_propositions(text) ⇒ Array<String>
Extract propositions using PropositionService.
-
.extract_tags(text, existing_ontology: []) ⇒ Array<String>
Extract tags using TagService.
-
.logger ⇒ Logger
Get configured logger.
-
.production? ⇒ Boolean
Check if running in production environment.
-
.reset_configuration! ⇒ Object
Reset configuration to defaults.
-
.test? ⇒ Boolean
Check if running in test environment.
Instance Method Summary collapse
-
#clear_working_memory ⇒ Integer
Clear all nodes from working memory.
-
#forget(node_id, soft: true, confirm: false) ⇒ Boolean
Forget a memory node (soft delete by default, permanent delete requires confirmation).
-
#forget_content(content_substring, soft: true, confirm: false) ⇒ Array<Integer>
Forget all nodes whose content includes the given string.
-
#initialize(working_memory_size: 128_000, robot_name: nil, db_config: nil, db_pool_size: 5, db_query_timeout: 30_000, db_cache_size: 1000, db_cache_ttl: 300) ⇒ HTM
constructor
Initialize a new HTM instance.
-
#load_directory(path, pattern: '**/*.md', force: false) ⇒ Array<Hash>
Load all matching files from a directory into long-term memory.
-
#load_file(path, force: false) ⇒ Hash
Load a single file into long-term memory.
-
#nodes_from_file(file_path) ⇒ Array<HTM::Models::Node>
Get all nodes loaded from a specific file.
-
#purge_deleted(older_than:, confirm: false) ⇒ Integer
Permanently delete all soft-deleted nodes older than specified time.
-
#recall(topic, timeframe: nil, limit: 20, strategy: :fulltext, with_relevance: false, query_tags: [], raw: false, metadata: {}) ⇒ Array<String>, Array<Hash>
Recall memories from a timeframe and topic.
-
#remember(content, tags: [], metadata: {}) ⇒ Integer
Remember new information.
-
#restore(node_id) ⇒ Boolean
Restore a soft-deleted memory node.
-
#unload_file(file_path) ⇒ Integer
Unload a file (soft-delete all its chunks and remove source record).
Constructor Details
#initialize(working_memory_size: 128_000, robot_name: nil, db_config: nil, db_pool_size: 5, db_query_timeout: 30_000, db_cache_size: 1000, db_cache_ttl: 300) ⇒ HTM
Initialize a new HTM instance
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/htm.rb', line 81 def initialize( working_memory_size: 128_000, robot_name: nil, db_config: nil, db_pool_size: 5, db_query_timeout: 30_000, db_cache_size: 1000, db_cache_ttl: 300 ) # Establish Sequel connection if not already connected HTM::SequelConfig.establish_connection! unless HTM::SequelConfig.db @robot_name = robot_name || "robot_#{SecureRandom.uuid[0..7]}" # Initialize components @working_memory = HTM::WorkingMemory.new(max_tokens: working_memory_size) @long_term_memory = HTM::LongTermMemory.new( db_config || HTM::Database.default_config, pool_size: db_pool_size, query_timeout: db_query_timeout, cache_size: db_cache_size, cache_ttl: db_cache_ttl ) # Register this robot in the database and get its integer ID @robot_id = register_robot end |
Instance Attribute Details
#long_term_memory ⇒ Object (readonly)
Returns the value of attribute long_term_memory.
62 63 64 |
# File 'lib/htm.rb', line 62 def long_term_memory @long_term_memory end |
#robot_id ⇒ Object (readonly)
Returns the value of attribute robot_id.
62 63 64 |
# File 'lib/htm.rb', line 62 def robot_id @robot_id end |
#robot_name ⇒ Object (readonly)
Returns the value of attribute robot_name.
62 63 64 |
# File 'lib/htm.rb', line 62 def robot_name @robot_name end |
#working_memory ⇒ Object (readonly)
Returns the value of attribute working_memory.
62 63 64 |
# File 'lib/htm.rb', line 62 def working_memory @working_memory end |
Class Method Details
.config ⇒ HTM::Config Also known as: configuration
Get current configuration (singleton)
681 682 683 |
# File 'lib/htm.rb', line 681 def config @config ||= Config.new end |
.configure {|config| ... } ⇒ Object
Configure HTM
702 703 704 705 706 |
# File 'lib/htm.rb', line 702 def configure yield(config) if block_given? config.validate! config end |
.count_tokens(text) ⇒ Integer
Count tokens using configured counter
779 780 781 782 783 |
# File 'lib/htm.rb', line 779 def count_tokens(text) config.token_counter.call(text) rescue StandardError => e raise HTM::ValidationError, "Token counting failed: #{e.}" end |
.db ⇒ Sequel::Database
Convenience method to access the database connection
213 214 215 |
# File 'lib/htm/sequel_config.rb', line 213 def self.db SequelConfig.db || SequelConfig.establish_connection! end |
.development? ⇒ Boolean
Check if running in development environment
733 734 735 |
# File 'lib/htm.rb', line 733 def development? env == 'development' end |
.embed(text) ⇒ Array<Float>
Generate embedding using EmbeddingService
750 751 752 753 |
# File 'lib/htm.rb', line 750 def (text) result = HTM::EmbeddingService.generate(text) result[:embedding] end |
.env ⇒ String
Get current environment
717 718 719 |
# File 'lib/htm.rb', line 717 def env Config.env end |
.extract_propositions(text) ⇒ Array<String>
Extract propositions using PropositionService
770 771 772 |
# File 'lib/htm.rb', line 770 def extract_propositions(text) HTM::PropositionService.extract(text) end |
.extract_tags(text, existing_ontology: []) ⇒ Array<String>
Extract tags using TagService
761 762 763 |
# File 'lib/htm.rb', line 761 def (text, existing_ontology: []) HTM::TagService.extract(text, existing_ontology: existing_ontology) end |
.logger ⇒ Logger
Get configured logger
789 790 791 |
# File 'lib/htm.rb', line 789 def logger config.logger end |
.production? ⇒ Boolean
Check if running in production environment
741 742 743 |
# File 'lib/htm.rb', line 741 def production? env == 'production' end |
.reset_configuration! ⇒ Object
Reset configuration to defaults
709 710 711 |
# File 'lib/htm.rb', line 709 def reset_configuration! @config = nil end |
.test? ⇒ Boolean
Check if running in test environment
725 726 727 |
# File 'lib/htm.rb', line 725 def test? env == 'test' end |
Instance Method Details
#clear_working_memory ⇒ Integer
Clear all nodes from working memory
Marks all nodes as evicted from working memory (in database) and clears the in-memory cache. Nodes remain in long-term memory.
422 423 424 425 426 427 428 429 430 431 432 433 |
# File 'lib/htm.rb', line 422 def clear_working_memory # Clear in-memory cache @working_memory.clear # Update database: mark all as evicted from working memory count = HTM::Models::RobotNode .where(robot_id: @robot_id, working_memory: true) .update(working_memory: false) HTM.logger.info "Cleared #{count} nodes from working memory" count end |
#forget(node_id, soft: true, confirm: false) ⇒ Boolean
Forget a memory node (soft delete by default, permanent delete requires confirmation)
By default, performs a soft delete (sets deleted_at timestamp). The node remains in the database but is excluded from queries. Use soft: false with confirm: :confirmed for permanent deletion.
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
# File 'lib/htm.rb', line 286 def forget(node_id, soft: true, confirm: false) # Validate inputs raise ArgumentError, "Node ID cannot be nil" if node_id.nil? # Permanent delete requires confirmation if !soft && confirm != :confirmed raise ArgumentError, "Permanent deletion requires confirm: :confirmed" end # Verify node exists (including soft-deleted for restore scenarios) node = HTM::Models::Node.with_deleted.first(id: node_id) raise HTM::NotFoundError, "Node not found: #{node_id}" unless node if soft # Soft delete - mark as deleted but keep in database node.soft_delete! @long_term_memory.clear_cache! # Invalidate cache since node is no longer visible HTM.logger.info "Node #{node_id} soft deleted" else # Permanent delete (also invalidates cache internally) @long_term_memory.delete(node_id) HTM.logger.info "Node #{node_id} permanently deleted" end # Remove from working memory either way @working_memory.remove(node_id) update_robot_activity true end |
#forget_content(content_substring, soft: true, confirm: false) ⇒ Array<Integer>
Forget all nodes whose content includes the given string
Performs a soft delete on all matching nodes. The nodes remain in the database but are excluded from queries. Use case-insensitive LIKE matching.
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 |
# File 'lib/htm.rb', line 336 def forget_content(content_substring, soft: true, confirm: false) raise ArgumentError, "Content substring cannot be blank" if content_substring.to_s.strip.empty? # Permanent delete requires confirmation if !soft && confirm != :confirmed raise ArgumentError, "Permanent deletion requires confirm: :confirmed" end # Find all nodes containing the substring (case-insensitive) matching_nodes = HTM::Models::Node.where(Sequel.ilike(:content, "%#{content_substring}%")) node_ids = matching_nodes.select_map(:id) if node_ids.empty? HTM.logger.info "No nodes found containing: #{content_substring}" return [] end # Delete each matching node node_ids.each do |node_id| forget(node_id, soft: soft, confirm: confirm) end HTM.logger.info "Forgot #{node_ids.length} nodes containing: #{content_substring}" node_ids end |
#load_directory(path, pattern: '**/*.md', force: false) ⇒ Array<Hash>
Load all matching files from a directory into long-term memory
478 479 480 481 482 483 484 485 486 487 488 |
# File 'lib/htm.rb', line 478 def load_directory(path, pattern: '**/*.md', force: false) loader = HTM::Loaders::MarkdownLoader.new(self) results = loader.load_directory(path, pattern: pattern, force: force) # Update activity if any files were processed if results.any? { |r| !r[:skipped] && !r[:error] } update_robot_activity end results end |
#load_file(path, force: false) ⇒ Hash
Load a single file into long-term memory
Reads a text-based file (starting with markdown), chunks it by paragraph, and stores each chunk as a node. YAML frontmatter is preserved as metadata on the first chunk.
457 458 459 460 461 462 463 |
# File 'lib/htm.rb', line 457 def load_file(path, force: false) loader = HTM::Loaders::MarkdownLoader.new(self) result = loader.load_file(path, force: force) update_robot_activity unless result[:skipped] result end |
#nodes_from_file(file_path) ⇒ Array<HTM::Models::Node>
Get all nodes loaded from a specific file
499 500 501 502 503 504 |
# File 'lib/htm.rb', line 499 def nodes_from_file(file_path) source = HTM::Models::FileSource.first(file_path: File.(file_path)) return [] unless source HTM::Models::Node.from_source(source.id).all end |
#purge_deleted(older_than:, confirm: false) ⇒ Integer
Permanently delete all soft-deleted nodes older than specified time
403 404 405 406 407 408 409 410 |
# File 'lib/htm.rb', line 403 def purge_deleted(older_than:, confirm: false) raise ArgumentError, "Purge requires confirm: :confirmed" unless confirm == :confirmed count = HTM::Models::Node.purge_deleted(older_than: older_than) HTM.logger.info "Purged #{count} soft-deleted nodes older than #{older_than}" count end |
#recall(topic, timeframe: nil, limit: 20, strategy: :fulltext, with_relevance: false, query_tags: [], raw: false, metadata: {}) ⇒ Array<String>, Array<Hash>
Recall memories from a timeframe and topic
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
# File 'lib/htm.rb', line 196 def recall(topic, timeframe: nil, limit: 20, strategy: :fulltext, with_relevance: false, query_tags: [], raw: false, metadata: {}) # Validate inputs validate_timeframe!(timeframe) validate_positive_integer!(limit, "limit") validate_recall_strategy!(strategy) validate_array!(, "query_tags") () # Normalize timeframe and potentially extract from query search_query = topic normalized_timeframe = if timeframe == :auto result = HTM::Timeframe.normalize(:auto, query: topic) search_query = result.query # Use cleaned query for search result.timeframe else HTM::Timeframe.normalize(timeframe) end # Use relevance-based search if requested nodes = if with_relevance @long_term_memory.search_with_relevance( timeframe: normalized_timeframe, query: search_query, query_tags: , limit: limit, embedding_service: %i[vector hybrid].include?(strategy) ? HTM : nil, metadata: ) else # Perform standard RAG-based retrieval case strategy when :vector # Vector search using query embedding @long_term_memory.search( timeframe: normalized_timeframe, query: search_query, limit: limit, embedding_service: HTM, metadata: ) when :fulltext @long_term_memory.search_fulltext( timeframe: normalized_timeframe, query: search_query, limit: limit, metadata: ) when :hybrid # Hybrid search combining vector + fulltext @long_term_memory.search_hybrid( timeframe: normalized_timeframe, query: search_query, limit: limit, embedding_service: HTM, metadata: ) end end # Add to working memory (evict if needed) nodes.each do |node| add_to_working_memory(node) end update_robot_activity # Return full nodes or just content based on raw parameter raw ? nodes : nodes.map { |node| node['content'] } end |
#remember(content, tags: [], metadata: {}) ⇒ Integer
Remember new information
Stores content in long-term memory and adds it to working memory. Embeddings and hierarchical tags are automatically extracted by LLM in the background.
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/htm.rb', line 128 def remember(content, tags: [], metadata: {}) raise ValidationError, "Content cannot be nil" if content.nil? content = content.to_s.strip raise ValidationError, "Content cannot be empty" if content.empty? validate_remember_content!(content) () () token_count = HTM.count_tokens(content) result = @long_term_memory.add( content: content, token_count: token_count, robot_id: @robot_id, embedding: nil, metadata: ) node_id = result[:node_id] if result[:is_new] HTM.logger.info "Node #{node_id} created for robot #{@robot_name} (#{token_count} tokens)" enqueue_background_jobs(node_id, tags: , metadata: ) else rc = result[:robot_node].remember_count HTM.logger.info "Node #{node_id} already exists, linked to robot #{@robot_name} (remember_count: #{rc})" (node_id, ) end store_in_working_memory(node_id, content, token_count: token_count, robot_node: result[:robot_node]) update_robot_activity node_id end |
#restore(node_id) ⇒ Boolean
Restore a soft-deleted memory node
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 |
# File 'lib/htm.rb', line 372 def restore(node_id) raise ArgumentError, "Node ID cannot be nil" if node_id.nil? # Find including soft-deleted nodes node = HTM::Models::Node.with_deleted.first(id: node_id) raise HTM::NotFoundError, "Node not found: #{node_id}" unless node unless node.deleted? raise ArgumentError, "Node #{node_id} is not soft-deleted" end node.restore! HTM.logger.info "Node #{node_id} restored" update_robot_activity true end |
#unload_file(file_path) ⇒ Integer
Unload a file (soft-delete all its chunks and remove source record)
515 516 517 518 519 520 521 522 523 524 525 |
# File 'lib/htm.rb', line 515 def unload_file(file_path) source = HTM::Models::FileSource.first(file_path: File.(file_path)) return 0 unless source count = source.soft_delete_chunks! @long_term_memory.clear_cache! source.delete update_robot_activity count end |