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
@@ -106,94 +106,79 @@ puts "Robot ID: #{htm.robot_id}"
106
106
 
107
107
  ```ruby
108
108
  # Add a fact about the user
109
- htm.add_node(
110
- "user_name", # Unique key
111
- "The user's name is Alice", # Content
112
- type: :fact, # Memory type
113
- importance: 8.0, # Importance score (0-10)
114
- tags: ["user", "identity"] # Tags for categorization
109
+ node_id = htm.remember(
110
+ "The user's name is Alice", # Content
111
+ tags: ["user", "identity"], # Tags for categorization
112
+ metadata: { category: "fact" } # Metadata for priority/categorization
115
113
  )
116
114
 
117
- puts "Memory added successfully!"
115
+ puts "Memory added with ID: #{node_id}"
118
116
  ```
119
117
 
120
- ### Understanding Memory Types
118
+ ### Using Tags and Metadata
121
119
 
122
- HTM supports six memory types, each optimized for different purposes:
120
+ HTM uses hierarchical tags and flexible metadata to categorize memories:
123
121
 
124
122
  ```ruby
125
123
  # Facts: Immutable truths
126
- htm.add_node(
127
- "fact_001",
124
+ htm.remember(
128
125
  "The user lives in San Francisco",
129
- type: :fact,
130
- importance: 7.0
126
+ tags: ["user:location"],
127
+ metadata: { category: "fact", priority: "high" }
131
128
  )
132
129
 
133
130
  # Context: Conversation state
134
- htm.add_node(
135
- "context_001",
131
+ htm.remember(
136
132
  "Currently discussing database architecture for a new project",
137
- type: :context,
138
- importance: 6.0
133
+ tags: ["conversation:topic"],
134
+ metadata: { category: "context" }
139
135
  )
140
136
 
141
137
  # Preferences: User preferences
142
- htm.add_node(
143
- "pref_001",
138
+ htm.remember(
144
139
  "User prefers Ruby over Python for scripting",
145
- type: :preference,
146
- importance: 5.0
140
+ tags: ["user:preference", "language:ruby"],
141
+ metadata: { category: "preference" }
147
142
  )
148
143
 
149
144
  # Decisions: Design decisions
150
- htm.add_node(
151
- "decision_001",
145
+ htm.remember(
152
146
  "Decided to use PostgreSQL instead of MongoDB for better consistency",
153
- type: :decision,
154
- importance: 9.0,
155
- tags: ["architecture", "database"]
147
+ tags: ["architecture", "database:postgresql"],
148
+ metadata: { category: "decision", priority: "critical" }
156
149
  )
157
150
 
158
151
  # Code: Code snippets
159
- htm.add_node(
160
- "code_001",
152
+ htm.remember(
161
153
  "def greet(name)\n puts \"Hello, \#{name}!\"\nend",
162
- type: :code,
163
- importance: 4.0,
164
- tags: ["ruby", "functions"]
154
+ tags: ["code:ruby", "pattern:function"],
155
+ metadata: { category: "code", language: "ruby" }
165
156
  )
166
157
 
167
158
  # Questions: Unresolved questions
168
- htm.add_node(
169
- "question_001",
159
+ htm.remember(
170
160
  "Should we add Redis caching to improve performance?",
171
- type: :question,
172
- importance: 6.0,
173
- tags: ["performance", "caching"]
161
+ tags: ["performance", "caching"],
162
+ metadata: { category: "question", resolved: false }
174
163
  )
175
164
  ```
176
165
 
177
- !!! tip "Choosing the Right Type"
178
- - Use `:fact` for unchanging information
179
- - Use `:context` for temporary conversation state
180
- - Use `:preference` for user settings
181
- - Use `:decision` for important architectural choices
182
- - Use `:code` for code examples and snippets
183
- - Use `:question` for tracking open questions
166
+ !!! tip "Organizing Memories"
167
+ - Use hierarchical tags (e.g., `user:preference`, `database:postgresql`)
168
+ - Use metadata for structured data (priority, category, resolved status)
169
+ - Combine tags and metadata for powerful filtering
184
170
 
185
171
  ### Retrieving Memories
186
172
 
187
- Retrieve a specific memory by its key:
173
+ Retrieve a specific memory by its ID:
188
174
 
189
175
  ```ruby
190
- memory = htm.retrieve("user_name")
176
+ # Retrieve by ID (returned from remember())
177
+ memory = htm.long_term_memory.retrieve(node_id)
191
178
 
192
179
  if memory
193
- puts "Found: #{memory['value']}"
194
- puts "Type: #{memory['type']}"
195
- puts "Created: #{memory['created_at']}"
196
- puts "Importance: #{memory['importance']}"
180
+ puts "Found: #{memory.content}"
181
+ puts "Created: #{memory.created_at}"
197
182
  end
198
183
  ```
199
184
 
@@ -204,13 +189,14 @@ Use HTM's RAG capabilities to recall relevant memories:
204
189
  ```ruby
205
190
  # Recall memories about databases from the last week
206
191
  memories = htm.recall(
192
+ "database architecture", # Topic is first positional argument
207
193
  timeframe: "last week",
208
- topic: "database architecture",
209
- limit: 10
194
+ limit: 10,
195
+ raw: true # Get full hash with similarity scores
210
196
  )
211
197
 
212
198
  memories.each do |memory|
213
- puts "- #{memory['value']}"
199
+ puts "- #{memory['content']}"
214
200
  puts " Similarity: #{memory['similarity']}"
215
201
  puts
216
202
  end
@@ -225,7 +211,7 @@ Assemble working memory into context for your LLM:
225
211
 
226
212
  ```ruby
227
213
  # Get a balanced mix of important and recent memories
228
- context = htm.create_context(
214
+ context = htm.working_memory.assemble_context(
229
215
  strategy: :balanced,
230
216
  max_tokens: 50_000
231
217
  )
@@ -260,27 +246,22 @@ class ConversationTracker
260
246
  @turn += 1
261
247
 
262
248
  # Store user message
263
- @htm.add_node(
264
- "turn_#{@turn}_user",
249
+ @htm.remember(
265
250
  user_message,
266
- type: :context,
267
- importance: 5.0,
268
- tags: ["conversation", "user"]
251
+ tags: ["conversation", "user", "turn:#{@turn}"],
252
+ metadata: { category: "context", turn: @turn, role: "user" }
269
253
  )
270
254
 
271
255
  # Store assistant response
272
- @htm.add_node(
273
- "turn_#{@turn}_assistant",
256
+ @htm.remember(
274
257
  assistant_response,
275
- type: :context,
276
- importance: 5.0,
277
- tags: ["conversation", "assistant"],
278
- related_to: ["turn_#{@turn}_user"]
258
+ tags: ["conversation", "assistant", "turn:#{@turn}"],
259
+ metadata: { category: "context", turn: @turn, role: "assistant" }
279
260
  )
280
261
  end
281
262
 
282
263
  def recall_context
283
- @htm.create_context(strategy: :recent, max_tokens: 10_000)
264
+ @htm.working_memory.assemble_context(strategy: :recent, max_tokens: 10_000)
284
265
  end
285
266
  end
286
267
  ```
@@ -295,22 +276,19 @@ class KnowledgeBase
295
276
  @htm = HTM.new(robot_name: "Knowledge Bot")
296
277
  end
297
278
 
298
- def add_fact(key, fact, category:, tags: [])
299
- @htm.add_node(
300
- key,
279
+ def add_fact(fact, category:, tags: [])
280
+ @htm.remember(
301
281
  fact,
302
- type: :fact,
303
- category: category,
304
- importance: 8.0,
305
- tags: tags
282
+ tags: tags,
283
+ metadata: { category: category, priority: "high" }
306
284
  )
307
285
  end
308
286
 
309
287
  def query(question)
310
288
  # Search all time for relevant facts
311
289
  @htm.recall(
290
+ question,
312
291
  timeframe: "last 10 years", # Effectively all memories
313
- topic: question,
314
292
  limit: 5
315
293
  )
316
294
  end
@@ -319,7 +297,6 @@ end
319
297
  # Usage
320
298
  kb = KnowledgeBase.new
321
299
  kb.add_fact(
322
- "ruby_version",
323
300
  "Ruby 3.0 introduced Ractors for parallel execution",
324
301
  category: "programming",
325
302
  tags: ["ruby", "concurrency"]
@@ -339,8 +316,6 @@ class DecisionJournal
339
316
  end
340
317
 
341
318
  def record_decision(title, rationale, alternatives: [], tags: [])
342
- key = "decision_#{Time.now.to_i}"
343
-
344
319
  decision = <<~DECISION
345
320
  Decision: #{title}
346
321
 
@@ -349,20 +324,19 @@ class DecisionJournal
349
324
  #{alternatives.any? ? "Alternatives considered: #{alternatives.join(', ')}" : ''}
350
325
  DECISION
351
326
 
352
- @htm.add_node(
353
- key,
327
+ @htm.remember(
354
328
  decision,
355
- type: :decision,
356
- importance: 9.0,
357
- tags: tags + ["decision"]
329
+ tags: tags + ["decision"],
330
+ metadata: { category: "decision", priority: "critical" }
358
331
  )
359
332
  end
360
333
 
361
334
  def get_decision_history(topic)
362
335
  @htm.recall(
336
+ topic,
363
337
  timeframe: "last year",
364
- topic: topic,
365
- limit: 20
338
+ limit: 20,
339
+ raw: true
366
340
  ).sort_by { |d| d['created_at'] }
367
341
  end
368
342
  end
@@ -379,11 +353,9 @@ puts "Nodes in working memory: #{wm.node_count}"
379
353
  puts "Tokens used: #{wm.token_count} / #{wm.max_tokens}"
380
354
  puts "Utilization: #{wm.utilization_percentage}%"
381
355
 
382
- # Get comprehensive stats
383
- stats = htm.memory_stats
384
- puts "Total nodes in long-term: #{stats[:total_nodes]}"
385
- puts "Active robots: #{stats[:active_robots]}"
386
- puts "Database size: #{stats[:database_size] / (1024.0 * 1024.0)} MB"
356
+ # Get comprehensive stats using ActiveRecord
357
+ puts "Total nodes in long-term: #{HTM::Models::Node.count}"
358
+ puts "Active robots: #{HTM::Models::Robot.count}"
387
359
  ```
388
360
 
389
361
  !!! tip
@@ -391,73 +363,103 @@ puts "Database size: #{stats[:database_size] / (1024.0 * 1024.0)} MB"
391
363
 
392
364
  ## Best Practices for Beginners
393
365
 
394
- ### 1. Use Meaningful Keys
366
+ ### 1. Use Descriptive Content
395
367
 
396
368
  ```ruby
397
- # Good: Descriptive, unique keys
398
- htm.add_node("user_pref_theme_dark", "User prefers dark theme", ...)
369
+ # Good: Clear, self-contained content
370
+ htm.remember(
371
+ "User prefers dark theme for all interfaces",
372
+ tags: ["user:preference", "ui:theme"],
373
+ metadata: { category: "preference" }
374
+ )
399
375
 
400
- # Bad: Generic keys that might conflict
401
- htm.add_node("pref", "User prefers dark theme", ...)
376
+ # Bad: Vague or context-dependent
377
+ htm.remember("prefers dark", tags: ["pref"])
402
378
  ```
403
379
 
404
- ### 2. Set Appropriate Importance
380
+ ### 2. Use Metadata for Priority
405
381
 
406
382
  ```ruby
407
- # Critical facts: 9-10
408
- htm.add_node("api_key", "API key is: ...", importance: 10.0)
383
+ # Critical facts
384
+ htm.remember(
385
+ "Production API endpoint: https://api.example.com",
386
+ tags: ["api", "production"],
387
+ metadata: { priority: "critical", category: "fact" }
388
+ )
409
389
 
410
- # Important decisions: 7-9
411
- htm.add_node("arch_001", "Using microservices", importance: 8.0)
390
+ # Important decisions
391
+ htm.remember(
392
+ "Using microservices architecture for scalability",
393
+ tags: ["architecture", "decision"],
394
+ metadata: { priority: "high", category: "decision" }
395
+ )
412
396
 
413
- # Contextual information: 4-6
414
- htm.add_node("ctx_001", "Discussing weather", importance: 5.0)
397
+ # Contextual information
398
+ htm.remember(
399
+ "Currently discussing weather API integration",
400
+ tags: ["context", "conversation"],
401
+ metadata: { priority: "medium", category: "context" }
402
+ )
415
403
 
416
- # Temporary notes: 1-3
417
- htm.add_node("note_001", "Remember to check logs", importance: 2.0)
404
+ # Temporary notes
405
+ htm.remember(
406
+ "Remember to check server logs after deployment",
407
+ tags: ["todo", "temporary"],
408
+ metadata: { priority: "low", category: "note" }
409
+ )
418
410
  ```
419
411
 
420
412
  ### 3. Use Tags Liberally
421
413
 
422
414
  ```ruby
423
- htm.add_node(
424
- "decision_001",
425
- "Chose PostgreSQL for data persistence",
426
- type: :decision,
427
- importance: 9.0,
415
+ htm.remember(
416
+ "Chose PostgreSQL for data persistence due to ACID compliance",
428
417
  tags: [
429
418
  "database",
430
419
  "architecture",
431
420
  "backend",
432
421
  "persistence",
433
- "postgresql"
434
- ]
422
+ "database:postgresql"
423
+ ],
424
+ metadata: { category: "decision", priority: "high" }
435
425
  )
436
426
  ```
437
427
 
438
- ### 4. Leverage Relationships
428
+ ### 4. Use Hierarchical Tags for Relationships
439
429
 
440
430
  ```ruby
441
- # Add related memories
442
- htm.add_node("decision_db", "Use PostgreSQL", type: :decision)
443
-
444
- htm.add_node(
445
- "code_db_connect",
446
- "Connection code for PostgreSQL",
447
- type: :code,
448
- related_to: ["decision_db"] # Link to the decision
431
+ # Add related memories using hierarchical tags
432
+ htm.remember(
433
+ "Use PostgreSQL for primary data storage",
434
+ tags: ["decision:database", "database:postgresql"],
435
+ metadata: { category: "decision" }
436
+ )
437
+
438
+ htm.remember(
439
+ "PG.connect(host: 'localhost', dbname: 'app_production')",
440
+ tags: ["code:ruby", "database:postgresql", "decision:database"],
441
+ metadata: { category: "code", language: "ruby" }
449
442
  )
443
+
444
+ # Find related by shared tags
445
+ htm.recall("database:postgresql", timeframe: "last month", limit: 10)
450
446
  ```
451
447
 
452
448
  ### 5. Clean Up When Needed
453
449
 
454
450
  ```ruby
455
- # Explicitly forget outdated information
456
- htm.forget("old_api_key", confirm: :confirmed)
451
+ # Store the node_id when creating memories you may want to remove
452
+ node_id = htm.remember("Temporary calculation result", tags: ["scratch"])
453
+
454
+ # Later, soft delete (recoverable)
455
+ htm.forget(node_id)
456
+
457
+ # Or permanently delete with confirmation
458
+ htm.forget(node_id, soft: false, confirm: :confirmed)
457
459
  ```
458
460
 
459
461
  !!! warning
460
- The `forget` method requires explicit confirmation to prevent accidental data loss. HTM never deletes memories automatically.
462
+ Permanent deletion requires explicit confirmation to prevent accidental data loss. Use soft delete (default) when possible - you can always restore with `htm.restore(node_id)`.
461
463
 
462
464
  ## Troubleshooting
463
465
 
@@ -493,12 +495,22 @@ end
493
495
  ### Memory Not Found
494
496
 
495
497
  ```ruby
496
- memory = htm.retrieve("my_key")
498
+ # Retrieve by node_id (returned from remember())
499
+ node_id = 123 # Use the ID you saved when creating the memory
500
+ memory = htm.long_term_memory.retrieve(node_id)
497
501
 
498
502
  if memory.nil?
499
- puts "Memory not found. Check the key spelling."
503
+ puts "Memory not found. Check the node ID."
500
504
  else
501
- puts "Found: #{memory['value']}"
505
+ puts "Found: #{memory.content}"
506
+ end
507
+
508
+ # Or search for memories by content
509
+ results = htm.recall("what I'm looking for", timeframe: "last month", limit: 5)
510
+ if results.empty?
511
+ puts "No matching memories found."
512
+ else
513
+ puts "Found #{results.length} matches"
502
514
  end
503
515
  ```
504
516
 
@@ -525,53 +537,47 @@ htm = HTM.new(
525
537
  )
526
538
 
527
539
  # Add various memories
528
- htm.add_node(
529
- "user_001",
540
+ htm.remember(
530
541
  "User's name is Alice and she's a software engineer",
531
- type: :fact,
532
- importance: 8.0,
533
- tags: ["user", "identity", "profession"]
542
+ tags: ["user", "identity", "profession"],
543
+ metadata: { category: "fact", priority: "high" }
534
544
  )
535
545
 
536
- htm.add_node(
537
- "decision_001",
546
+ htm.remember(
538
547
  "Decided to use HTM for managing conversation memory",
539
- type: :decision,
540
- importance: 9.0,
541
- tags: ["architecture", "memory"]
548
+ tags: ["architecture", "memory", "decision"],
549
+ metadata: { category: "decision", priority: "critical" }
542
550
  )
543
551
 
544
- htm.add_node(
545
- "pref_001",
552
+ htm.remember(
546
553
  "Alice prefers detailed explanations with examples",
547
- type: :preference,
548
- importance: 7.0,
549
- tags: ["user", "communication"],
550
- related_to: ["user_001"]
554
+ tags: ["user", "communication", "user:preference"],
555
+ metadata: { category: "preference" }
551
556
  )
552
557
 
553
558
  # Recall relevant memories
554
559
  memories = htm.recall(
560
+ "user preferences", # Topic is positional
555
561
  timeframe: "last 7 days",
556
- topic: "user preferences",
557
- limit: 5
562
+ limit: 5,
563
+ raw: true # Get full hash with similarity scores
558
564
  )
559
565
 
560
566
  puts "Found #{memories.length} relevant memories:"
561
567
  memories.each do |m|
562
- puts "- #{m['value']} (importance: #{m['importance']})"
568
+ puts "- #{m['content']}"
563
569
  end
564
570
 
565
571
  # Create context for LLM
566
- context = htm.create_context(strategy: :balanced)
572
+ context = htm.working_memory.assemble_context(strategy: :balanced)
567
573
  puts "\nContext length: #{context.length} characters"
568
574
 
569
575
  # Check stats
570
- stats = htm.memory_stats
576
+ wm = htm.working_memory
571
577
  puts "\nMemory statistics:"
572
- puts "- Total nodes: #{stats[:total_nodes]}"
573
- puts "- Working memory: #{stats[:working_memory][:utilization]}% full"
574
- puts "- Database size: #{(stats[:database_size] / 1024.0 / 1024.0).round(2)} MB"
578
+ puts "- Total nodes in database: #{HTM::Models::Node.count}"
579
+ puts "- Working memory nodes: #{wm.node_count}"
580
+ puts "- Working memory: #{wm.utilization_percentage}% full"
575
581
  ```
576
582
 
577
583
  Happy coding with HTM!
data/docs/guides/index.md CHANGED
@@ -36,6 +36,12 @@ Dive deeper into HTM's powerful capabilities.
36
36
  - [**Search Strategies**](search-strategies.md) - Vector, full-text, and hybrid search
37
37
  - [**Context Assembly**](context-assembly.md) - Creating optimized context for LLMs
38
38
 
39
+ ### Integrations
40
+
41
+ Connect HTM to external tools and services.
42
+
43
+ - [**MCP Server**](mcp-server.md) - Use HTM with Claude Desktop, Claude Code, and AIA
44
+
39
45
  ## Learning Path
40
46
 
41
47
  We recommend the following progression:
@@ -68,6 +74,7 @@ We recommend the following progression:
68
74
  - **Create LLM context**: See [Context Assembly](context-assembly.md#basic-usage)
69
75
  - **Monitor memory usage**: See [Working Memory](working-memory.md#monitoring-utilization)
70
76
  - **Multi-robot setup**: See [Multi-Robot Usage](multi-robot.md#setting-up-multiple-robots)
77
+ - **Use with Claude/AIA**: See [MCP Server](mcp-server.md#client-configuration)
71
78
 
72
79
  ### Memory Types
73
80