htm 0.0.2 → 0.0.11

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 (129) 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 +95 -0
  5. data/.irbrc +283 -80
  6. data/.tbls.yml +2 -1
  7. data/CHANGELOG.md +327 -26
  8. data/CLAUDE.md +603 -0
  9. data/README.md +83 -12
  10. data/Rakefile +5 -0
  11. data/bin/htm_mcp.rb +527 -0
  12. data/db/migrate/{20250101000001_enable_extensions.rb → 00001_enable_extensions.rb} +0 -1
  13. data/db/migrate/00002_create_robots.rb +11 -0
  14. data/db/migrate/00003_create_file_sources.rb +20 -0
  15. data/db/migrate/00004_create_nodes.rb +65 -0
  16. data/db/migrate/00005_create_tags.rb +13 -0
  17. data/db/migrate/00006_create_node_tags.rb +18 -0
  18. data/db/migrate/00007_create_robot_nodes.rb +26 -0
  19. data/db/migrate/00009_add_working_memory_to_robot_nodes.rb +12 -0
  20. data/db/schema.sql +172 -1
  21. data/docs/api/database.md +1 -2
  22. data/docs/api/htm.md +197 -2
  23. data/docs/api/yard/HTM/ActiveRecordConfig.md +23 -0
  24. data/docs/api/yard/HTM/AuthorizationError.md +11 -0
  25. data/docs/api/yard/HTM/CircuitBreaker.md +92 -0
  26. data/docs/api/yard/HTM/CircuitBreakerOpenError.md +34 -0
  27. data/docs/api/yard/HTM/Configuration.md +175 -0
  28. data/docs/api/yard/HTM/Database.md +99 -0
  29. data/docs/api/yard/HTM/DatabaseError.md +14 -0
  30. data/docs/api/yard/HTM/EmbeddingError.md +18 -0
  31. data/docs/api/yard/HTM/EmbeddingService.md +58 -0
  32. data/docs/api/yard/HTM/Error.md +11 -0
  33. data/docs/api/yard/HTM/JobAdapter.md +39 -0
  34. data/docs/api/yard/HTM/LongTermMemory.md +342 -0
  35. data/docs/api/yard/HTM/NotFoundError.md +17 -0
  36. data/docs/api/yard/HTM/Observability.md +107 -0
  37. data/docs/api/yard/HTM/QueryTimeoutError.md +19 -0
  38. data/docs/api/yard/HTM/Railtie.md +27 -0
  39. data/docs/api/yard/HTM/ResourceExhaustedError.md +13 -0
  40. data/docs/api/yard/HTM/TagError.md +18 -0
  41. data/docs/api/yard/HTM/TagService.md +67 -0
  42. data/docs/api/yard/HTM/Timeframe/Result.md +24 -0
  43. data/docs/api/yard/HTM/Timeframe.md +40 -0
  44. data/docs/api/yard/HTM/TimeframeExtractor/Result.md +24 -0
  45. data/docs/api/yard/HTM/TimeframeExtractor.md +45 -0
  46. data/docs/api/yard/HTM/ValidationError.md +20 -0
  47. data/docs/api/yard/HTM/WorkingMemory.md +131 -0
  48. data/docs/api/yard/HTM.md +80 -0
  49. data/docs/api/yard/index.csv +179 -0
  50. data/docs/api/yard-reference.md +51 -0
  51. data/docs/database/README.md +128 -128
  52. data/docs/database/public.file_sources.md +42 -0
  53. data/docs/database/public.file_sources.svg +211 -0
  54. data/docs/database/public.node_tags.md +4 -4
  55. data/docs/database/public.node_tags.svg +212 -79
  56. data/docs/database/public.nodes.md +22 -12
  57. data/docs/database/public.nodes.svg +246 -127
  58. data/docs/database/public.robot_nodes.md +11 -9
  59. data/docs/database/public.robot_nodes.svg +220 -98
  60. data/docs/database/public.robots.md +2 -2
  61. data/docs/database/public.robots.svg +136 -81
  62. data/docs/database/public.tags.md +3 -3
  63. data/docs/database/public.tags.svg +118 -39
  64. data/docs/database/schema.json +850 -771
  65. data/docs/database/schema.svg +256 -197
  66. data/docs/development/schema.md +67 -2
  67. data/docs/guides/adding-memories.md +93 -7
  68. data/docs/guides/recalling-memories.md +36 -1
  69. data/examples/README.md +405 -0
  70. data/examples/cli_app/htm_cli.rb +65 -5
  71. data/examples/cli_app/temp.log +93 -0
  72. data/examples/file_loader_usage.rb +177 -0
  73. data/examples/mcp_client.rb +529 -0
  74. data/examples/robot_groups/lib/robot_group.rb +419 -0
  75. data/examples/robot_groups/lib/working_memory_channel.rb +140 -0
  76. data/examples/robot_groups/multi_process.rb +286 -0
  77. data/examples/robot_groups/robot_worker.rb +136 -0
  78. data/examples/robot_groups/same_process.rb +229 -0
  79. data/examples/timeframe_demo.rb +276 -0
  80. data/lib/htm/active_record_config.rb +1 -1
  81. data/lib/htm/circuit_breaker.rb +202 -0
  82. data/lib/htm/configuration.rb +59 -13
  83. data/lib/htm/database.rb +67 -36
  84. data/lib/htm/embedding_service.rb +39 -2
  85. data/lib/htm/errors.rb +131 -11
  86. data/lib/htm/jobs/generate_embedding_job.rb +5 -4
  87. data/lib/htm/jobs/generate_tags_job.rb +4 -0
  88. data/lib/htm/loaders/markdown_loader.rb +263 -0
  89. data/lib/htm/loaders/paragraph_chunker.rb +112 -0
  90. data/lib/htm/long_term_memory.rb +460 -343
  91. data/lib/htm/models/file_source.rb +99 -0
  92. data/lib/htm/models/node.rb +80 -5
  93. data/lib/htm/models/robot.rb +24 -1
  94. data/lib/htm/models/robot_node.rb +1 -0
  95. data/lib/htm/models/tag.rb +254 -4
  96. data/lib/htm/observability.rb +395 -0
  97. data/lib/htm/tag_service.rb +60 -3
  98. data/lib/htm/tasks.rb +26 -1
  99. data/lib/htm/timeframe.rb +194 -0
  100. data/lib/htm/timeframe_extractor.rb +307 -0
  101. data/lib/htm/version.rb +1 -1
  102. data/lib/htm/working_memory.rb +165 -70
  103. data/lib/htm.rb +328 -130
  104. data/lib/tasks/doc.rake +300 -0
  105. data/lib/tasks/files.rake +299 -0
  106. data/lib/tasks/htm.rake +158 -3
  107. data/lib/tasks/jobs.rake +3 -9
  108. data/lib/tasks/tags.rake +166 -6
  109. data/mkdocs.yml +36 -1
  110. data/notes/ARCHITECTURE_REVIEW.md +1167 -0
  111. data/notes/IMPLEMENTATION_SUMMARY.md +606 -0
  112. data/notes/MULTI_FRAMEWORK_IMPLEMENTATION.md +451 -0
  113. data/notes/next_steps.md +100 -0
  114. data/notes/plan.md +627 -0
  115. data/notes/tag_ontology_enhancement_ideas.md +222 -0
  116. data/notes/timescaledb_removal_summary.md +200 -0
  117. metadata +158 -17
  118. data/db/migrate/20250101000002_create_robots.rb +0 -14
  119. data/db/migrate/20250101000003_create_nodes.rb +0 -42
  120. data/db/migrate/20250101000005_create_tags.rb +0 -38
  121. data/db/migrate/20250101000007_add_node_vector_indexes.rb +0 -30
  122. data/db/migrate/20250125000001_add_content_hash_to_nodes.rb +0 -14
  123. data/db/migrate/20250125000002_create_robot_nodes.rb +0 -35
  124. data/db/migrate/20250125000003_remove_source_and_robot_id_from_nodes.rb +0 -28
  125. data/db/migrate/20250126000001_create_working_memories.rb +0 -19
  126. data/db/migrate/20250126000002_remove_unused_columns.rb +0 -12
  127. data/docs/database/public.working_memories.md +0 -40
  128. data/docs/database/public.working_memories.svg +0 -112
  129. data/lib/htm/models/working_memory_entry.rb +0 -88
data/.irbrc CHANGED
@@ -1,97 +1,144 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # HTM IRB Configuration
4
- # Load this with: irb -r ./.irbrc
5
- # Or just: irb (if in the htm directory)
3
+ # HTM Interactive Development Console
4
+ # Usage: HTM_DBURL="postgresql://user@localhost:5432/htm_development" irb
6
5
 
7
- puts "Loading HTM library..."
8
-
9
- # Load the HTM library
10
6
  require_relative 'lib/htm'
7
+ require 'debug_me'
11
8
 
12
- # Establish database connection
13
- HTM::ActiveRecordConfig.establish_connection! unless HTM::ActiveRecordConfig.connected?
9
+ puts "=" * 60
10
+ puts "HTM Interactive Console"
11
+ puts "=" * 60
14
12
 
15
- # Configure HTM with Ollama for embedding and tag generation
16
- HTM.configure do |c|
17
- c.embedding_provider = :ollama
18
- c.embedding_model = 'nomic-embed-text'
19
- c.embedding_dimensions = 768
20
- c.tag_provider = :ollama
21
- c.tag_model = 'gemma3'
22
- c.reset_to_defaults
13
+ # Database Connection
14
+ begin
15
+ HTM::ActiveRecordConfig.establish_connection! unless HTM::ActiveRecordConfig.connected?
16
+ db_name = HTM::Database.default_config[:dbname] rescue 'unknown'
17
+ puts "✓ Database connected: #{db_name}"
18
+ rescue => e
19
+ puts "✗ Database connection failed: #{e.message}"
23
20
  end
24
21
 
25
- # Convenience aliases for models
26
- Node = HTM::Models::Node
27
- Tag = HTM::Models::Tag
28
- NodeTag = HTM::Models::NodeTag
29
- Robot = HTM::Models::Robot
22
+ # Configure HTM with sensible defaults for interactive use
23
+ HTM.configure do |config|
24
+ config.job_backend = :inline
25
+ config.embedding_provider = :ollama
26
+ config.embedding_model = 'nomic-embed-text:latest'
27
+ config.embedding_dimensions = 768
28
+ config.tag_provider = :ollama
29
+ config.tag_model = 'gemma3:latest'
30
+ config.reset_to_defaults
31
+ end
32
+ puts "✓ HTM configured (inline jobs, Ollama provider)"
30
33
 
31
- # Helper methods
32
- def reload!
33
- puts "Reloading HTM library..."
34
- load 'lib/htm.rb'
35
- puts "✓ Reloaded"
34
+ # Model shortcuts (constants for easy access)
35
+ Node = HTM::Models::Node
36
+ Tag = HTM::Models::Tag
37
+ NodeTag = HTM::Models::NodeTag
38
+ Robot = HTM::Models::Robot
39
+ RobotNode = HTM::Models::RobotNode
40
+ FileSource = HTM::Models::FileSource
41
+
42
+ # Pre-built HTM instances for common testing scenarios
43
+ def user
44
+ @user ||= HTM.new(robot_name: "User")
36
45
  end
37
46
 
47
+ def assistant
48
+ @assistant ||= HTM.new(robot_name: "Assistant")
49
+ end
50
+
51
+ def researcher
52
+ @researcher ||= HTM.new(robot_name: "Researcher")
53
+ end
54
+
55
+ def coder
56
+ @coder ||= HTM.new(robot_name: "Coder")
57
+ end
58
+
59
+ def htm
60
+ @htm ||= HTM.new(robot_name: "IRB Console")
61
+ end
62
+
63
+ # RubyLLM shortcuts
64
+ def llm
65
+ require 'ruby_llm' unless defined?(RubyLLM)
66
+ RubyLLM
67
+ end
68
+
69
+ def chat(message, model: 'gemma3:latest')
70
+ llm.chat(messages: [{ role: 'user', content: message }], model: model).content
71
+ end
72
+
73
+ def embed(text, model: 'nomic-embed-text:latest')
74
+ llm.embed(text, model: model)
75
+ end
76
+
77
+ # Database utilities
38
78
  def db_stats
39
79
  puts <<~STATS
40
80
 
41
81
  === Database Statistics ===
42
- Nodes: #{Node.count}
43
- Tags: #{Tag.count}
44
- NodeTags: #{NodeTag.count}
45
- Robots: #{Robot.count}
82
+ Nodes: #{Node.count} (#{Node.deleted.count} deleted)
83
+ Tags: #{Tag.count}
84
+ NodeTags: #{NodeTag.count}
85
+ Robots: #{Robot.count}
86
+ RobotNodes: #{RobotNode.count}
87
+ FileSources: #{FileSource.count}
46
88
 
47
89
  STATS
48
90
  end
49
91
 
92
+ def health
93
+ HTM::Observability.health_check
94
+ end
95
+
96
+ def healthy?
97
+ HTM::Observability.healthy?
98
+ end
99
+
100
+ # Node exploration
50
101
  def recent_nodes(limit = 5)
51
102
  puts "\n=== Recent Nodes ==="
52
103
  Node.order(created_at: :desc).limit(limit).each do |node|
53
104
  tags = node.tags.pluck(:name).join(', ')
54
105
  tags_str = tags.empty? ? "(no tags)" : tags
55
- puts "Node #{node.id}: #{node.content[0..60]}..."
106
+ content_preview = node.content.to_s[0..60].gsub("\n", " ")
107
+ puts "Node #{node.id}: #{content_preview}..."
56
108
  puts " Tags: #{tags_str}"
57
- puts " Embedding: #{node.embedding ? '' : '✗'}"
58
- puts ""
109
+ puts " Embedding: #{node.embedding ? "(#{node.embedding.size}d)" : '✗'}"
110
+ puts " Created: #{node.created_at}"
111
+ puts
59
112
  end
113
+ nil
60
114
  end
61
115
 
62
- def recent_tags(limit = 10)
63
- puts "\n=== Recent Tags ==="
64
- Tag.order(created_at: :desc).limit(limit).each do |tag|
65
- count = tag.nodes.count
66
- puts "#{tag.name} (#{count} nodes)"
67
- end
68
- puts
69
- end
116
+ def recent_by_robot(robot_name, limit = 10)
117
+ robot = Robot.find_by(name: robot_name)
118
+ return puts("Robot '#{robot_name}' not found") unless robot
70
119
 
71
- def search_tags(pattern)
72
- puts "\n=== Tags matching '#{pattern}' ==="
73
- Tag.where("name LIKE ?", "%#{pattern}%").each do |tag|
74
- count = tag.nodes.count
75
- puts "#{tag.name} (#{count} nodes)"
120
+ puts "\n=== Recent Nodes by #{robot_name} ==="
121
+ robot.nodes.order(created_at: :desc).limit(limit).each do |node|
122
+ content_preview = node.content.to_s[0..60].gsub("\n", " ")
123
+ puts "Node #{node.id}: #{content_preview}..."
76
124
  end
77
- puts
125
+ nil
78
126
  end
79
127
 
80
- def node_with_tags(node_id)
81
- node = Node.includes(:tags).find(node_id)
82
- embedding_info = if node.embedding
83
- "✓ (#{node.embedding.size} dimensions)"
84
- else
85
- '✗'
86
- end
128
+ def node_info(node_id)
129
+ node = Node.includes(:tags, :robots).find(node_id)
130
+ embedding_info = node.embedding ? "✓ (#{node.embedding.size} dimensions)" : '✗'
87
131
 
88
132
  puts <<~NODE_INFO
89
133
 
90
134
  === Node #{node_id} ===
91
135
  Content: #{node.content}
92
- Source: #{node.source}
136
+ Type: #{node.memory_type}
137
+ Importance: #{node.importance}
93
138
  Created: #{node.created_at}
139
+ Last Accessed: #{node.last_accessed}
94
140
  Embedding: #{embedding_info}
141
+ Deleted: #{node.deleted_at ? "Yes (#{node.deleted_at})" : 'No'}
95
142
 
96
143
  Tags:
97
144
  NODE_INFO
@@ -101,45 +148,201 @@ def node_with_tags(node_id)
101
148
  else
102
149
  puts " (no tags)"
103
150
  end
151
+
152
+ puts "\n Robots:"
153
+ if node.robots.any?
154
+ node.robots.each { |robot| puts " - #{robot.name}" }
155
+ else
156
+ puts " (no robots)"
157
+ end
104
158
  puts
105
159
  node
106
160
  end
107
161
 
108
- def create_test_node(content, source: "irb")
109
- htm = HTM.new(robot_name: "IRB User")
110
- node_id = htm.remember(content, source: source)
111
- puts "✓ Created node #{node_id}"
112
- node_id
162
+ # Tag exploration
163
+ def tag_tree(prefix = nil)
164
+ scope = prefix ? Tag.where("name LIKE ?", "#{prefix}%") : Tag.all
165
+ puts scope.tree_string
166
+ nil
167
+ end
168
+
169
+ def recent_tags(limit = 10)
170
+ puts "\n=== Recent Tags ==="
171
+ Tag.order(created_at: :desc).limit(limit).each do |tag|
172
+ count = tag.nodes.count
173
+ puts "#{tag.name} (#{count} nodes)"
174
+ end
175
+ nil
176
+ end
177
+
178
+ def popular_tags(limit = 20)
179
+ puts "\n=== Popular Tags ==="
180
+ Tag.joins(:nodes)
181
+ .group('tags.id', 'tags.name')
182
+ .order('COUNT(nodes.id) DESC')
183
+ .limit(limit)
184
+ .pluck('tags.name', 'COUNT(nodes.id)')
185
+ .each { |name, count| puts "#{name} (#{count} nodes)" }
186
+ nil
187
+ end
188
+
189
+ def search_tags(pattern)
190
+ puts "\n=== Tags matching '#{pattern}' ==="
191
+ Tag.where("name LIKE ?", "%#{pattern}%").each do |tag|
192
+ count = tag.nodes.count
193
+ puts "#{tag.name} (#{count} nodes)"
194
+ end
195
+ nil
196
+ end
197
+
198
+ # Robot exploration
199
+ def list_robots
200
+ puts "\n=== Robots ==="
201
+ Robot.all.each do |robot|
202
+ node_count = robot.nodes.count
203
+ puts "#{robot.name} (#{node_count} nodes) - created #{robot.created_at}"
204
+ end
205
+ nil
206
+ end
207
+
208
+ # Memory operations
209
+ def remember(content, type: :fact, importance: 5.0, tags: [])
210
+ node = htm.remember(content, type: type, importance: importance, tags: tags)
211
+ puts "✓ Created node #{node.id}"
212
+ node
213
+ end
214
+
215
+ def recall(query, limit: 5, raw: false)
216
+ htm.recall(query, limit: limit, raw: raw)
217
+ end
218
+
219
+ def search(query, strategy: :hybrid, limit: 10)
220
+ htm.long_term_memory.search(query, strategy: strategy, limit: limit)
221
+ end
222
+
223
+ def similar_to(node_id, limit: 5)
224
+ node = Node.find(node_id)
225
+ node.nearest_neighbors(:embedding, distance: :cosine).limit(limit)
226
+ end
227
+
228
+ # Timeframe helpers
229
+ def today
230
+ Date.today.beginning_of_day..Date.today.end_of_day
231
+ end
232
+
233
+ def yesterday
234
+ 1.day.ago.beginning_of_day..1.day.ago.end_of_day
235
+ end
236
+
237
+ def this_week
238
+ Date.today.beginning_of_week..Date.today.end_of_day
239
+ end
240
+
241
+ def last_week
242
+ 1.week.ago.beginning_of_week..1.week.ago.end_of_week
243
+ end
244
+
245
+ def nodes_in(timeframe)
246
+ Node.where(created_at: timeframe)
247
+ end
248
+
249
+ # File loading helpers
250
+ def load_file(path, force: false)
251
+ htm.load_file(path, force: force)
252
+ end
253
+
254
+ def load_directory(path, pattern: '**/*.md', force: false)
255
+ htm.load_directory(path, pattern: pattern, force: force)
113
256
  end
114
257
 
258
+ def loaded_files
259
+ puts "\n=== Loaded Files ==="
260
+ FileSource.all.each do |fs|
261
+ sync_status = fs.needs_sync? ? "needs sync" : "synced"
262
+ puts "#{fs.file_path} (#{fs.chunks.count} chunks, #{sync_status})"
263
+ end
264
+ nil
265
+ end
266
+
267
+ # Reload helper
268
+ def reload!
269
+ puts "Reloading HTM library..."
270
+ load 'lib/htm.rb'
271
+ @htm = nil
272
+ @user = nil
273
+ @assistant = nil
274
+ @researcher = nil
275
+ @coder = nil
276
+ puts "✓ Reloaded"
277
+ end
278
+
279
+ # Help
115
280
  def htm_help
116
- puts <<~WELCOME
281
+ puts <<~HELP
117
282
 
118
283
  ============================================================
119
284
  HTM Interactive Console
120
285
  ============================================================
121
286
 
122
- Available models:
123
- - Node (HTM::Models::Node)
124
- - Tag (HTM::Models::Tag)
125
- - NodeTag (HTM::Models::NodeTag)
126
- - Robot (HTM::Models::Robot)
287
+ ROBOT INSTANCES (lazy-loaded):
288
+ htm - Default console instance
289
+ user - "User" robot (the human)
290
+ assistant - "Assistant" robot
291
+ researcher - "Researcher" robot
292
+ coder - "Coder" robot
293
+
294
+ MODEL CONSTANTS:
295
+ Node, Tag, NodeTag, Robot, RobotNode, FileSource
296
+
297
+ MEMORY OPERATIONS:
298
+ remember(content, type:, importance:, tags:)
299
+ recall(query, limit:, raw:)
300
+ search(query, strategy:, limit:)
301
+ similar_to(node_id, limit:)
302
+
303
+ NODE EXPLORATION:
304
+ recent_nodes(limit)
305
+ recent_by_robot(robot_name, limit)
306
+ node_info(node_id)
307
+ nodes_in(timeframe)
308
+
309
+ TAG EXPLORATION:
310
+ tag_tree(prefix)
311
+ recent_tags(limit)
312
+ popular_tags(limit)
313
+ search_tags(pattern)
314
+
315
+ ROBOT EXPLORATION:
316
+ list_robots
127
317
 
128
- Helper methods:
129
- htm_help # Reprints this message
130
- db_stats # Show database statistics
131
- recent_nodes(n) # Show n recent nodes (default: 5)
132
- recent_tags(n) # Show n recent tags (default: 10)
133
- search_tags(pattern) # Search tags by pattern
134
- node_with_tags(id) # Show node details with tags
135
- create_test_node(str) # Create a test node
136
- reload! # Reload HTM library
318
+ FILE LOADING:
319
+ load_file(path, force:)
320
+ load_directory(path, pattern:, force:)
321
+ loaded_files
137
322
 
138
- Database: #{HTM::Database.default_config[:dbname]}
139
- WELCOME
323
+ TIMEFRAME HELPERS:
324
+ today, yesterday, this_week, last_week
325
+
326
+ RUBYLLM:
327
+ llm - RubyLLM module
328
+ chat(message, model:) - Quick chat
329
+ embed(text, model:) - Generate embedding
330
+
331
+ DATABASE:
332
+ db_stats - Show table counts
333
+ health - Full health check
334
+ healthy? - Quick boolean check
335
+
336
+ UTILITIES:
337
+ reload! - Reload HTM library
338
+ htm_help - Show this help
339
+
340
+ HELP
341
+ nil
140
342
  end
141
343
 
142
- htm_help
143
- db_stats
344
+ puts "✓ Helpers loaded"
345
+ puts "=" * 60
346
+ puts "\nType 'htm_help' for available commands\n\n"
144
347
 
145
- print "HTM Ready!\n\n"
348
+ db_stats
data/.tbls.yml CHANGED
@@ -2,7 +2,8 @@
2
2
  # https://github.com/k1LoW/tbls
3
3
 
4
4
  # Database connection - uses HTM_DBURL environment variable
5
- dsn: env://HTM_DBURL
5
+ # tbls expands environment variables with $VAR or ${VAR} syntax
6
+ dsn: $HTM_DBURL
6
7
 
7
8
  # Output documentation to docs/database directory
8
9
  docPath: docs/database