From cae1ae36d6e39513a967e44649486c9235d2570f Mon Sep 17 00:00:00 2001 From: Rodribm10 Date: Sat, 18 Apr 2026 23:38:23 -0300 Subject: [PATCH] feat(captain-memory): create captain_contact_memories table with pgvector index --- ...9023642_create_captain_contact_memories.rb | 64 +++++++++++++++++++ db/schema.rb | 34 +++++++++- 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20260419023642_create_captain_contact_memories.rb diff --git a/db/migrate/20260419023642_create_captain_contact_memories.rb b/db/migrate/20260419023642_create_captain_contact_memories.rb new file mode 100644 index 000000000..6027601d8 --- /dev/null +++ b/db/migrate/20260419023642_create_captain_contact_memories.rb @@ -0,0 +1,64 @@ +class CreateCaptainContactMemories < ActiveRecord::Migration[7.1] + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def change + create_table :captain_contact_memories do |t| + t.references :account, null: false, foreign_key: true + t.references :contact, null: false, foreign_key: true + t.string :memory_type, null: false + t.text :content, null: false + t.text :evidence, null: false + t.float :confidence, null: false + t.string :scope, null: false, default: 'global' + t.vector :embedding, limit: 1536 + t.bigint :source_conversation_id + t.bigint :source_unit_id + t.bigint :source_inbox_id + t.datetime :expires_at + t.datetime :last_verified_at, null: false + t.datetime :superseded_at + t.bigint :superseded_by_id + t.datetime :deleted_at + t.jsonb :metadata, null: false, default: {} + t.timestamps + end + + add_index :captain_contact_memories, + [:account_id, :contact_id], + where: 'deleted_at IS NULL AND superseded_at IS NULL', + name: 'idx_ccm_recall' + + add_index :captain_contact_memories, + [:source_unit_id, :memory_type, :created_at], + name: 'idx_ccm_analytics' + + add_index :captain_contact_memories, + :deleted_at, + where: 'deleted_at IS NOT NULL', + name: 'idx_ccm_hard_delete' + + add_index :captain_contact_memories, + :superseded_by_id, + where: 'superseded_at IS NOT NULL', + name: 'idx_ccm_superseded' + + add_index :captain_contact_memories, + :source_conversation_id, + name: 'idx_ccm_source_conversation' + + reversible do |dir| + dir.up do + execute <<~SQL.squish + CREATE INDEX idx_ccm_embedding + ON captain_contact_memories + USING ivfflat (embedding vector_cosine_ops) + WITH (lists = 100); + SQL + end + + dir.down do + execute 'DROP INDEX IF EXISTS idx_ccm_embedding;' + end + end + end + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength +end diff --git a/db/schema.rb b/db/schema.rb index aa5354c80..4f1e74873 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2026_04_15_040957) do +ActiveRecord::Schema[7.1].define(version: 2026_04_19_023642) do # These extensions should be enabled to support this database enable_extension "pg_stat_statements" enable_extension "pg_trgm" @@ -367,6 +367,36 @@ ActiveRecord::Schema[7.1].define(version: 2026_04_15_040957) do t.index ["account_id"], name: "index_captain_configurations_on_account_id" end + create_table "captain_contact_memories", force: :cascade do |t| + t.bigint "account_id", null: false + t.bigint "contact_id", null: false + t.string "memory_type", null: false + t.text "content", null: false + t.text "evidence", null: false + t.float "confidence", null: false + t.string "scope", default: "global", null: false + t.vector "embedding", limit: 1536 + t.bigint "source_conversation_id" + t.bigint "source_unit_id" + t.bigint "source_inbox_id" + t.datetime "expires_at" + t.datetime "last_verified_at", null: false + t.datetime "superseded_at" + t.bigint "superseded_by_id" + t.datetime "deleted_at" + t.jsonb "metadata", default: {}, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["account_id", "contact_id"], name: "idx_ccm_recall", where: "((deleted_at IS NULL) AND (superseded_at IS NULL))" + t.index ["account_id"], name: "index_captain_contact_memories_on_account_id" + t.index ["contact_id"], name: "index_captain_contact_memories_on_contact_id" + t.index ["deleted_at"], name: "idx_ccm_hard_delete", where: "(deleted_at IS NOT NULL)" + t.index ["embedding"], name: "idx_ccm_embedding", opclass: :vector_cosine_ops, using: :ivfflat + t.index ["source_conversation_id"], name: "idx_ccm_source_conversation" + t.index ["source_unit_id", "memory_type", "created_at"], name: "idx_ccm_analytics" + t.index ["superseded_by_id"], name: "idx_ccm_superseded", where: "(superseded_at IS NOT NULL)" + end + create_table "captain_conversation_insights", force: :cascade do |t| t.bigint "account_id", null: false t.bigint "captain_unit_id" @@ -2074,6 +2104,8 @@ ActiveRecord::Schema[7.1].define(version: 2026_04_15_040957) do add_foreign_key "captain_assets", "captain_suites" add_foreign_key "captain_brands", "accounts" add_foreign_key "captain_configurations", "accounts" + add_foreign_key "captain_contact_memories", "accounts" + add_foreign_key "captain_contact_memories", "contacts" add_foreign_key "captain_conversation_insights", "accounts" add_foreign_key "captain_conversation_insights", "captain_units" add_foreign_key "captain_conversation_insights", "inboxes", name: "fk_rails_inbox_id"