htm 0.0.14 → 0.0.15

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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/README.md +269 -79
  4. data/db/migrate/00003_create_file_sources.rb +5 -0
  5. data/db/migrate/00004_create_nodes.rb +17 -0
  6. data/db/migrate/00005_create_tags.rb +7 -0
  7. data/db/migrate/00006_create_node_tags.rb +2 -0
  8. data/db/migrate/00007_create_robot_nodes.rb +7 -0
  9. data/db/schema.sql +41 -29
  10. data/docs/api/yard/HTM/Configuration.md +54 -0
  11. data/docs/api/yard/HTM/Database.md +13 -10
  12. data/docs/api/yard/HTM/EmbeddingService.md +5 -1
  13. data/docs/api/yard/HTM/LongTermMemory.md +18 -277
  14. data/docs/api/yard/HTM/PropositionError.md +18 -0
  15. data/docs/api/yard/HTM/PropositionService.md +66 -0
  16. data/docs/api/yard/HTM/QueryCache.md +88 -0
  17. data/docs/api/yard/HTM/RobotGroup.md +481 -0
  18. data/docs/api/yard/HTM/SqlBuilder.md +108 -0
  19. data/docs/api/yard/HTM/TagService.md +4 -0
  20. data/docs/api/yard/HTM/Telemetry/NullInstrument.md +13 -0
  21. data/docs/api/yard/HTM/Telemetry/NullMeter.md +15 -0
  22. data/docs/api/yard/HTM/Telemetry.md +109 -0
  23. data/docs/api/yard/HTM/WorkingMemoryChannel.md +176 -0
  24. data/docs/api/yard/HTM.md +11 -23
  25. data/docs/api/yard/index.csv +102 -25
  26. data/docs/api/yard-reference.md +8 -0
  27. data/docs/assets/images/multi-provider-failover.svg +51 -0
  28. data/docs/assets/images/robot-group-architecture.svg +65 -0
  29. data/docs/database/README.md +3 -3
  30. data/docs/database/public.file_sources.svg +29 -21
  31. data/docs/database/public.node_tags.md +2 -0
  32. data/docs/database/public.node_tags.svg +53 -41
  33. data/docs/database/public.nodes.md +2 -0
  34. data/docs/database/public.nodes.svg +52 -40
  35. data/docs/database/public.robot_nodes.md +2 -0
  36. data/docs/database/public.robot_nodes.svg +30 -22
  37. data/docs/database/public.robots.svg +16 -12
  38. data/docs/database/public.tags.md +3 -0
  39. data/docs/database/public.tags.svg +41 -33
  40. data/docs/database/schema.json +66 -0
  41. data/docs/database/schema.svg +60 -48
  42. data/docs/development/index.md +13 -0
  43. data/docs/development/rake-tasks.md +1068 -0
  44. data/docs/getting-started/quick-start.md +144 -155
  45. data/docs/guides/adding-memories.md +2 -3
  46. data/docs/guides/context-assembly.md +185 -184
  47. data/docs/guides/getting-started.md +154 -148
  48. data/docs/guides/index.md +7 -0
  49. data/docs/guides/long-term-memory.md +60 -92
  50. data/docs/guides/mcp-server.md +617 -0
  51. data/docs/guides/multi-robot.md +249 -345
  52. data/docs/guides/recalling-memories.md +153 -163
  53. data/docs/guides/robot-groups.md +604 -0
  54. data/docs/guides/search-strategies.md +61 -58
  55. data/docs/guides/working-memory.md +103 -136
  56. data/docs/index.md +30 -26
  57. data/examples/robot_groups/robot_worker.rb +1 -2
  58. data/examples/robot_groups/same_process.rb +1 -4
  59. data/lib/htm/robot_group.rb +721 -0
  60. data/lib/htm/version.rb +1 -1
  61. data/lib/htm/working_memory_channel.rb +250 -0
  62. data/lib/htm.rb +2 -0
  63. data/mkdocs.yml +2 -0
  64. metadata +18 -9
  65. data/db/migrate/00009_add_working_memory_to_robot_nodes.rb +0 -12
  66. data/db/migrate/00010_add_soft_delete_to_associations.rb +0 -29
  67. data/db/migrate/00011_add_performance_indexes.rb +0 -21
  68. data/db/migrate/00012_add_tags_trigram_index.rb +0 -18
  69. data/db/migrate/00013_enable_lz4_compression.rb +0 -43
  70. data/examples/robot_groups/lib/robot_group.rb +0 -419
  71. data/examples/robot_groups/lib/working_memory_channel.rb +0 -140
@@ -0,0 +1,108 @@
1
+ # Class: HTM::SqlBuilder
2
+ **Inherits:** Object
3
+
4
+
5
+ SQL building utilities for constructing safe, parameterized queries
6
+
7
+ Provides class methods for building SQL conditions for:
8
+ * Timeframe filtering (single range or multiple ranges)
9
+ * Metadata filtering (JSONB containment)
10
+ * Embedding sanitization and padding (SQL injection prevention)
11
+ * LIKE pattern sanitization (wildcard injection prevention)
12
+
13
+ All methods use proper escaping and parameterization to prevent SQL injection.
14
+
15
+
16
+ **`@example`**
17
+ ```ruby
18
+ HTM::SqlBuilder.timeframe_condition(1.week.ago..Time.now)
19
+ # => "(created_at BETWEEN '2024-01-01' AND '2024-01-08')"
20
+ ```
21
+ **`@example`**
22
+ ```ruby
23
+ HTM::SqlBuilder.metadata_condition({ priority: "high" })
24
+ # => "(metadata @> '{\"priority\":\"high\"}'::jsonb)"
25
+ ```
26
+ **`@example`**
27
+ ```ruby
28
+ HTM::SqlBuilder.sanitize_embedding([0.1, 0.2, 0.3])
29
+ # => "[0.1,0.2,0.3]"
30
+ ```
31
+ **`@example`**
32
+ ```ruby
33
+ HTM::SqlBuilder.sanitize_like_pattern("test%pattern")
34
+ # => "test\\%pattern"
35
+ ```
36
+ # Class Methods
37
+ ## apply_metadata(scope , metadata , column: "metadata") {: #method-c-apply_metadata }
38
+ Apply metadata filter to ActiveRecord scope
39
+ **`@param`** [ActiveRecord::Relation] Base scope
40
+
41
+ **`@param`** [Hash] Metadata to filter by
42
+
43
+ **`@param`** [String] Column name (default: "metadata")
44
+
45
+ **`@return`** [ActiveRecord::Relation] Scoped query
46
+
47
+ ## apply_timeframe(scope , timeframe , column: :created_at) {: #method-c-apply_timeframe }
48
+ Apply timeframe filter to ActiveRecord scope
49
+ **`@param`** [ActiveRecord::Relation] Base scope
50
+
51
+ **`@param`** [nil, Range, Array<Range>] Time range(s)
52
+
53
+ **`@param`** [Symbol] Column name (default: :created_at)
54
+
55
+ **`@return`** [ActiveRecord::Relation] Scoped query
56
+
57
+ ## metadata_condition(metadata , table_alias: nil, column: "metadata") {: #method-c-metadata_condition }
58
+ Build SQL condition for metadata filtering (JSONB containment)
59
+ **`@param`** [Hash] Metadata to filter by
60
+
61
+ **`@param`** [String, nil] Table alias (default: none)
62
+
63
+ **`@param`** [String] Column name (default: "metadata")
64
+
65
+ **`@return`** [String, nil] SQL condition or nil for no filter
66
+
67
+ ## pad_embedding(embedding , target_dimension: MAX_EMBEDDING_DIMENSION) {: #method-c-pad_embedding }
68
+ Pad embedding to target dimension
69
+
70
+ Pads embedding with zeros to reach the target dimension for pgvector
71
+ compatibility.
72
+ **`@param`** [Array<Numeric>] Embedding vector
73
+
74
+ **`@param`** [Integer] Target dimension (default: MAX_EMBEDDING_DIMENSION)
75
+
76
+ **`@return`** [Array<Numeric>] Padded embedding
77
+
78
+ ## sanitize_embedding(embedding ) {: #method-c-sanitize_embedding }
79
+ Sanitize embedding for SQL use
80
+
81
+ Validates that all values are numeric and converts to safe PostgreSQL vector
82
+ format. This prevents SQL injection by ensuring only valid numeric values are
83
+ included.
84
+ **`@param`** [Array<Numeric>] Embedding vector
85
+
86
+ **`@raise`** [ArgumentError] If embedding contains non-numeric values
87
+
88
+ **`@return`** [String] Sanitized vector string for PostgreSQL (e.g., "[0.1,0.2,0.3]")
89
+
90
+ ## sanitize_like_pattern(pattern ) {: #method-c-sanitize_like_pattern }
91
+ Sanitize a string for use in SQL LIKE patterns
92
+
93
+ Escapes SQL LIKE wildcards (% and _) to prevent pattern injection.
94
+ **`@param`** [String] Pattern to sanitize
95
+
96
+ **`@return`** [String] Sanitized pattern safe for LIKE queries
97
+
98
+ ## timeframe_condition(timeframe , table_alias: nil, column: "created_at") {: #method-c-timeframe_condition }
99
+ Build SQL condition for timeframe filtering
100
+ **`@param`** [nil, Range, Array<Range>] Time range(s)
101
+
102
+ **`@param`** [String, nil] Table alias (default: none)
103
+
104
+ **`@param`** [String] Column name (default: "created_at")
105
+
106
+ **`@return`** [String, nil] SQL condition or nil for no filter
107
+
108
+
@@ -29,6 +29,10 @@ Extract tags with validation and processing
29
29
 
30
30
  **`@return`** [Array<String>] Validated tag names
31
31
 
32
+ ## max_depth() {: #method-c-max_depth }
33
+ Maximum tag hierarchy depth (configurable, default 4)
34
+ **`@return`** [Integer] Max depth (3 colons max by default)
35
+
32
36
  ## parse_hierarchy(tag ) {: #method-c-parse_hierarchy }
33
37
  Parse hierarchical structure of a tag
34
38
  **`@param`** [String] Hierarchical tag (e.g., "ai:llm:embedding")
@@ -0,0 +1,13 @@
1
+ # Class: HTM::Telemetry::NullInstrument
2
+ **Inherits:** Object
3
+
4
+ **Includes:** Singleton
5
+
6
+
7
+ Null instrument that accepts but ignores all metric operations
8
+
9
+
10
+
11
+ # Instance Methods
12
+ ## add() {: #method-i-add }
13
+ ## record() {: #method-i-record }
@@ -0,0 +1,15 @@
1
+ # Class: HTM::Telemetry::NullMeter
2
+ **Inherits:** Object
3
+
4
+ **Includes:** Singleton
5
+
6
+
7
+ Null meter that creates null instruments Used when telemetry is disabled or
8
+ SDK unavailable
9
+
10
+
11
+
12
+ # Instance Methods
13
+ ## create_counter() {: #method-i-create_counter }
14
+ ## create_histogram() {: #method-i-create_histogram }
15
+ ## create_up_down_counter() {: #method-i-create_up_down_counter }
@@ -0,0 +1,109 @@
1
+ # Module: HTM::Telemetry
2
+
3
+
4
+ OpenTelemetry-based observability for HTM
5
+
6
+ Provides opt-in metrics collection with zero overhead when disabled. Uses the
7
+ null object pattern - when telemetry is disabled or the SDK is not available,
8
+ all metric operations are no-ops.
9
+
10
+ **`@see`** [] for full implementation details
11
+
12
+
13
+ **`@example`**
14
+ ```ruby
15
+ HTM.configure do |config|
16
+ config.telemetry_enabled = true
17
+ end
18
+ ```
19
+ **`@example`**
20
+ ```ruby
21
+ # Export to OTLP endpoint
22
+ ENV['OTEL_METRICS_EXPORTER'] = 'otlp'
23
+ ENV['OTEL_EXPORTER_OTLP_ENDPOINT'] = 'http://localhost:4318'
24
+ ```
25
+ # Class Methods
26
+ ## cache_operations() {: #method-c-cache_operations }
27
+ Counter for cache operations (hits, misses)
28
+ **`@return`** [OpenTelemetry::Metrics::Counter, NullInstrument]
29
+
30
+
31
+ **`@example`**
32
+ ```ruby
33
+ Telemetry.cache_operations.add(1, attributes: { 'operation' => 'hit' })
34
+ ```
35
+ ## embedding_latency() {: #method-c-embedding_latency }
36
+ Histogram for embedding generation latency
37
+ **`@return`** [OpenTelemetry::Metrics::Histogram, NullInstrument]
38
+
39
+
40
+ **`@example`**
41
+ ```ruby
42
+ Telemetry.embedding_latency.record(145, attributes: { 'provider' => 'ollama', 'status' => 'success' })
43
+ ```
44
+ ## enabled?() {: #method-c-enabled? }
45
+ Check if telemetry is enabled and SDK is available
46
+ **`@return`** [Boolean] true if telemetry should be active
47
+
48
+ ## job_counter() {: #method-c-job_counter }
49
+ Counter for job execution (enqueued, completed, failed)
50
+ **`@return`** [OpenTelemetry::Metrics::Counter, NullInstrument]
51
+
52
+
53
+ **`@example`**
54
+ ```ruby
55
+ Telemetry.job_counter.add(1, attributes: { 'job' => 'embedding', 'status' => 'success' })
56
+ ```
57
+ ## measure(histogram , attributes {}) {: #method-c-measure }
58
+ Measure execution time of a block and record to a histogram
59
+ **`@param`** [OpenTelemetry::Metrics::Histogram, NullInstrument] The histogram to record to
60
+
61
+ **`@param`** [Hash] Attributes to attach to the measurement
62
+
63
+ **`@return`** [Object] The result of the block
64
+
65
+ **`@yield`** [] The block to measure
66
+
67
+
68
+ **`@example`**
69
+ ```ruby
70
+ result = Telemetry.measure(Telemetry.embedding_latency, 'provider' => 'ollama') do
71
+ generate_embedding(text)
72
+ end
73
+ ```
74
+ ## meter() {: #method-c-meter }
75
+ Get the meter for creating instruments
76
+ **`@return`** [OpenTelemetry::Metrics::Meter, NullMeter] Real or null meter
77
+
78
+ ## reset!() {: #method-c-reset! }
79
+ Reset telemetry state (for testing)
80
+ **`@return`** [void]
81
+
82
+ ## sdk_available?() {: #method-c-sdk_available? }
83
+ Check if OpenTelemetry SDK is installed
84
+ **`@return`** [Boolean] true if SDK can be loaded
85
+
86
+ ## search_latency() {: #method-c-search_latency }
87
+ Histogram for search operation latency
88
+ **`@return`** [OpenTelemetry::Metrics::Histogram, NullInstrument]
89
+
90
+
91
+ **`@example`**
92
+ ```ruby
93
+ Telemetry.search_latency.record(50, attributes: { 'strategy' => 'vector' })
94
+ ```
95
+ ## setup() {: #method-c-setup }
96
+ Initialize OpenTelemetry SDK
97
+
98
+ Called automatically when telemetry is enabled. Safe to call multiple times.
99
+ **`@return`** [void]
100
+
101
+ ## tag_latency() {: #method-c-tag_latency }
102
+ Histogram for tag extraction latency
103
+ **`@return`** [OpenTelemetry::Metrics::Histogram, NullInstrument]
104
+
105
+
106
+ **`@example`**
107
+ ```ruby
108
+ Telemetry.tag_latency.record(250, attributes: { 'provider' => 'ollama', 'status' => 'success' })
109
+ ```
@@ -0,0 +1,176 @@
1
+ # Class: HTM::WorkingMemoryChannel
2
+ **Inherits:** Object
3
+
4
+
5
+ Provides real-time synchronization of working memory changes across multiple
6
+ robots using PostgreSQL LISTEN/NOTIFY pub/sub mechanism.
7
+
8
+ This class enables distributed robots to maintain synchronized working memory
9
+ by broadcasting change notifications through PostgreSQL channels. When one
10
+ robot adds, evicts, or clears working memory, all other robots in the group
11
+ receive immediate notification.
12
+
13
+ **`@see`** [] Higher-level coordination using this channel
14
+
15
+
16
+ **`@example`**
17
+ ```ruby
18
+ channel = HTM::WorkingMemoryChannel.new('support-team', db_config)
19
+
20
+ # Subscribe to changes
21
+ channel.on_change do |event, node_id, robot_id|
22
+ case event
23
+ when :added then puts "Node #{node_id} added by robot #{robot_id}"
24
+ when :evicted then puts "Node #{node_id} evicted by robot #{robot_id}"
25
+ when :cleared then puts "Working memory cleared by robot #{robot_id}"
26
+ end
27
+ end
28
+
29
+ # Start listening in background thread
30
+ channel.start_listening
31
+
32
+ # Publish a change
33
+ channel.notify(:added, node_id: 123, robot_id: 456)
34
+
35
+ # Cleanup when done
36
+ channel.stop_listening
37
+ ```
38
+ # Attributes
39
+ ## notifications_received[RW] {: #attribute-i-notifications_received }
40
+ Number of notifications received since channel was created
41
+
42
+ **`@return`** [Integer]
43
+
44
+
45
+ # Instance Methods
46
+ ## channel_name() {: #method-i-channel_name }
47
+ Returns the PostgreSQL channel name used for notifications.
48
+
49
+ The channel name is derived from the group name with a prefix and sanitization
50
+ of special characters.
51
+
52
+ **`@return`** [String] The PostgreSQL LISTEN/NOTIFY channel name
53
+
54
+
55
+ **`@example`**
56
+ ```ruby
57
+ channel = HTM::WorkingMemoryChannel.new('my-group', db_config)
58
+ channel.channel_name # => "htm_wm_my_group"
59
+ ```
60
+ ## initialize(group_name, db_config) {: #method-i-initialize }
61
+ Creates a new working memory channel for a robot group.
62
+
63
+ The channel name is derived from the group name with non-alphanumeric
64
+ characters replaced by underscores to ensure PostgreSQL compatibility.
65
+
66
+ **`@option`** []
67
+
68
+ **`@option`** []
69
+
70
+ **`@option`** []
71
+
72
+ **`@option`** []
73
+
74
+ **`@option`** []
75
+
76
+ **`@param`** [String] Name of the robot group (used to create unique channel)
77
+
78
+ **`@param`** [Hash] PostgreSQL connection configuration hash
79
+
80
+ **`@return`** [WorkingMemoryChannel] a new instance of WorkingMemoryChannel
81
+
82
+
83
+ **`@example`**
84
+ ```ruby
85
+ db_config = { host: 'localhost', port: 5432, dbname: 'htm_dev', user: 'postgres' }
86
+ channel = HTM::WorkingMemoryChannel.new('customer-support', db_config)
87
+ ```
88
+ ## listening?() {: #method-i-listening? }
89
+ Checks if the listener thread is currently active.
90
+
91
+ **`@return`** [Boolean] true if listening for notifications, false otherwise
92
+
93
+
94
+ **`@example`**
95
+ ```ruby
96
+ channel.start_listening
97
+ channel.listening? # => true
98
+ channel.stop_listening
99
+ channel.listening? # => false
100
+ ```
101
+ ## notify(event, node_id:, robot_id:) {: #method-i-notify }
102
+ Broadcasts a working memory change notification to all listeners.
103
+
104
+ Uses PostgreSQL's pg_notify function to send a JSON payload containing the
105
+ event type, affected node ID, originating robot ID, and timestamp.
106
+
107
+ **`@param`** [Symbol] Type of change (:added, :evicted, or :cleared)
108
+
109
+ **`@param`** [Integer, nil] ID of the affected node (nil for :cleared events)
110
+
111
+ **`@param`** [Integer] ID of the robot that triggered the change
112
+
113
+ **`@return`** [void]
114
+
115
+
116
+ **`@example`**
117
+ ```ruby
118
+ channel.notify(:added, node_id: 123, robot_id: 1)
119
+ ```
120
+ **`@example`**
121
+ ```ruby
122
+ channel.notify(:cleared, node_id: nil, robot_id: 1)
123
+ ```
124
+ ## on_change(&callback) {: #method-i-on_change }
125
+ Registers a callback to be invoked when working memory changes occur.
126
+
127
+ Multiple callbacks can be registered; all will be called for each event.
128
+ Callbacks are invoked synchronously within the listener thread.
129
+
130
+ **`@return`** [void]
131
+
132
+ **`@yield`** [event, node_id, robot_id] Block called for each notification
133
+
134
+ **`@yieldparam`** [Symbol] Type of change (:added, :evicted, or :cleared)
135
+
136
+ **`@yieldparam`** [Integer, nil] ID of the affected node
137
+
138
+ **`@yieldparam`** [Integer] ID of the robot that triggered the change
139
+
140
+
141
+ **`@example`**
142
+ ```ruby
143
+ channel.on_change do |event, node_id, robot_id|
144
+ puts "Received #{event} event for node #{node_id}"
145
+ end
146
+ ```
147
+ ## start_listening() {: #method-i-start_listening }
148
+ Starts listening for notifications in a background thread.
149
+
150
+ Creates a dedicated PostgreSQL connection that uses LISTEN to receive
151
+ notifications. The thread polls every 0.5 seconds, allowing for clean shutdown
152
+ via {#stop_listening}.
153
+
154
+ **`@return`** [Thread] The background listener thread
155
+
156
+
157
+ **`@example`**
158
+ ```ruby
159
+ thread = channel.start_listening
160
+ puts "Listening: #{channel.listening?}" # => true
161
+ ```
162
+ ## stop_listening() {: #method-i-stop_listening }
163
+ Stops the background listener thread.
164
+
165
+ Signals the listener to stop, waits up to 0.5 seconds for clean exit, then
166
+ forcefully terminates if still running. The PostgreSQL connection is closed
167
+ automatically.
168
+
169
+ **`@return`** [void]
170
+
171
+
172
+ **`@example`**
173
+ ```ruby
174
+ channel.stop_listening
175
+ puts "Listening: #{channel.listening?}" # => false
176
+ ```
data/docs/api/yard/HTM.md CHANGED
@@ -2,30 +2,10 @@
2
2
  **Inherits:** Object
3
3
 
4
4
 
5
- HTM (Hierarchical Temporal Memory) error classes
5
+ examples/robot_groups/lib/htm/working_memory_channel.rb frozen_string_literal:
6
+ true
6
7
 
7
- All HTM errors inherit from HTM::Error, allowing you to catch all HTM-related
8
- errors with a single rescue clause.
9
8
 
10
-
11
- **`@example`**
12
- ```ruby
13
- begin
14
- htm.remember("some content")
15
- rescue HTM::Error => e
16
- logger.error "HTM error: #{e.message}"
17
- end
18
- ```
19
- **`@example`**
20
- ```ruby
21
- begin
22
- htm.forget(node_id, soft: false)
23
- rescue HTM::NotFoundError
24
- puts "Node not found"
25
- rescue HTM::ValidationError
26
- puts "Invalid input"
27
- end
28
- ```
29
9
  # Class Methods
30
10
  ## configure() {: #method-c-configure }
31
11
  Configure HTM
@@ -57,6 +37,12 @@ Generate embedding using EmbeddingService
57
37
 
58
38
  **`@return`** [Array<Float>] Embedding vector (original, not padded)
59
39
 
40
+ ## extract_propositions(text ) {: #method-c-extract_propositions }
41
+ Extract propositions using PropositionService
42
+ **`@param`** [String] Text to analyze
43
+
44
+ **`@return`** [Array<String>] Extracted atomic propositions
45
+
60
46
  ## extract_tags(text , existing_ontology: []) {: #method-c-extract_tags }
61
47
  Extract tags using TagService
62
48
  **`@param`** [String] Text to analyze
@@ -75,4 +61,6 @@ Reset configuration to defaults
75
61
  ## configuration[RW] {: #attribute-c-configuration }
76
62
  Get current configuration
77
63
 
78
- **`@return`** [HTM::Configuration]
64
+ **`@return`** [HTM::Configuration]
65
+
66
+