htm 0.0.2 → 0.0.10
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.
- checksums.yaml +4 -4
- data/.aigcm_msg +1 -0
- data/.architecture/reviews/comprehensive-codebase-review.md +577 -0
- data/.claude/settings.local.json +92 -0
- data/.irbrc +283 -80
- data/.tbls.yml +2 -1
- data/CHANGELOG.md +294 -26
- data/CLAUDE.md +603 -0
- data/README.md +76 -5
- data/Rakefile +5 -0
- data/db/migrate/{20250101000001_enable_extensions.rb → 00001_enable_extensions.rb} +0 -1
- data/db/migrate/00002_create_robots.rb +11 -0
- data/db/migrate/00003_create_file_sources.rb +20 -0
- data/db/migrate/00004_create_nodes.rb +65 -0
- data/db/migrate/00005_create_tags.rb +13 -0
- data/db/migrate/00006_create_node_tags.rb +18 -0
- data/db/migrate/00007_create_robot_nodes.rb +26 -0
- data/db/migrate/00009_add_working_memory_to_robot_nodes.rb +12 -0
- data/db/schema.sql +172 -1
- data/docs/api/database.md +1 -2
- data/docs/api/htm.md +197 -2
- data/docs/api/yard/HTM/ActiveRecordConfig.md +23 -0
- data/docs/api/yard/HTM/AuthorizationError.md +11 -0
- data/docs/api/yard/HTM/CircuitBreaker.md +92 -0
- data/docs/api/yard/HTM/CircuitBreakerOpenError.md +34 -0
- data/docs/api/yard/HTM/Configuration.md +175 -0
- data/docs/api/yard/HTM/Database.md +99 -0
- data/docs/api/yard/HTM/DatabaseError.md +14 -0
- data/docs/api/yard/HTM/EmbeddingError.md +18 -0
- data/docs/api/yard/HTM/EmbeddingService.md +58 -0
- data/docs/api/yard/HTM/Error.md +11 -0
- data/docs/api/yard/HTM/JobAdapter.md +39 -0
- data/docs/api/yard/HTM/LongTermMemory.md +342 -0
- data/docs/api/yard/HTM/NotFoundError.md +17 -0
- data/docs/api/yard/HTM/Observability.md +107 -0
- data/docs/api/yard/HTM/QueryTimeoutError.md +19 -0
- data/docs/api/yard/HTM/Railtie.md +27 -0
- data/docs/api/yard/HTM/ResourceExhaustedError.md +13 -0
- data/docs/api/yard/HTM/TagError.md +18 -0
- data/docs/api/yard/HTM/TagService.md +67 -0
- data/docs/api/yard/HTM/Timeframe/Result.md +24 -0
- data/docs/api/yard/HTM/Timeframe.md +40 -0
- data/docs/api/yard/HTM/TimeframeExtractor/Result.md +24 -0
- data/docs/api/yard/HTM/TimeframeExtractor.md +45 -0
- data/docs/api/yard/HTM/ValidationError.md +20 -0
- data/docs/api/yard/HTM/WorkingMemory.md +131 -0
- data/docs/api/yard/HTM.md +80 -0
- data/docs/api/yard/index.csv +179 -0
- data/docs/api/yard-reference.md +51 -0
- data/docs/database/README.md +128 -128
- data/docs/database/public.file_sources.md +42 -0
- data/docs/database/public.file_sources.svg +211 -0
- data/docs/database/public.node_tags.md +4 -4
- data/docs/database/public.node_tags.svg +212 -79
- data/docs/database/public.nodes.md +22 -12
- data/docs/database/public.nodes.svg +246 -127
- data/docs/database/public.robot_nodes.md +11 -9
- data/docs/database/public.robot_nodes.svg +220 -98
- data/docs/database/public.robots.md +2 -2
- data/docs/database/public.robots.svg +136 -81
- data/docs/database/public.tags.md +3 -3
- data/docs/database/public.tags.svg +118 -39
- data/docs/database/schema.json +850 -771
- data/docs/database/schema.svg +256 -197
- data/docs/development/schema.md +67 -2
- data/docs/guides/adding-memories.md +93 -7
- data/docs/guides/recalling-memories.md +36 -1
- data/examples/README.md +280 -0
- data/examples/cli_app/htm_cli.rb +65 -5
- data/examples/cli_app/temp.log +93 -0
- data/examples/file_loader_usage.rb +177 -0
- data/examples/robot_groups/lib/robot_group.rb +419 -0
- data/examples/robot_groups/lib/working_memory_channel.rb +140 -0
- data/examples/robot_groups/multi_process.rb +286 -0
- data/examples/robot_groups/robot_worker.rb +136 -0
- data/examples/robot_groups/same_process.rb +229 -0
- data/examples/timeframe_demo.rb +276 -0
- data/lib/htm/active_record_config.rb +1 -1
- data/lib/htm/circuit_breaker.rb +202 -0
- data/lib/htm/configuration.rb +59 -13
- data/lib/htm/database.rb +67 -36
- data/lib/htm/embedding_service.rb +39 -2
- data/lib/htm/errors.rb +131 -11
- data/lib/htm/jobs/generate_embedding_job.rb +5 -4
- data/lib/htm/jobs/generate_tags_job.rb +4 -0
- data/lib/htm/loaders/markdown_loader.rb +263 -0
- data/lib/htm/loaders/paragraph_chunker.rb +112 -0
- data/lib/htm/long_term_memory.rb +460 -343
- data/lib/htm/models/file_source.rb +99 -0
- data/lib/htm/models/node.rb +80 -5
- data/lib/htm/models/robot.rb +24 -1
- data/lib/htm/models/robot_node.rb +1 -0
- data/lib/htm/models/tag.rb +254 -4
- data/lib/htm/observability.rb +395 -0
- data/lib/htm/tag_service.rb +60 -3
- data/lib/htm/tasks.rb +26 -1
- data/lib/htm/timeframe.rb +194 -0
- data/lib/htm/timeframe_extractor.rb +307 -0
- data/lib/htm/version.rb +1 -1
- data/lib/htm/working_memory.rb +165 -70
- data/lib/htm.rb +328 -130
- data/lib/tasks/doc.rake +300 -0
- data/lib/tasks/files.rake +299 -0
- data/lib/tasks/htm.rake +158 -3
- data/lib/tasks/jobs.rake +3 -9
- data/lib/tasks/tags.rake +166 -6
- data/mkdocs.yml +36 -1
- data/notes/ARCHITECTURE_REVIEW.md +1167 -0
- data/notes/IMPLEMENTATION_SUMMARY.md +606 -0
- data/notes/MULTI_FRAMEWORK_IMPLEMENTATION.md +451 -0
- data/notes/next_steps.md +100 -0
- data/notes/plan.md +627 -0
- data/notes/tag_ontology_enhancement_ideas.md +222 -0
- data/notes/timescaledb_removal_summary.md +200 -0
- metadata +125 -15
- data/db/migrate/20250101000002_create_robots.rb +0 -14
- data/db/migrate/20250101000003_create_nodes.rb +0 -42
- data/db/migrate/20250101000005_create_tags.rb +0 -38
- data/db/migrate/20250101000007_add_node_vector_indexes.rb +0 -30
- data/db/migrate/20250125000001_add_content_hash_to_nodes.rb +0 -14
- data/db/migrate/20250125000002_create_robot_nodes.rb +0 -35
- data/db/migrate/20250125000003_remove_source_and_robot_id_from_nodes.rb +0 -28
- data/db/migrate/20250126000001_create_working_memories.rb +0 -19
- data/db/migrate/20250126000002_remove_unused_columns.rb +0 -12
- data/docs/database/public.working_memories.md +0 -40
- data/docs/database/public.working_memories.svg +0 -112
- data/lib/htm/models/working_memory_entry.rb +0 -88
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class CreateNodes < ActiveRecord::Migration[7.1]
|
|
4
|
-
def change
|
|
5
|
-
unless table_exists?(:nodes)
|
|
6
|
-
create_table :nodes, comment: 'Core memory storage for conversation messages and context' do |t|
|
|
7
|
-
t.text :content, null: false, comment: 'The conversation message/utterance content'
|
|
8
|
-
t.text :source, default: '', comment: 'From where the content came (empty string if unknown)'
|
|
9
|
-
t.integer :access_count, default: 0, null: false, comment: 'Number of times this node has been accessed/retrieved'
|
|
10
|
-
t.timestamptz :created_at, default: -> { 'CURRENT_TIMESTAMP' }, comment: 'When this memory was created'
|
|
11
|
-
t.timestamptz :updated_at, default: -> { 'CURRENT_TIMESTAMP' }, comment: 'When this memory was last modified'
|
|
12
|
-
t.timestamptz :last_accessed, default: -> { 'CURRENT_TIMESTAMP' }, comment: 'When this memory was last accessed'
|
|
13
|
-
t.integer :token_count, comment: 'Number of tokens in the content (for context budget management)'
|
|
14
|
-
t.boolean :in_working_memory, default: false, comment: 'Whether this memory is currently in working memory'
|
|
15
|
-
t.bigint :robot_id, null: false, comment: 'ID of the robot that owns this memory'
|
|
16
|
-
t.vector :embedding, limit: 2000, comment: 'Vector embedding (max 2000 dimensions) for semantic search'
|
|
17
|
-
t.integer :embedding_dimension, comment: 'Actual number of dimensions used in the embedding vector (max 2000)'
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
# Basic indexes for common queries
|
|
21
|
-
add_index :nodes, :created_at, name: 'idx_nodes_created_at'
|
|
22
|
-
add_index :nodes, :updated_at, name: 'idx_nodes_updated_at'
|
|
23
|
-
add_index :nodes, :last_accessed, name: 'idx_nodes_last_accessed'
|
|
24
|
-
add_index :nodes, :access_count, name: 'idx_nodes_access_count'
|
|
25
|
-
add_index :nodes, :robot_id, name: 'idx_nodes_robot_id'
|
|
26
|
-
add_index :nodes, :source, name: 'idx_nodes_source'
|
|
27
|
-
add_index :nodes, :in_working_memory, name: 'idx_nodes_in_working_memory'
|
|
28
|
-
|
|
29
|
-
# Add check constraint for embedding dimensions
|
|
30
|
-
# Only validates when embedding_dimension is provided (allows NULL for nodes without embeddings)
|
|
31
|
-
execute <<-SQL
|
|
32
|
-
ALTER TABLE nodes ADD CONSTRAINT check_embedding_dimension
|
|
33
|
-
CHECK (embedding_dimension IS NULL OR (embedding_dimension > 0 AND embedding_dimension <= 2000))
|
|
34
|
-
SQL
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Foreign key to robots table (outside table_exists check so it gets added even if table already exists)
|
|
38
|
-
unless foreign_key_exists?(:nodes, :robots, column: :robot_id)
|
|
39
|
-
add_foreign_key :nodes, :robots, column: :robot_id, primary_key: :id, on_delete: :cascade
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class CreateTags < ActiveRecord::Migration[7.1]
|
|
4
|
-
def change
|
|
5
|
-
# Create tags table with unique tag names
|
|
6
|
-
unless table_exists?(:tags)
|
|
7
|
-
create_table :tags, comment: 'Unique tag names for categorization' do |t|
|
|
8
|
-
t.text :name, null: false, comment: 'Hierarchical tag in format: root:level1:level2 (e.g., database:postgresql:timescaledb)'
|
|
9
|
-
t.timestamptz :created_at, default: -> { 'CURRENT_TIMESTAMP' }, comment: 'When this tag was created'
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
add_index :tags, :name, unique: true, name: 'idx_tags_name_unique'
|
|
13
|
-
add_index :tags, :name, using: :btree, opclass: :text_pattern_ops, name: 'idx_tags_name_pattern'
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# Create join table for many-to-many relationship
|
|
17
|
-
unless table_exists?(:node_tags)
|
|
18
|
-
create_table :node_tags, comment: 'Join table connecting nodes to tags (many-to-many)' do |t|
|
|
19
|
-
t.bigint :node_id, null: false, comment: 'ID of the node being tagged'
|
|
20
|
-
t.bigint :tag_id, null: false, comment: 'ID of the tag being applied'
|
|
21
|
-
t.timestamptz :created_at, default: -> { 'CURRENT_TIMESTAMP' }, comment: 'When this association was created'
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
add_index :node_tags, [:node_id, :tag_id], unique: true, name: 'idx_node_tags_unique'
|
|
25
|
-
add_index :node_tags, :node_id, name: 'idx_node_tags_node_id'
|
|
26
|
-
add_index :node_tags, :tag_id, name: 'idx_node_tags_tag_id'
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# Add foreign keys (outside table_exists check so they get added even if table already exists)
|
|
30
|
-
unless foreign_key_exists?(:node_tags, :nodes, column: :node_id)
|
|
31
|
-
add_foreign_key :node_tags, :nodes, column: :node_id, primary_key: :id, on_delete: :cascade
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
unless foreign_key_exists?(:node_tags, :tags, column: :tag_id)
|
|
35
|
-
add_foreign_key :node_tags, :tags, column: :tag_id, primary_key: :id, on_delete: :cascade
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class AddNodeVectorIndexes < ActiveRecord::Migration[7.1]
|
|
4
|
-
def up
|
|
5
|
-
# Vector similarity search index (HNSW for better performance)
|
|
6
|
-
execute <<-SQL
|
|
7
|
-
CREATE INDEX IF NOT EXISTS idx_nodes_embedding ON nodes
|
|
8
|
-
USING hnsw (embedding vector_cosine_ops)
|
|
9
|
-
WITH (m = 16, ef_construction = 64)
|
|
10
|
-
SQL
|
|
11
|
-
|
|
12
|
-
# Full-text search on conversation content
|
|
13
|
-
execute <<-SQL
|
|
14
|
-
CREATE INDEX IF NOT EXISTS idx_nodes_content_gin ON nodes
|
|
15
|
-
USING gin(to_tsvector('english', content))
|
|
16
|
-
SQL
|
|
17
|
-
|
|
18
|
-
# Trigram indexes for fuzzy matching on conversation content
|
|
19
|
-
execute <<-SQL
|
|
20
|
-
CREATE INDEX IF NOT EXISTS idx_nodes_content_trgm ON nodes
|
|
21
|
-
USING gin(content gin_trgm_ops)
|
|
22
|
-
SQL
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def down
|
|
26
|
-
remove_index :nodes, name: 'idx_nodes_embedding'
|
|
27
|
-
remove_index :nodes, name: 'idx_nodes_content_gin'
|
|
28
|
-
remove_index :nodes, name: 'idx_nodes_content_trgm'
|
|
29
|
-
end
|
|
30
|
-
end
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class AddContentHashToNodes < ActiveRecord::Migration[7.1]
|
|
4
|
-
def change
|
|
5
|
-
unless column_exists?(:nodes, :content_hash)
|
|
6
|
-
# Add content_hash column for SHA-256 hash of content (64 hex characters)
|
|
7
|
-
add_column :nodes, :content_hash, :string, limit: 64,
|
|
8
|
-
comment: 'SHA-256 hash of content for deduplication'
|
|
9
|
-
|
|
10
|
-
# Unique index to prevent duplicate content
|
|
11
|
-
add_index :nodes, :content_hash, unique: true, name: 'idx_nodes_content_hash_unique'
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class CreateRobotNodes < ActiveRecord::Migration[7.1]
|
|
4
|
-
def change
|
|
5
|
-
unless table_exists?(:robot_nodes)
|
|
6
|
-
create_table :robot_nodes, comment: 'Join table connecting robots to nodes (many-to-many)' do |t|
|
|
7
|
-
t.bigint :robot_id, null: false, comment: 'ID of the robot that remembered this node'
|
|
8
|
-
t.bigint :node_id, null: false, comment: 'ID of the node being remembered'
|
|
9
|
-
t.timestamptz :first_remembered_at, default: -> { 'CURRENT_TIMESTAMP' },
|
|
10
|
-
comment: 'When this robot first remembered this content'
|
|
11
|
-
t.timestamptz :last_remembered_at, default: -> { 'CURRENT_TIMESTAMP' },
|
|
12
|
-
comment: 'When this robot last tried to remember this content'
|
|
13
|
-
t.integer :remember_count, default: 1, null: false,
|
|
14
|
-
comment: 'Number of times this robot has tried to remember this content'
|
|
15
|
-
t.timestamptz :created_at, default: -> { 'CURRENT_TIMESTAMP' }
|
|
16
|
-
t.timestamptz :updated_at, default: -> { 'CURRENT_TIMESTAMP' }
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# Unique constraint: each robot can only link to a node once
|
|
20
|
-
add_index :robot_nodes, [:robot_id, :node_id], unique: true, name: 'idx_robot_nodes_unique'
|
|
21
|
-
add_index :robot_nodes, :robot_id, name: 'idx_robot_nodes_robot_id'
|
|
22
|
-
add_index :robot_nodes, :node_id, name: 'idx_robot_nodes_node_id'
|
|
23
|
-
add_index :robot_nodes, :last_remembered_at, name: 'idx_robot_nodes_last_remembered_at'
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Add foreign keys
|
|
27
|
-
unless foreign_key_exists?(:robot_nodes, :robots, column: :robot_id)
|
|
28
|
-
add_foreign_key :robot_nodes, :robots, column: :robot_id, primary_key: :id, on_delete: :cascade
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
unless foreign_key_exists?(:robot_nodes, :nodes, column: :node_id)
|
|
32
|
-
add_foreign_key :robot_nodes, :nodes, column: :node_id, primary_key: :id, on_delete: :cascade
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class RemoveSourceAndRobotIdFromNodes < ActiveRecord::Migration[7.1]
|
|
4
|
-
def change
|
|
5
|
-
# Remove foreign key constraint first
|
|
6
|
-
if foreign_key_exists?(:nodes, :robots, column: :robot_id)
|
|
7
|
-
remove_foreign_key :nodes, :robots, column: :robot_id
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
# Remove indexes
|
|
11
|
-
if index_exists?(:nodes, :robot_id, name: 'idx_nodes_robot_id')
|
|
12
|
-
remove_index :nodes, name: 'idx_nodes_robot_id'
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
if index_exists?(:nodes, :source, name: 'idx_nodes_source')
|
|
16
|
-
remove_index :nodes, name: 'idx_nodes_source'
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# Remove columns
|
|
20
|
-
if column_exists?(:nodes, :robot_id)
|
|
21
|
-
remove_column :nodes, :robot_id
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
if column_exists?(:nodes, :source)
|
|
25
|
-
remove_column :nodes, :source
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class CreateWorkingMemories < ActiveRecord::Migration[7.1]
|
|
4
|
-
def change
|
|
5
|
-
create_table :working_memories, comment: 'Per-robot working memory state (optional persistence)' do |t|
|
|
6
|
-
t.bigint :robot_id, null: false, comment: 'Robot whose working memory this belongs to'
|
|
7
|
-
t.bigint :node_id, null: false, comment: 'Node currently in working memory'
|
|
8
|
-
t.timestamptz :added_at, default: -> { 'CURRENT_TIMESTAMP' }, comment: 'When node was added to working memory'
|
|
9
|
-
t.integer :token_count, comment: 'Cached token count for budget tracking'
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
add_index :working_memories, :robot_id, name: 'idx_working_memories_robot_id'
|
|
13
|
-
add_index :working_memories, :node_id, name: 'idx_working_memories_node_id'
|
|
14
|
-
add_index :working_memories, [:robot_id, :node_id], unique: true, name: 'idx_working_memories_unique'
|
|
15
|
-
|
|
16
|
-
add_foreign_key :working_memories, :robots, on_delete: :cascade
|
|
17
|
-
add_foreign_key :working_memories, :nodes, on_delete: :cascade
|
|
18
|
-
end
|
|
19
|
-
end
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class RemoveUnusedColumns < ActiveRecord::Migration[7.1]
|
|
4
|
-
def change
|
|
5
|
-
# Remove in_working_memory from nodes - now tracked per-robot in working_memories table
|
|
6
|
-
remove_index :nodes, name: 'idx_nodes_in_working_memory', if_exists: true
|
|
7
|
-
remove_column :nodes, :in_working_memory, :boolean, default: false
|
|
8
|
-
|
|
9
|
-
# Remove unused metadata column from robots
|
|
10
|
-
remove_column :robots, :metadata, :jsonb
|
|
11
|
-
end
|
|
12
|
-
end
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# public.working_memories
|
|
2
|
-
|
|
3
|
-
## Description
|
|
4
|
-
|
|
5
|
-
Per-robot working memory state (optional persistence)
|
|
6
|
-
|
|
7
|
-
## Columns
|
|
8
|
-
|
|
9
|
-
| Name | Type | Default | Nullable | Children | Parents | Comment |
|
|
10
|
-
| ---- | ---- | ------- | -------- | -------- | ------- | ------- |
|
|
11
|
-
| id | bigint | nextval('working_memories_id_seq'::regclass) | false | | | |
|
|
12
|
-
| robot_id | bigint | | false | | [public.robots](public.robots.md) | Robot whose working memory this belongs to |
|
|
13
|
-
| node_id | bigint | | false | | [public.nodes](public.nodes.md) | Node currently in working memory |
|
|
14
|
-
| added_at | timestamp with time zone | CURRENT_TIMESTAMP | true | | | When node was added to working memory |
|
|
15
|
-
| token_count | integer | | true | | | Cached token count for budget tracking |
|
|
16
|
-
|
|
17
|
-
## Constraints
|
|
18
|
-
|
|
19
|
-
| Name | Type | Definition |
|
|
20
|
-
| ---- | ---- | ---------- |
|
|
21
|
-
| fk_rails_4b7c3eb07b | FOREIGN KEY | FOREIGN KEY (robot_id) REFERENCES robots(id) ON DELETE CASCADE |
|
|
22
|
-
| fk_rails_2c1d8b383c | FOREIGN KEY | FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE CASCADE |
|
|
23
|
-
| working_memories_pkey | PRIMARY KEY | PRIMARY KEY (id) |
|
|
24
|
-
|
|
25
|
-
## Indexes
|
|
26
|
-
|
|
27
|
-
| Name | Definition |
|
|
28
|
-
| ---- | ---------- |
|
|
29
|
-
| working_memories_pkey | CREATE UNIQUE INDEX working_memories_pkey ON public.working_memories USING btree (id) |
|
|
30
|
-
| idx_working_memories_robot_id | CREATE INDEX idx_working_memories_robot_id ON public.working_memories USING btree (robot_id) |
|
|
31
|
-
| idx_working_memories_node_id | CREATE INDEX idx_working_memories_node_id ON public.working_memories USING btree (node_id) |
|
|
32
|
-
| idx_working_memories_unique | CREATE UNIQUE INDEX idx_working_memories_unique ON public.working_memories USING btree (robot_id, node_id) |
|
|
33
|
-
|
|
34
|
-
## Relations
|
|
35
|
-
|
|
36
|
-

|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
> Generated by [tbls](https://github.com/k1LoW/tbls)
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
-
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
|
3
|
-
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
4
|
-
<!-- Generated by graphviz version 12.1.2 (20240928.0832)
|
|
5
|
-
-->
|
|
6
|
-
<!-- Title: public.working_memories Pages: 1 -->
|
|
7
|
-
<svg width="1046pt" height="756pt"
|
|
8
|
-
viewBox="0.00 0.00 1046.14 756.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
9
|
-
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 752)">
|
|
10
|
-
<title>public.working_memories</title>
|
|
11
|
-
<polygon fill="white" stroke="none" points="-4,4 -4,-752 1042.14,-752 1042.14,4 -4,4"/>
|
|
12
|
-
<!-- public.working_memories -->
|
|
13
|
-
<g id="node1" class="node">
|
|
14
|
-
<title>public.working_memories</title>
|
|
15
|
-
<polygon fill="#efefef" stroke="none" points="355.64,-666.2 355.64,-701.8 683.59,-701.8 683.59,-666.2 355.64,-666.2"/>
|
|
16
|
-
<polygon fill="none" stroke="black" points="355.64,-666.2 355.64,-701.8 683.59,-701.8 683.59,-666.2 355.64,-666.2"/>
|
|
17
|
-
<text text-anchor="start" x="362.64" y="-679.6" font-family="Arial Bold" font-size="18.00">public.working_memories</text>
|
|
18
|
-
<text text-anchor="start" x="552.11" y="-679.6" font-family="Arial" font-size="14.00"> </text>
|
|
19
|
-
<text text-anchor="start" x="583.22" y="-679.6" font-family="Arial" font-size="14.00" fill="#666666">[BASE TABLE]</text>
|
|
20
|
-
<polygon fill="none" stroke="black" points="355.64,-635.4 355.64,-666.2 683.59,-666.2 683.59,-635.4 355.64,-635.4"/>
|
|
21
|
-
<text text-anchor="start" x="362.64" y="-647.6" font-family="Arial" font-size="14.00">id </text>
|
|
22
|
-
<text text-anchor="start" x="377.43" y="-647.6" font-family="Arial" font-size="14.00" fill="#666666">[bigint]</text>
|
|
23
|
-
<polygon fill="none" stroke="black" points="355.64,-604.6 355.64,-635.4 683.59,-635.4 683.59,-604.6 355.64,-604.6"/>
|
|
24
|
-
<text text-anchor="start" x="362.64" y="-616.8" font-family="Arial" font-size="14.00">robot_id </text>
|
|
25
|
-
<text text-anchor="start" x="417.12" y="-616.8" font-family="Arial" font-size="14.00" fill="#666666">[bigint]</text>
|
|
26
|
-
<polygon fill="none" stroke="black" points="355.64,-573.8 355.64,-604.6 683.59,-604.6 683.59,-573.8 355.64,-573.8"/>
|
|
27
|
-
<text text-anchor="start" x="362.64" y="-586" font-family="Arial" font-size="14.00">node_id </text>
|
|
28
|
-
<text text-anchor="start" x="416.36" y="-586" font-family="Arial" font-size="14.00" fill="#666666">[bigint]</text>
|
|
29
|
-
<polygon fill="none" stroke="black" points="355.64,-543 355.64,-573.8 683.59,-573.8 683.59,-543 355.64,-543"/>
|
|
30
|
-
<text text-anchor="start" x="362.64" y="-555.2" font-family="Arial" font-size="14.00">added_at </text>
|
|
31
|
-
<text text-anchor="start" x="424.92" y="-555.2" font-family="Arial" font-size="14.00" fill="#666666">[timestamp with time zone]</text>
|
|
32
|
-
<polygon fill="none" stroke="black" points="355.64,-512.2 355.64,-543 683.59,-543 683.59,-512.2 355.64,-512.2"/>
|
|
33
|
-
<text text-anchor="start" x="362.64" y="-524.4" font-family="Arial" font-size="14.00">token_count </text>
|
|
34
|
-
<text text-anchor="start" x="442.81" y="-524.4" font-family="Arial" font-size="14.00" fill="#666666">[integer]</text>
|
|
35
|
-
<polygon fill="none" stroke="black" stroke-width="3" points="354.14,-510.7 354.14,-703.3 685.09,-703.3 685.09,-510.7 354.14,-510.7"/>
|
|
36
|
-
</g>
|
|
37
|
-
<!-- public.robots -->
|
|
38
|
-
<g id="node2" class="node">
|
|
39
|
-
<title>public.robots</title>
|
|
40
|
-
<polygon fill="#efefef" stroke="none" points="212.73,-258.8 212.73,-294.4 462.5,-294.4 462.5,-258.8 212.73,-258.8"/>
|
|
41
|
-
<polygon fill="none" stroke="black" points="212.73,-258.8 212.73,-294.4 462.5,-294.4 462.5,-258.8 212.73,-258.8"/>
|
|
42
|
-
<text text-anchor="start" x="228.13" y="-272.2" font-family="Arial Bold" font-size="18.00">public.robots</text>
|
|
43
|
-
<text text-anchor="start" x="322.62" y="-272.2" font-family="Arial" font-size="14.00"> </text>
|
|
44
|
-
<text text-anchor="start" x="353.74" y="-272.2" font-family="Arial" font-size="14.00" fill="#666666">[BASE TABLE]</text>
|
|
45
|
-
<polygon fill="none" stroke="black" points="212.73,-228 212.73,-258.8 462.5,-258.8 462.5,-228 212.73,-228"/>
|
|
46
|
-
<text text-anchor="start" x="219.73" y="-240.2" font-family="Arial" font-size="14.00">id </text>
|
|
47
|
-
<text text-anchor="start" x="234.52" y="-240.2" font-family="Arial" font-size="14.00" fill="#666666">[bigint]</text>
|
|
48
|
-
<polygon fill="none" stroke="black" points="212.73,-197.2 212.73,-228 462.5,-228 462.5,-197.2 212.73,-197.2"/>
|
|
49
|
-
<text text-anchor="start" x="219.73" y="-209.4" font-family="Arial" font-size="14.00">name </text>
|
|
50
|
-
<text text-anchor="start" x="258.64" y="-209.4" font-family="Arial" font-size="14.00" fill="#666666">[text]</text>
|
|
51
|
-
<polygon fill="none" stroke="black" points="212.73,-166.4 212.73,-197.2 462.5,-197.2 462.5,-166.4 212.73,-166.4"/>
|
|
52
|
-
<text text-anchor="start" x="219.73" y="-178.6" font-family="Arial" font-size="14.00">created_at </text>
|
|
53
|
-
<text text-anchor="start" x="289.78" y="-178.6" font-family="Arial" font-size="14.00" fill="#666666">[timestamp with time zone]</text>
|
|
54
|
-
<polygon fill="none" stroke="black" points="212.73,-135.6 212.73,-166.4 462.5,-166.4 462.5,-135.6 212.73,-135.6"/>
|
|
55
|
-
<text text-anchor="start" x="219.73" y="-147.8" font-family="Arial" font-size="14.00">last_active </text>
|
|
56
|
-
<text text-anchor="start" x="289.77" y="-147.8" font-family="Arial" font-size="14.00" fill="#666666">[timestamp with time zone]</text>
|
|
57
|
-
</g>
|
|
58
|
-
<!-- public.working_memories->public.robots -->
|
|
59
|
-
<g id="edge1" class="edge">
|
|
60
|
-
<title>public.working_memories:robot_id->public.robots:id</title>
|
|
61
|
-
<path fill="none" stroke="black" d="M344.41,-619.21C291.15,-610.26 333.43,-526.77 355.64,-466 381.12,-396.26 436.88,-407.28 462.5,-337.6 476.95,-298.3 505.37,-243.4 463.5,-243.4"/>
|
|
62
|
-
<polygon fill="black" stroke="black" points="344.34,-619.21 353.96,-624.46 349.65,-619.62 353.97,-619.95 353.97,-619.95 353.97,-619.95 349.65,-619.62 354.65,-615.49 344.34,-619.21"/>
|
|
63
|
-
<text text-anchor="start" x="7" y="-630" font-family="Arial" font-size="10.00">FOREIGN KEY (robot_id) REFERENCES robots(id) ON DELETE CASCADE</text>
|
|
64
|
-
</g>
|
|
65
|
-
<!-- public.nodes -->
|
|
66
|
-
<g id="node3" class="node">
|
|
67
|
-
<title>public.nodes</title>
|
|
68
|
-
<polygon fill="#efefef" stroke="none" points="566.45,-351.2 566.45,-386.8 838.78,-386.8 838.78,-351.2 566.45,-351.2"/>
|
|
69
|
-
<polygon fill="none" stroke="black" points="566.45,-351.2 566.45,-386.8 838.78,-386.8 838.78,-351.2 566.45,-351.2"/>
|
|
70
|
-
<text text-anchor="start" x="594.63" y="-364.6" font-family="Arial Bold" font-size="18.00">public.nodes</text>
|
|
71
|
-
<text text-anchor="start" x="686.12" y="-364.6" font-family="Arial" font-size="14.00"> </text>
|
|
72
|
-
<text text-anchor="start" x="717.23" y="-364.6" font-family="Arial" font-size="14.00" fill="#666666">[BASE TABLE]</text>
|
|
73
|
-
<polygon fill="none" stroke="black" points="566.45,-320.4 566.45,-351.2 838.78,-351.2 838.78,-320.4 566.45,-320.4"/>
|
|
74
|
-
<text text-anchor="start" x="573.45" y="-332.6" font-family="Arial" font-size="14.00">id </text>
|
|
75
|
-
<text text-anchor="start" x="588.24" y="-332.6" font-family="Arial" font-size="14.00" fill="#666666">[bigint]</text>
|
|
76
|
-
<polygon fill="none" stroke="black" points="566.45,-289.6 566.45,-320.4 838.78,-320.4 838.78,-289.6 566.45,-289.6"/>
|
|
77
|
-
<text text-anchor="start" x="573.45" y="-301.8" font-family="Arial" font-size="14.00">content </text>
|
|
78
|
-
<text text-anchor="start" x="623.27" y="-301.8" font-family="Arial" font-size="14.00" fill="#666666">[text]</text>
|
|
79
|
-
<polygon fill="none" stroke="black" points="566.45,-258.8 566.45,-289.6 838.78,-289.6 838.78,-258.8 566.45,-258.8"/>
|
|
80
|
-
<text text-anchor="start" x="573.45" y="-271" font-family="Arial" font-size="14.00">access_count </text>
|
|
81
|
-
<text text-anchor="start" x="662.95" y="-271" font-family="Arial" font-size="14.00" fill="#666666">[integer]</text>
|
|
82
|
-
<polygon fill="none" stroke="black" points="566.45,-228 566.45,-258.8 838.78,-258.8 838.78,-228 566.45,-228"/>
|
|
83
|
-
<text text-anchor="start" x="573.45" y="-240.2" font-family="Arial" font-size="14.00">created_at </text>
|
|
84
|
-
<text text-anchor="start" x="643.5" y="-240.2" font-family="Arial" font-size="14.00" fill="#666666">[timestamp with time zone]</text>
|
|
85
|
-
<polygon fill="none" stroke="black" points="566.45,-197.2 566.45,-228 838.78,-228 838.78,-197.2 566.45,-197.2"/>
|
|
86
|
-
<text text-anchor="start" x="573.45" y="-209.4" font-family="Arial" font-size="14.00">updated_at </text>
|
|
87
|
-
<text text-anchor="start" x="647.41" y="-209.4" font-family="Arial" font-size="14.00" fill="#666666">[timestamp with time zone]</text>
|
|
88
|
-
<polygon fill="none" stroke="black" points="566.45,-166.4 566.45,-197.2 838.78,-197.2 838.78,-166.4 566.45,-166.4"/>
|
|
89
|
-
<text text-anchor="start" x="573.45" y="-178.6" font-family="Arial" font-size="14.00">last_accessed </text>
|
|
90
|
-
<text text-anchor="start" x="666.06" y="-178.6" font-family="Arial" font-size="14.00" fill="#666666">[timestamp with time zone]</text>
|
|
91
|
-
<polygon fill="none" stroke="black" points="566.45,-135.6 566.45,-166.4 838.78,-166.4 838.78,-135.6 566.45,-135.6"/>
|
|
92
|
-
<text text-anchor="start" x="573.45" y="-147.8" font-family="Arial" font-size="14.00">token_count </text>
|
|
93
|
-
<text text-anchor="start" x="653.62" y="-147.8" font-family="Arial" font-size="14.00" fill="#666666">[integer]</text>
|
|
94
|
-
<polygon fill="none" stroke="black" points="566.45,-104.8 566.45,-135.6 838.78,-135.6 838.78,-104.8 566.45,-104.8"/>
|
|
95
|
-
<text text-anchor="start" x="573.45" y="-117" font-family="Arial" font-size="14.00">embedding </text>
|
|
96
|
-
<text text-anchor="start" x="646.62" y="-117" font-family="Arial" font-size="14.00" fill="#666666">[vector(2000)]</text>
|
|
97
|
-
<polygon fill="none" stroke="black" points="566.45,-74 566.45,-104.8 838.78,-104.8 838.78,-74 566.45,-74"/>
|
|
98
|
-
<text text-anchor="start" x="573.45" y="-86.2" font-family="Arial" font-size="14.00">embedding_dimension </text>
|
|
99
|
-
<text text-anchor="start" x="718.22" y="-86.2" font-family="Arial" font-size="14.00" fill="#666666">[integer]</text>
|
|
100
|
-
<polygon fill="none" stroke="black" points="566.45,-43.2 566.45,-74 838.78,-74 838.78,-43.2 566.45,-43.2"/>
|
|
101
|
-
<text text-anchor="start" x="573.45" y="-55.4" font-family="Arial" font-size="14.00">content_hash </text>
|
|
102
|
-
<text text-anchor="start" x="661.41" y="-55.4" font-family="Arial" font-size="14.00" fill="#666666">[varchar(64)]</text>
|
|
103
|
-
</g>
|
|
104
|
-
<!-- public.working_memories->public.nodes -->
|
|
105
|
-
<g id="edge2" class="edge">
|
|
106
|
-
<title>public.working_memories:node_id->public.nodes:id</title>
|
|
107
|
-
<path fill="none" stroke="black" d="M694.76,-588.13C736.16,-578.52 714.08,-506.91 683.59,-466 651.05,-422.32 599.05,-473.63 566.45,-430 541.39,-396.46 523.58,-335.8 565.45,-335.8"/>
|
|
108
|
-
<polygon fill="black" stroke="black" points="694.87,-588.11 684.45,-584.69 689.57,-588.67 685.26,-589.13 685.26,-589.13 685.26,-589.13 689.57,-588.67 685.4,-593.64 694.87,-588.11"/>
|
|
109
|
-
<text text-anchor="start" x="691.59" y="-599.2" font-family="Arial" font-size="10.00">FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE CASCADE</text>
|
|
110
|
-
</g>
|
|
111
|
-
</g>
|
|
112
|
-
</svg>
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class HTM
|
|
4
|
-
module Models
|
|
5
|
-
# WorkingMemoryEntry - Tracks which nodes are in each robot's working memory
|
|
6
|
-
#
|
|
7
|
-
# This provides optional database persistence for working memory state.
|
|
8
|
-
# Useful for:
|
|
9
|
-
# - Restoring working memory after process restart
|
|
10
|
-
# - Querying what nodes robots are actively working with
|
|
11
|
-
# - Debugging/monitoring working memory across robots
|
|
12
|
-
#
|
|
13
|
-
# Note: The in-memory WorkingMemory class remains the primary interface.
|
|
14
|
-
# This table is for persistence/observability, not required for operation.
|
|
15
|
-
#
|
|
16
|
-
class WorkingMemoryEntry < ActiveRecord::Base
|
|
17
|
-
self.table_name = 'working_memories'
|
|
18
|
-
|
|
19
|
-
belongs_to :robot, class_name: 'HTM::Models::Robot'
|
|
20
|
-
belongs_to :node, class_name: 'HTM::Models::Node'
|
|
21
|
-
|
|
22
|
-
validates :robot_id, presence: true
|
|
23
|
-
validates :node_id, presence: true
|
|
24
|
-
validates :robot_id, uniqueness: { scope: :node_id, message: 'already has this node in working memory' }
|
|
25
|
-
|
|
26
|
-
# Scopes
|
|
27
|
-
scope :by_robot, ->(robot_id) { where(robot_id: robot_id) }
|
|
28
|
-
scope :recent, -> { order(added_at: :desc) }
|
|
29
|
-
|
|
30
|
-
# Class methods
|
|
31
|
-
|
|
32
|
-
# Sync working memory state from in-memory hash to database
|
|
33
|
-
#
|
|
34
|
-
# @param robot_id [Integer] Robot ID
|
|
35
|
-
# @param node_entries [Hash] Hash of node_id => { token_count:, ... }
|
|
36
|
-
#
|
|
37
|
-
def self.sync_from_memory(robot_id, node_entries)
|
|
38
|
-
transaction do
|
|
39
|
-
# Clear existing entries for this robot
|
|
40
|
-
where(robot_id: robot_id).delete_all
|
|
41
|
-
|
|
42
|
-
# Insert current entries
|
|
43
|
-
node_entries.each do |node_id, data|
|
|
44
|
-
create!(
|
|
45
|
-
robot_id: robot_id,
|
|
46
|
-
node_id: node_id,
|
|
47
|
-
token_count: data[:token_count],
|
|
48
|
-
added_at: data[:added_at] || Time.current
|
|
49
|
-
)
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Load working memory state from database
|
|
55
|
-
#
|
|
56
|
-
# @param robot_id [Integer] Robot ID
|
|
57
|
-
# @return [Hash] Hash suitable for WorkingMemory restoration
|
|
58
|
-
#
|
|
59
|
-
def self.load_for_robot(robot_id)
|
|
60
|
-
by_robot(robot_id).includes(:node).each_with_object({}) do |entry, hash|
|
|
61
|
-
hash[entry.node_id] = {
|
|
62
|
-
content: entry.node.content,
|
|
63
|
-
token_count: entry.token_count,
|
|
64
|
-
added_at: entry.added_at,
|
|
65
|
-
access_count: entry.node.access_count
|
|
66
|
-
}
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# Clear working memory for a robot
|
|
71
|
-
#
|
|
72
|
-
# @param robot_id [Integer] Robot ID
|
|
73
|
-
#
|
|
74
|
-
def self.clear_for_robot(robot_id)
|
|
75
|
-
where(robot_id: robot_id).delete_all
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# Get total token count for a robot's working memory
|
|
79
|
-
#
|
|
80
|
-
# @param robot_id [Integer] Robot ID
|
|
81
|
-
# @return [Integer] Total tokens
|
|
82
|
-
#
|
|
83
|
-
def self.total_tokens_for_robot(robot_id)
|
|
84
|
-
where(robot_id: robot_id).sum(:token_count)
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|