remdb 0.2.6__py3-none-any.whl → 0.3.103__py3-none-any.whl

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.

Potentially problematic release.


This version of remdb might be problematic. Click here for more details.

Files changed (82) hide show
  1. rem/__init__.py +129 -2
  2. rem/agentic/README.md +76 -0
  3. rem/agentic/__init__.py +15 -0
  4. rem/agentic/agents/__init__.py +16 -2
  5. rem/agentic/agents/sse_simulator.py +500 -0
  6. rem/agentic/context.py +7 -5
  7. rem/agentic/llm_provider_models.py +301 -0
  8. rem/agentic/providers/phoenix.py +32 -43
  9. rem/agentic/providers/pydantic_ai.py +84 -10
  10. rem/api/README.md +238 -1
  11. rem/api/deps.py +255 -0
  12. rem/api/main.py +70 -22
  13. rem/api/mcp_router/server.py +8 -1
  14. rem/api/mcp_router/tools.py +80 -0
  15. rem/api/middleware/tracking.py +172 -0
  16. rem/api/routers/admin.py +277 -0
  17. rem/api/routers/auth.py +124 -0
  18. rem/api/routers/chat/completions.py +123 -14
  19. rem/api/routers/chat/models.py +7 -3
  20. rem/api/routers/chat/sse_events.py +526 -0
  21. rem/api/routers/chat/streaming.py +468 -45
  22. rem/api/routers/dev.py +81 -0
  23. rem/api/routers/feedback.py +455 -0
  24. rem/api/routers/messages.py +473 -0
  25. rem/api/routers/models.py +78 -0
  26. rem/api/routers/shared_sessions.py +406 -0
  27. rem/auth/middleware.py +126 -27
  28. rem/cli/commands/ask.py +15 -11
  29. rem/cli/commands/configure.py +169 -94
  30. rem/cli/commands/db.py +53 -7
  31. rem/cli/commands/experiments.py +278 -96
  32. rem/cli/commands/process.py +8 -7
  33. rem/cli/commands/scaffold.py +47 -0
  34. rem/cli/commands/schema.py +9 -9
  35. rem/cli/main.py +10 -0
  36. rem/config.py +2 -2
  37. rem/models/core/core_model.py +7 -1
  38. rem/models/entities/__init__.py +21 -0
  39. rem/models/entities/domain_resource.py +38 -0
  40. rem/models/entities/feedback.py +123 -0
  41. rem/models/entities/message.py +30 -1
  42. rem/models/entities/session.py +83 -0
  43. rem/models/entities/shared_session.py +206 -0
  44. rem/models/entities/user.py +10 -3
  45. rem/registry.py +367 -0
  46. rem/schemas/agents/rem.yaml +7 -3
  47. rem/services/content/providers.py +94 -140
  48. rem/services/content/service.py +85 -16
  49. rem/services/dreaming/affinity_service.py +2 -16
  50. rem/services/dreaming/moment_service.py +2 -15
  51. rem/services/embeddings/api.py +20 -13
  52. rem/services/phoenix/EXPERIMENT_DESIGN.md +3 -3
  53. rem/services/phoenix/client.py +252 -19
  54. rem/services/postgres/README.md +29 -10
  55. rem/services/postgres/repository.py +132 -0
  56. rem/services/postgres/schema_generator.py +86 -5
  57. rem/services/rate_limit.py +113 -0
  58. rem/services/rem/README.md +14 -0
  59. rem/services/session/compression.py +17 -1
  60. rem/services/user_service.py +98 -0
  61. rem/settings.py +115 -17
  62. rem/sql/background_indexes.sql +10 -0
  63. rem/sql/migrations/001_install.sql +152 -2
  64. rem/sql/migrations/002_install_models.sql +580 -231
  65. rem/sql/migrations/003_seed_default_user.sql +48 -0
  66. rem/utils/constants.py +97 -0
  67. rem/utils/date_utils.py +228 -0
  68. rem/utils/embeddings.py +17 -4
  69. rem/utils/files.py +167 -0
  70. rem/utils/mime_types.py +158 -0
  71. rem/utils/model_helpers.py +156 -1
  72. rem/utils/schema_loader.py +273 -14
  73. rem/utils/sql_types.py +3 -1
  74. rem/utils/vision.py +9 -14
  75. rem/workers/README.md +14 -14
  76. rem/workers/db_maintainer.py +74 -0
  77. {remdb-0.2.6.dist-info → remdb-0.3.103.dist-info}/METADATA +486 -132
  78. {remdb-0.2.6.dist-info → remdb-0.3.103.dist-info}/RECORD +80 -57
  79. {remdb-0.2.6.dist-info → remdb-0.3.103.dist-info}/WHEEL +1 -1
  80. rem/sql/002_install_models.sql +0 -1068
  81. rem/sql/install_models.sql +0 -1038
  82. {remdb-0.2.6.dist-info → remdb-0.3.103.dist-info}/entry_points.txt +0 -0
@@ -1,9 +1,9 @@
1
1
  -- REM Model Schema (install_models.sql)
2
2
  -- Generated from Pydantic models
3
- -- Source directory: src/rem/models/entities
4
- -- Generated at: 2025-11-21T22:20:29.773072
3
+ -- Source: directory: src/rem/models/entities
4
+ -- Generated at: 2025-11-28T08:13:28.661915
5
5
  --
6
- -- DO NOT EDIT MANUALLY - Regenerate with: rem schema generate
6
+ -- DO NOT EDIT MANUALLY - Regenerate with: rem db schema generate
7
7
  --
8
8
  -- This script creates:
9
9
  -- 1. Primary entity tables
@@ -19,11 +19,11 @@ DO $$
19
19
  BEGIN
20
20
  -- Check that install.sql has been run
21
21
  IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'kv_store') THEN
22
- RAISE EXCEPTION 'KV_STORE table not found. Run sql/install.sql first.';
22
+ RAISE EXCEPTION 'KV_STORE table not found. Run migrations/001_install.sql first.';
23
23
  END IF;
24
24
 
25
25
  IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector') THEN
26
- RAISE EXCEPTION 'pgvector extension not found. Run sql/install.sql first.';
26
+ RAISE EXCEPTION 'pgvector extension not found. Run migrations/001_install.sql first.';
27
27
  END IF;
28
28
 
29
29
  RAISE NOTICE 'Prerequisites check passed';
@@ -41,6 +41,7 @@ CREATE TABLE IF NOT EXISTS users (
41
41
  email VARCHAR(256),
42
42
  role VARCHAR(256),
43
43
  tier TEXT,
44
+ anonymous_ids TEXT[] DEFAULT ARRAY[]::TEXT[],
44
45
  sec_policy JSONB DEFAULT '{}'::jsonb,
45
46
  summary TEXT,
46
47
  interests TEXT[] DEFAULT ARRAY[]::TEXT[],
@@ -55,11 +56,11 @@ CREATE TABLE IF NOT EXISTS users (
55
56
  tags TEXT[] DEFAULT ARRAY[]::TEXT[]
56
57
  );
57
58
 
58
- CREATE INDEX IF NOT EXISTS idx_users_tenant ON users (tenant_id);
59
- CREATE INDEX IF NOT EXISTS idx_users_user ON users (user_id);
60
- CREATE INDEX IF NOT EXISTS idx_users_graph_edges ON users USING GIN (graph_edges);
61
- CREATE INDEX IF NOT EXISTS idx_users_metadata ON users USING GIN (metadata);
62
- CREATE INDEX IF NOT EXISTS idx_users_tags ON users USING GIN (tags);
59
+ CREATE INDEX idx_users_tenant ON users (tenant_id);
60
+ CREATE INDEX idx_users_user ON users (user_id);
61
+ CREATE INDEX idx_users_graph_edges ON users USING GIN (graph_edges);
62
+ CREATE INDEX idx_users_metadata ON users USING GIN (metadata);
63
+ CREATE INDEX idx_users_tags ON users USING GIN (tags);
63
64
 
64
65
  -- Embeddings for users
65
66
  CREATE TABLE IF NOT EXISTS embeddings_users (
@@ -77,14 +78,14 @@ CREATE TABLE IF NOT EXISTS embeddings_users (
77
78
  );
78
79
 
79
80
  -- Index for entity lookup (get all embeddings for entity)
80
- CREATE INDEX IF NOT EXISTS idx_embeddings_users_entity ON embeddings_users (entity_id);
81
+ CREATE INDEX idx_embeddings_users_entity ON embeddings_users (entity_id);
81
82
 
82
83
  -- Index for field + provider lookup
83
- CREATE INDEX IF NOT EXISTS idx_embeddings_users_field_provider ON embeddings_users (field_name, provider);
84
+ CREATE INDEX idx_embeddings_users_field_provider ON embeddings_users (field_name, provider);
84
85
 
85
86
  -- HNSW index for vector similarity search (created in background)
86
87
  -- Note: This will be created by background thread after data load
87
- -- CREATE INDEX IF NOT EXISTS idx_embeddings_users_vector_hnsw ON embeddings_users
88
+ -- CREATE INDEX idx_embeddings_users_vector_hnsw ON embeddings_users
88
89
  -- USING hnsw (embedding vector_cosine_ops);
89
90
 
90
91
  -- KV_STORE trigger for users
@@ -109,7 +110,7 @@ BEGIN
109
110
  graph_edges,
110
111
  updated_at
111
112
  ) VALUES (
112
- NEW.name, -- Use name as entity_key (json_schema_extra)
113
+ NEW.name::VARCHAR,
113
114
  'users',
114
115
  NEW.id,
115
116
  NEW.tenant_id,
@@ -145,7 +146,7 @@ CREATE TABLE IF NOT EXISTS image_resources (
145
146
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
146
147
  tenant_id VARCHAR(100) NOT NULL,
147
148
  user_id VARCHAR(256),
148
- name VARCHAR(256) NOT NULL,
149
+ name VARCHAR(256),
149
150
  uri VARCHAR(256),
150
151
  ordinal INTEGER,
151
152
  content TEXT,
@@ -168,11 +169,11 @@ CREATE TABLE IF NOT EXISTS image_resources (
168
169
  tags TEXT[] DEFAULT ARRAY[]::TEXT[]
169
170
  );
170
171
 
171
- CREATE INDEX IF NOT EXISTS idx_image_resources_tenant ON image_resources (tenant_id);
172
- CREATE INDEX IF NOT EXISTS idx_image_resources_user ON image_resources (user_id);
173
- CREATE INDEX IF NOT EXISTS idx_image_resources_graph_edges ON image_resources USING GIN (graph_edges);
174
- CREATE INDEX IF NOT EXISTS idx_image_resources_metadata ON image_resources USING GIN (metadata);
175
- CREATE INDEX IF NOT EXISTS idx_image_resources_tags ON image_resources USING GIN (tags);
172
+ CREATE INDEX idx_image_resources_tenant ON image_resources (tenant_id);
173
+ CREATE INDEX idx_image_resources_user ON image_resources (user_id);
174
+ CREATE INDEX idx_image_resources_graph_edges ON image_resources USING GIN (graph_edges);
175
+ CREATE INDEX idx_image_resources_metadata ON image_resources USING GIN (metadata);
176
+ CREATE INDEX idx_image_resources_tags ON image_resources USING GIN (tags);
176
177
 
177
178
  -- Embeddings for image_resources
178
179
  CREATE TABLE IF NOT EXISTS embeddings_image_resources (
@@ -190,14 +191,14 @@ CREATE TABLE IF NOT EXISTS embeddings_image_resources (
190
191
  );
191
192
 
192
193
  -- Index for entity lookup (get all embeddings for entity)
193
- CREATE INDEX IF NOT EXISTS idx_embeddings_image_resources_entity ON embeddings_image_resources (entity_id);
194
+ CREATE INDEX idx_embeddings_image_resources_entity ON embeddings_image_resources (entity_id);
194
195
 
195
196
  -- Index for field + provider lookup
196
- CREATE INDEX IF NOT EXISTS idx_embeddings_image_resources_field_provider ON embeddings_image_resources (field_name, provider);
197
+ CREATE INDEX idx_embeddings_image_resources_field_provider ON embeddings_image_resources (field_name, provider);
197
198
 
198
199
  -- HNSW index for vector similarity search (created in background)
199
200
  -- Note: This will be created by background thread after data load
200
- -- CREATE INDEX IF NOT EXISTS idx_embeddings_image_resources_vector_hnsw ON embeddings_image_resources
201
+ -- CREATE INDEX idx_embeddings_image_resources_vector_hnsw ON embeddings_image_resources
201
202
  -- USING hnsw (embedding vector_cosine_ops);
202
203
 
203
204
  -- KV_STORE trigger for image_resources
@@ -222,7 +223,7 @@ BEGIN
222
223
  graph_edges,
223
224
  updated_at
224
225
  ) VALUES (
225
- NEW.name, -- Use name as entity_key (json_schema_extra)
226
+ NEW.name::VARCHAR,
226
227
  'image_resources',
227
228
  NEW.id,
228
229
  NEW.tenant_id,
@@ -250,6 +251,88 @@ CREATE TRIGGER trg_image_resources_kv_store
250
251
  AFTER INSERT OR UPDATE OR DELETE ON image_resources
251
252
  FOR EACH ROW EXECUTE FUNCTION fn_image_resources_kv_store_upsert();
252
253
 
254
+ -- ======================================================================
255
+ -- FEEDBACKS (Model: Feedback)
256
+ -- ======================================================================
257
+
258
+ CREATE TABLE IF NOT EXISTS feedbacks (
259
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
260
+ tenant_id VARCHAR(100) NOT NULL,
261
+ user_id VARCHAR(256),
262
+ session_id VARCHAR(256) NOT NULL,
263
+ message_id VARCHAR(256),
264
+ rating INTEGER,
265
+ categories TEXT[] DEFAULT ARRAY[]::TEXT[],
266
+ comment TEXT,
267
+ trace_id VARCHAR(256),
268
+ span_id VARCHAR(256),
269
+ phoenix_synced BOOLEAN,
270
+ phoenix_annotation_id VARCHAR(256),
271
+ annotator_kind VARCHAR(256),
272
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
273
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
274
+ deleted_at TIMESTAMP,
275
+ graph_edges JSONB DEFAULT '[]'::jsonb,
276
+ metadata JSONB DEFAULT '{}'::jsonb,
277
+ tags TEXT[] DEFAULT ARRAY[]::TEXT[]
278
+ );
279
+
280
+ CREATE INDEX idx_feedbacks_tenant ON feedbacks (tenant_id);
281
+ CREATE INDEX idx_feedbacks_user ON feedbacks (user_id);
282
+ CREATE INDEX idx_feedbacks_graph_edges ON feedbacks USING GIN (graph_edges);
283
+ CREATE INDEX idx_feedbacks_metadata ON feedbacks USING GIN (metadata);
284
+ CREATE INDEX idx_feedbacks_tags ON feedbacks USING GIN (tags);
285
+
286
+ -- KV_STORE trigger for feedbacks
287
+ -- Trigger function to maintain KV_STORE for feedbacks
288
+ CREATE OR REPLACE FUNCTION fn_feedbacks_kv_store_upsert()
289
+ RETURNS TRIGGER AS $$
290
+ BEGIN
291
+ IF (TG_OP = 'DELETE') THEN
292
+ -- Remove from KV_STORE on delete
293
+ DELETE FROM kv_store
294
+ WHERE entity_id = OLD.id;
295
+ RETURN OLD;
296
+ ELSIF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
297
+ -- Upsert to KV_STORE (O(1) lookup by entity_key)
298
+ INSERT INTO kv_store (
299
+ entity_key,
300
+ entity_type,
301
+ entity_id,
302
+ tenant_id,
303
+ user_id,
304
+ metadata,
305
+ graph_edges,
306
+ updated_at
307
+ ) VALUES (
308
+ NEW.id::VARCHAR,
309
+ 'feedbacks',
310
+ NEW.id,
311
+ NEW.tenant_id,
312
+ NEW.user_id,
313
+ NEW.metadata,
314
+ COALESCE(NEW.graph_edges, '[]'::jsonb),
315
+ CURRENT_TIMESTAMP
316
+ )
317
+ ON CONFLICT (tenant_id, entity_key)
318
+ DO UPDATE SET
319
+ entity_id = EXCLUDED.entity_id,
320
+ user_id = EXCLUDED.user_id,
321
+ metadata = EXCLUDED.metadata,
322
+ graph_edges = EXCLUDED.graph_edges,
323
+ updated_at = CURRENT_TIMESTAMP;
324
+
325
+ RETURN NEW;
326
+ END IF;
327
+ END;
328
+ $$ LANGUAGE plpgsql;
329
+
330
+ -- Create trigger
331
+ DROP TRIGGER IF EXISTS trg_feedbacks_kv_store ON feedbacks;
332
+ CREATE TRIGGER trg_feedbacks_kv_store
333
+ AFTER INSERT OR UPDATE OR DELETE ON feedbacks
334
+ FOR EACH ROW EXECUTE FUNCTION fn_feedbacks_kv_store_upsert();
335
+
253
336
  -- ======================================================================
254
337
  -- MOMENTS (Model: Moment)
255
338
  -- ======================================================================
@@ -258,7 +341,7 @@ CREATE TABLE IF NOT EXISTS moments (
258
341
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
259
342
  tenant_id VARCHAR(100) NOT NULL,
260
343
  user_id VARCHAR(256),
261
- name VARCHAR(256) NOT NULL,
344
+ name VARCHAR(256),
262
345
  moment_type VARCHAR(256),
263
346
  category VARCHAR(256),
264
347
  starts_timestamp TIMESTAMP NOT NULL,
@@ -276,11 +359,11 @@ CREATE TABLE IF NOT EXISTS moments (
276
359
  tags TEXT[] DEFAULT ARRAY[]::TEXT[]
277
360
  );
278
361
 
279
- CREATE INDEX IF NOT EXISTS idx_moments_tenant ON moments (tenant_id);
280
- CREATE INDEX IF NOT EXISTS idx_moments_user ON moments (user_id);
281
- CREATE INDEX IF NOT EXISTS idx_moments_graph_edges ON moments USING GIN (graph_edges);
282
- CREATE INDEX IF NOT EXISTS idx_moments_metadata ON moments USING GIN (metadata);
283
- CREATE INDEX IF NOT EXISTS idx_moments_tags ON moments USING GIN (tags);
362
+ CREATE INDEX idx_moments_tenant ON moments (tenant_id);
363
+ CREATE INDEX idx_moments_user ON moments (user_id);
364
+ CREATE INDEX idx_moments_graph_edges ON moments USING GIN (graph_edges);
365
+ CREATE INDEX idx_moments_metadata ON moments USING GIN (metadata);
366
+ CREATE INDEX idx_moments_tags ON moments USING GIN (tags);
284
367
 
285
368
  -- Embeddings for moments
286
369
  CREATE TABLE IF NOT EXISTS embeddings_moments (
@@ -298,14 +381,14 @@ CREATE TABLE IF NOT EXISTS embeddings_moments (
298
381
  );
299
382
 
300
383
  -- Index for entity lookup (get all embeddings for entity)
301
- CREATE INDEX IF NOT EXISTS idx_embeddings_moments_entity ON embeddings_moments (entity_id);
384
+ CREATE INDEX idx_embeddings_moments_entity ON embeddings_moments (entity_id);
302
385
 
303
386
  -- Index for field + provider lookup
304
- CREATE INDEX IF NOT EXISTS idx_embeddings_moments_field_provider ON embeddings_moments (field_name, provider);
387
+ CREATE INDEX idx_embeddings_moments_field_provider ON embeddings_moments (field_name, provider);
305
388
 
306
389
  -- HNSW index for vector similarity search (created in background)
307
390
  -- Note: This will be created by background thread after data load
308
- -- CREATE INDEX IF NOT EXISTS idx_embeddings_moments_vector_hnsw ON embeddings_moments
391
+ -- CREATE INDEX idx_embeddings_moments_vector_hnsw ON embeddings_moments
309
392
  -- USING hnsw (embedding vector_cosine_ops);
310
393
 
311
394
  -- KV_STORE trigger for moments
@@ -330,7 +413,7 @@ BEGIN
330
413
  graph_edges,
331
414
  updated_at
332
415
  ) VALUES (
333
- NEW.name, -- Use name as entity_key (json_schema_extra)
416
+ NEW.name::VARCHAR,
334
417
  'moments',
335
418
  NEW.id,
336
419
  NEW.tenant_id,
@@ -376,11 +459,11 @@ CREATE TABLE IF NOT EXISTS persons (
376
459
  tags TEXT[] DEFAULT ARRAY[]::TEXT[]
377
460
  );
378
461
 
379
- CREATE INDEX IF NOT EXISTS idx_persons_tenant ON persons (tenant_id);
380
- CREATE INDEX IF NOT EXISTS idx_persons_user ON persons (user_id);
381
- CREATE INDEX IF NOT EXISTS idx_persons_graph_edges ON persons USING GIN (graph_edges);
382
- CREATE INDEX IF NOT EXISTS idx_persons_metadata ON persons USING GIN (metadata);
383
- CREATE INDEX IF NOT EXISTS idx_persons_tags ON persons USING GIN (tags);
462
+ CREATE INDEX idx_persons_tenant ON persons (tenant_id);
463
+ CREATE INDEX idx_persons_user ON persons (user_id);
464
+ CREATE INDEX idx_persons_graph_edges ON persons USING GIN (graph_edges);
465
+ CREATE INDEX idx_persons_metadata ON persons USING GIN (metadata);
466
+ CREATE INDEX idx_persons_tags ON persons USING GIN (tags);
384
467
 
385
468
  -- KV_STORE trigger for persons
386
469
  -- Trigger function to maintain KV_STORE for persons
@@ -404,7 +487,7 @@ BEGIN
404
487
  graph_edges,
405
488
  updated_at
406
489
  ) VALUES (
407
- NEW.name, -- Use name as entity_key (json_schema_extra)
490
+ NEW.id::VARCHAR,
408
491
  'persons',
409
492
  NEW.id,
410
493
  NEW.tenant_id,
@@ -432,6 +515,113 @@ CREATE TRIGGER trg_persons_kv_store
432
515
  AFTER INSERT OR UPDATE OR DELETE ON persons
433
516
  FOR EACH ROW EXECUTE FUNCTION fn_persons_kv_store_upsert();
434
517
 
518
+ -- ======================================================================
519
+ -- SESSIONS (Model: Session)
520
+ -- ======================================================================
521
+
522
+ CREATE TABLE IF NOT EXISTS sessions (
523
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
524
+ tenant_id VARCHAR(100) NOT NULL,
525
+ user_id VARCHAR(256),
526
+ name VARCHAR(256) NOT NULL,
527
+ mode TEXT,
528
+ description TEXT,
529
+ original_trace_id VARCHAR(256),
530
+ settings_overrides JSONB,
531
+ prompt TEXT,
532
+ agent_schema_uri VARCHAR(256),
533
+ message_count INTEGER,
534
+ total_tokens INTEGER,
535
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
536
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
537
+ deleted_at TIMESTAMP,
538
+ graph_edges JSONB DEFAULT '[]'::jsonb,
539
+ metadata JSONB DEFAULT '{}'::jsonb,
540
+ tags TEXT[] DEFAULT ARRAY[]::TEXT[]
541
+ );
542
+
543
+ CREATE INDEX idx_sessions_tenant ON sessions (tenant_id);
544
+ CREATE INDEX idx_sessions_user ON sessions (user_id);
545
+ CREATE INDEX idx_sessions_graph_edges ON sessions USING GIN (graph_edges);
546
+ CREATE INDEX idx_sessions_metadata ON sessions USING GIN (metadata);
547
+ CREATE INDEX idx_sessions_tags ON sessions USING GIN (tags);
548
+
549
+ -- Embeddings for sessions
550
+ CREATE TABLE IF NOT EXISTS embeddings_sessions (
551
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
552
+ entity_id UUID NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
553
+ field_name VARCHAR(100) NOT NULL,
554
+ provider VARCHAR(50) NOT NULL DEFAULT 'openai',
555
+ model VARCHAR(100) NOT NULL DEFAULT 'text-embedding-3-small',
556
+ embedding vector(1536) NOT NULL,
557
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
558
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
559
+
560
+ -- Unique: one embedding per entity per field per provider
561
+ UNIQUE (entity_id, field_name, provider)
562
+ );
563
+
564
+ -- Index for entity lookup (get all embeddings for entity)
565
+ CREATE INDEX idx_embeddings_sessions_entity ON embeddings_sessions (entity_id);
566
+
567
+ -- Index for field + provider lookup
568
+ CREATE INDEX idx_embeddings_sessions_field_provider ON embeddings_sessions (field_name, provider);
569
+
570
+ -- HNSW index for vector similarity search (created in background)
571
+ -- Note: This will be created by background thread after data load
572
+ -- CREATE INDEX idx_embeddings_sessions_vector_hnsw ON embeddings_sessions
573
+ -- USING hnsw (embedding vector_cosine_ops);
574
+
575
+ -- KV_STORE trigger for sessions
576
+ -- Trigger function to maintain KV_STORE for sessions
577
+ CREATE OR REPLACE FUNCTION fn_sessions_kv_store_upsert()
578
+ RETURNS TRIGGER AS $$
579
+ BEGIN
580
+ IF (TG_OP = 'DELETE') THEN
581
+ -- Remove from KV_STORE on delete
582
+ DELETE FROM kv_store
583
+ WHERE entity_id = OLD.id;
584
+ RETURN OLD;
585
+ ELSIF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
586
+ -- Upsert to KV_STORE (O(1) lookup by entity_key)
587
+ INSERT INTO kv_store (
588
+ entity_key,
589
+ entity_type,
590
+ entity_id,
591
+ tenant_id,
592
+ user_id,
593
+ metadata,
594
+ graph_edges,
595
+ updated_at
596
+ ) VALUES (
597
+ NEW.name::VARCHAR,
598
+ 'sessions',
599
+ NEW.id,
600
+ NEW.tenant_id,
601
+ NEW.user_id,
602
+ NEW.metadata,
603
+ COALESCE(NEW.graph_edges, '[]'::jsonb),
604
+ CURRENT_TIMESTAMP
605
+ )
606
+ ON CONFLICT (tenant_id, entity_key)
607
+ DO UPDATE SET
608
+ entity_id = EXCLUDED.entity_id,
609
+ user_id = EXCLUDED.user_id,
610
+ metadata = EXCLUDED.metadata,
611
+ graph_edges = EXCLUDED.graph_edges,
612
+ updated_at = CURRENT_TIMESTAMP;
613
+
614
+ RETURN NEW;
615
+ END IF;
616
+ END;
617
+ $$ LANGUAGE plpgsql;
618
+
619
+ -- Create trigger
620
+ DROP TRIGGER IF EXISTS trg_sessions_kv_store ON sessions;
621
+ CREATE TRIGGER trg_sessions_kv_store
622
+ AFTER INSERT OR UPDATE OR DELETE ON sessions
623
+ FOR EACH ROW EXECUTE FUNCTION fn_sessions_kv_store_upsert();
624
+
435
625
  -- ======================================================================
436
626
  -- RESOURCES (Model: Resource)
437
627
  -- ======================================================================
@@ -440,7 +630,7 @@ CREATE TABLE IF NOT EXISTS resources (
440
630
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
441
631
  tenant_id VARCHAR(100) NOT NULL,
442
632
  user_id VARCHAR(256),
443
- name VARCHAR(256) NOT NULL,
633
+ name VARCHAR(256),
444
634
  uri VARCHAR(256),
445
635
  ordinal INTEGER,
446
636
  content TEXT,
@@ -455,11 +645,11 @@ CREATE TABLE IF NOT EXISTS resources (
455
645
  tags TEXT[] DEFAULT ARRAY[]::TEXT[]
456
646
  );
457
647
 
458
- CREATE INDEX IF NOT EXISTS idx_resources_tenant ON resources (tenant_id);
459
- CREATE INDEX IF NOT EXISTS idx_resources_user ON resources (user_id);
460
- CREATE INDEX IF NOT EXISTS idx_resources_graph_edges ON resources USING GIN (graph_edges);
461
- CREATE INDEX IF NOT EXISTS idx_resources_metadata ON resources USING GIN (metadata);
462
- CREATE INDEX IF NOT EXISTS idx_resources_tags ON resources USING GIN (tags);
648
+ CREATE INDEX idx_resources_tenant ON resources (tenant_id);
649
+ CREATE INDEX idx_resources_user ON resources (user_id);
650
+ CREATE INDEX idx_resources_graph_edges ON resources USING GIN (graph_edges);
651
+ CREATE INDEX idx_resources_metadata ON resources USING GIN (metadata);
652
+ CREATE INDEX idx_resources_tags ON resources USING GIN (tags);
463
653
 
464
654
  -- Embeddings for resources
465
655
  CREATE TABLE IF NOT EXISTS embeddings_resources (
@@ -477,14 +667,14 @@ CREATE TABLE IF NOT EXISTS embeddings_resources (
477
667
  );
478
668
 
479
669
  -- Index for entity lookup (get all embeddings for entity)
480
- CREATE INDEX IF NOT EXISTS idx_embeddings_resources_entity ON embeddings_resources (entity_id);
670
+ CREATE INDEX idx_embeddings_resources_entity ON embeddings_resources (entity_id);
481
671
 
482
672
  -- Index for field + provider lookup
483
- CREATE INDEX IF NOT EXISTS idx_embeddings_resources_field_provider ON embeddings_resources (field_name, provider);
673
+ CREATE INDEX idx_embeddings_resources_field_provider ON embeddings_resources (field_name, provider);
484
674
 
485
675
  -- HNSW index for vector similarity search (created in background)
486
676
  -- Note: This will be created by background thread after data load
487
- -- CREATE INDEX IF NOT EXISTS idx_embeddings_resources_vector_hnsw ON embeddings_resources
677
+ -- CREATE INDEX idx_embeddings_resources_vector_hnsw ON embeddings_resources
488
678
  -- USING hnsw (embedding vector_cosine_ops);
489
679
 
490
680
  -- KV_STORE trigger for resources
@@ -509,7 +699,7 @@ BEGIN
509
699
  graph_edges,
510
700
  updated_at
511
701
  ) VALUES (
512
- NEW.name, -- Use name as entity_key (json_schema_extra)
702
+ NEW.name::VARCHAR,
513
703
  'resources',
514
704
  NEW.id,
515
705
  NEW.tenant_id,
@@ -546,8 +736,13 @@ CREATE TABLE IF NOT EXISTS messages (
546
736
  tenant_id VARCHAR(100) NOT NULL,
547
737
  user_id VARCHAR(256),
548
738
  content TEXT NOT NULL,
549
- message_type TEXT,
550
- session_id TEXT,
739
+ message_type VARCHAR(256),
740
+ session_id VARCHAR(256),
741
+ prompt TEXT,
742
+ model VARCHAR(256),
743
+ token_count INTEGER,
744
+ trace_id VARCHAR(256),
745
+ span_id VARCHAR(256),
551
746
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
552
747
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
553
748
  deleted_at TIMESTAMP,
@@ -556,11 +751,11 @@ CREATE TABLE IF NOT EXISTS messages (
556
751
  tags TEXT[] DEFAULT ARRAY[]::TEXT[]
557
752
  );
558
753
 
559
- CREATE INDEX IF NOT EXISTS idx_messages_tenant ON messages (tenant_id);
560
- CREATE INDEX IF NOT EXISTS idx_messages_user ON messages (user_id);
561
- CREATE INDEX IF NOT EXISTS idx_messages_graph_edges ON messages USING GIN (graph_edges);
562
- CREATE INDEX IF NOT EXISTS idx_messages_metadata ON messages USING GIN (metadata);
563
- CREATE INDEX IF NOT EXISTS idx_messages_tags ON messages USING GIN (tags);
754
+ CREATE INDEX idx_messages_tenant ON messages (tenant_id);
755
+ CREATE INDEX idx_messages_user ON messages (user_id);
756
+ CREATE INDEX idx_messages_graph_edges ON messages USING GIN (graph_edges);
757
+ CREATE INDEX idx_messages_metadata ON messages USING GIN (metadata);
758
+ CREATE INDEX idx_messages_tags ON messages USING GIN (tags);
564
759
 
565
760
  -- Embeddings for messages
566
761
  CREATE TABLE IF NOT EXISTS embeddings_messages (
@@ -578,14 +773,14 @@ CREATE TABLE IF NOT EXISTS embeddings_messages (
578
773
  );
579
774
 
580
775
  -- Index for entity lookup (get all embeddings for entity)
581
- CREATE INDEX IF NOT EXISTS idx_embeddings_messages_entity ON embeddings_messages (entity_id);
776
+ CREATE INDEX idx_embeddings_messages_entity ON embeddings_messages (entity_id);
582
777
 
583
778
  -- Index for field + provider lookup
584
- CREATE INDEX IF NOT EXISTS idx_embeddings_messages_field_provider ON embeddings_messages (field_name, provider);
779
+ CREATE INDEX idx_embeddings_messages_field_provider ON embeddings_messages (field_name, provider);
585
780
 
586
781
  -- HNSW index for vector similarity search (created in background)
587
782
  -- Note: This will be created by background thread after data load
588
- -- CREATE INDEX IF NOT EXISTS idx_embeddings_messages_vector_hnsw ON embeddings_messages
783
+ -- CREATE INDEX idx_embeddings_messages_vector_hnsw ON embeddings_messages
589
784
  -- USING hnsw (embedding vector_cosine_ops);
590
785
 
591
786
  -- KV_STORE trigger for messages
@@ -610,7 +805,7 @@ BEGIN
610
805
  graph_edges,
611
806
  updated_at
612
807
  ) VALUES (
613
- NEW.id::VARCHAR, -- Messages use id as entity_key (no name field)
808
+ NEW.id::VARCHAR,
614
809
  'messages',
615
810
  NEW.id,
616
811
  NEW.tenant_id,
@@ -661,11 +856,11 @@ CREATE TABLE IF NOT EXISTS files (
661
856
  tags TEXT[] DEFAULT ARRAY[]::TEXT[]
662
857
  );
663
858
 
664
- CREATE INDEX IF NOT EXISTS idx_files_tenant ON files (tenant_id);
665
- CREATE INDEX IF NOT EXISTS idx_files_user ON files (user_id);
666
- CREATE INDEX IF NOT EXISTS idx_files_graph_edges ON files USING GIN (graph_edges);
667
- CREATE INDEX IF NOT EXISTS idx_files_metadata ON files USING GIN (metadata);
668
- CREATE INDEX IF NOT EXISTS idx_files_tags ON files USING GIN (tags);
859
+ CREATE INDEX idx_files_tenant ON files (tenant_id);
860
+ CREATE INDEX idx_files_user ON files (user_id);
861
+ CREATE INDEX idx_files_graph_edges ON files USING GIN (graph_edges);
862
+ CREATE INDEX idx_files_metadata ON files USING GIN (metadata);
863
+ CREATE INDEX idx_files_tags ON files USING GIN (tags);
669
864
 
670
865
  -- Embeddings for files
671
866
  CREATE TABLE IF NOT EXISTS embeddings_files (
@@ -683,14 +878,14 @@ CREATE TABLE IF NOT EXISTS embeddings_files (
683
878
  );
684
879
 
685
880
  -- Index for entity lookup (get all embeddings for entity)
686
- CREATE INDEX IF NOT EXISTS idx_embeddings_files_entity ON embeddings_files (entity_id);
881
+ CREATE INDEX idx_embeddings_files_entity ON embeddings_files (entity_id);
687
882
 
688
883
  -- Index for field + provider lookup
689
- CREATE INDEX IF NOT EXISTS idx_embeddings_files_field_provider ON embeddings_files (field_name, provider);
884
+ CREATE INDEX idx_embeddings_files_field_provider ON embeddings_files (field_name, provider);
690
885
 
691
886
  -- HNSW index for vector similarity search (created in background)
692
887
  -- Note: This will be created by background thread after data load
693
- -- CREATE INDEX IF NOT EXISTS idx_embeddings_files_vector_hnsw ON embeddings_files
888
+ -- CREATE INDEX idx_embeddings_files_vector_hnsw ON embeddings_files
694
889
  -- USING hnsw (embedding vector_cosine_ops);
695
890
 
696
891
  -- KV_STORE trigger for files
@@ -715,7 +910,7 @@ BEGIN
715
910
  graph_edges,
716
911
  updated_at
717
912
  ) VALUES (
718
- NEW.name, -- Use name as entity_key (json_schema_extra)
913
+ NEW.id::VARCHAR,
719
914
  'files',
720
915
  NEW.id,
721
916
  NEW.tenant_id,
@@ -752,7 +947,7 @@ CREATE TABLE IF NOT EXISTS ontologies (
752
947
  tenant_id VARCHAR(100) NOT NULL,
753
948
  user_id VARCHAR(256),
754
949
  name VARCHAR(256) NOT NULL,
755
- file_id TEXT NOT NULL,
950
+ file_id UUID NOT NULL,
756
951
  agent_schema_id VARCHAR(256) NOT NULL,
757
952
  provider_name VARCHAR(256) NOT NULL,
758
953
  model_name VARCHAR(256) NOT NULL,
@@ -768,11 +963,11 @@ CREATE TABLE IF NOT EXISTS ontologies (
768
963
  tags TEXT[] DEFAULT ARRAY[]::TEXT[]
769
964
  );
770
965
 
771
- CREATE INDEX IF NOT EXISTS idx_ontologies_tenant ON ontologies (tenant_id);
772
- CREATE INDEX IF NOT EXISTS idx_ontologies_user ON ontologies (user_id);
773
- CREATE INDEX IF NOT EXISTS idx_ontologies_graph_edges ON ontologies USING GIN (graph_edges);
774
- CREATE INDEX IF NOT EXISTS idx_ontologies_metadata ON ontologies USING GIN (metadata);
775
- CREATE INDEX IF NOT EXISTS idx_ontologies_tags ON ontologies USING GIN (tags);
966
+ CREATE INDEX idx_ontologies_tenant ON ontologies (tenant_id);
967
+ CREATE INDEX idx_ontologies_user ON ontologies (user_id);
968
+ CREATE INDEX idx_ontologies_graph_edges ON ontologies USING GIN (graph_edges);
969
+ CREATE INDEX idx_ontologies_metadata ON ontologies USING GIN (metadata);
970
+ CREATE INDEX idx_ontologies_tags ON ontologies USING GIN (tags);
776
971
 
777
972
  -- KV_STORE trigger for ontologies
778
973
  -- Trigger function to maintain KV_STORE for ontologies
@@ -796,7 +991,7 @@ BEGIN
796
991
  graph_edges,
797
992
  updated_at
798
993
  ) VALUES (
799
- NEW.name, -- Use name as entity_key (json_schema_extra)
994
+ NEW.id::VARCHAR,
800
995
  'ontologies',
801
996
  NEW.id,
802
997
  NEW.tenant_id,
@@ -850,11 +1045,11 @@ CREATE TABLE IF NOT EXISTS ontology_configs (
850
1045
  tags TEXT[] DEFAULT ARRAY[]::TEXT[]
851
1046
  );
852
1047
 
853
- CREATE INDEX IF NOT EXISTS idx_ontology_configs_tenant ON ontology_configs (tenant_id);
854
- CREATE INDEX IF NOT EXISTS idx_ontology_configs_user ON ontology_configs (user_id);
855
- CREATE INDEX IF NOT EXISTS idx_ontology_configs_graph_edges ON ontology_configs USING GIN (graph_edges);
856
- CREATE INDEX IF NOT EXISTS idx_ontology_configs_metadata ON ontology_configs USING GIN (metadata);
857
- CREATE INDEX IF NOT EXISTS idx_ontology_configs_tags ON ontology_configs USING GIN (tags);
1048
+ CREATE INDEX idx_ontology_configs_tenant ON ontology_configs (tenant_id);
1049
+ CREATE INDEX idx_ontology_configs_user ON ontology_configs (user_id);
1050
+ CREATE INDEX idx_ontology_configs_graph_edges ON ontology_configs USING GIN (graph_edges);
1051
+ CREATE INDEX idx_ontology_configs_metadata ON ontology_configs USING GIN (metadata);
1052
+ CREATE INDEX idx_ontology_configs_tags ON ontology_configs USING GIN (tags);
858
1053
 
859
1054
  -- Embeddings for ontology_configs
860
1055
  CREATE TABLE IF NOT EXISTS embeddings_ontology_configs (
@@ -872,14 +1067,14 @@ CREATE TABLE IF NOT EXISTS embeddings_ontology_configs (
872
1067
  );
873
1068
 
874
1069
  -- Index for entity lookup (get all embeddings for entity)
875
- CREATE INDEX IF NOT EXISTS idx_embeddings_ontology_configs_entity ON embeddings_ontology_configs (entity_id);
1070
+ CREATE INDEX idx_embeddings_ontology_configs_entity ON embeddings_ontology_configs (entity_id);
876
1071
 
877
1072
  -- Index for field + provider lookup
878
- CREATE INDEX IF NOT EXISTS idx_embeddings_ontology_configs_field_provider ON embeddings_ontology_configs (field_name, provider);
1073
+ CREATE INDEX idx_embeddings_ontology_configs_field_provider ON embeddings_ontology_configs (field_name, provider);
879
1074
 
880
1075
  -- HNSW index for vector similarity search (created in background)
881
1076
  -- Note: This will be created by background thread after data load
882
- -- CREATE INDEX IF NOT EXISTS idx_embeddings_ontology_configs_vector_hnsw ON embeddings_ontology_configs
1077
+ -- CREATE INDEX idx_embeddings_ontology_configs_vector_hnsw ON embeddings_ontology_configs
883
1078
  -- USING hnsw (embedding vector_cosine_ops);
884
1079
 
885
1080
  -- KV_STORE trigger for ontology_configs
@@ -904,7 +1099,7 @@ BEGIN
904
1099
  graph_edges,
905
1100
  updated_at
906
1101
  ) VALUES (
907
- NEW.name, -- Use name as entity_key (json_schema_extra)
1102
+ NEW.id::VARCHAR,
908
1103
  'ontology_configs',
909
1104
  NEW.id,
910
1105
  NEW.tenant_id,
@@ -932,6 +1127,111 @@ CREATE TRIGGER trg_ontology_configs_kv_store
932
1127
  AFTER INSERT OR UPDATE OR DELETE ON ontology_configs
933
1128
  FOR EACH ROW EXECUTE FUNCTION fn_ontology_configs_kv_store_upsert();
934
1129
 
1130
+ -- ======================================================================
1131
+ -- DOMAIN_RESOURCES (Model: DomainResource)
1132
+ -- ======================================================================
1133
+
1134
+ CREATE TABLE IF NOT EXISTS domain_resources (
1135
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
1136
+ tenant_id VARCHAR(100) NOT NULL,
1137
+ user_id VARCHAR(256),
1138
+ name VARCHAR(256),
1139
+ uri VARCHAR(256),
1140
+ ordinal INTEGER,
1141
+ content TEXT,
1142
+ timestamp TIMESTAMP,
1143
+ category VARCHAR(256),
1144
+ related_entities JSONB DEFAULT '{}'::jsonb,
1145
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1146
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1147
+ deleted_at TIMESTAMP,
1148
+ graph_edges JSONB DEFAULT '[]'::jsonb,
1149
+ metadata JSONB DEFAULT '{}'::jsonb,
1150
+ tags TEXT[] DEFAULT ARRAY[]::TEXT[]
1151
+ );
1152
+
1153
+ CREATE INDEX idx_domain_resources_tenant ON domain_resources (tenant_id);
1154
+ CREATE INDEX idx_domain_resources_user ON domain_resources (user_id);
1155
+ CREATE INDEX idx_domain_resources_graph_edges ON domain_resources USING GIN (graph_edges);
1156
+ CREATE INDEX idx_domain_resources_metadata ON domain_resources USING GIN (metadata);
1157
+ CREATE INDEX idx_domain_resources_tags ON domain_resources USING GIN (tags);
1158
+
1159
+ -- Embeddings for domain_resources
1160
+ CREATE TABLE IF NOT EXISTS embeddings_domain_resources (
1161
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
1162
+ entity_id UUID NOT NULL REFERENCES domain_resources(id) ON DELETE CASCADE,
1163
+ field_name VARCHAR(100) NOT NULL,
1164
+ provider VARCHAR(50) NOT NULL DEFAULT 'openai',
1165
+ model VARCHAR(100) NOT NULL DEFAULT 'text-embedding-3-small',
1166
+ embedding vector(1536) NOT NULL,
1167
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1168
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1169
+
1170
+ -- Unique: one embedding per entity per field per provider
1171
+ UNIQUE (entity_id, field_name, provider)
1172
+ );
1173
+
1174
+ -- Index for entity lookup (get all embeddings for entity)
1175
+ CREATE INDEX idx_embeddings_domain_resources_entity ON embeddings_domain_resources (entity_id);
1176
+
1177
+ -- Index for field + provider lookup
1178
+ CREATE INDEX idx_embeddings_domain_resources_field_provider ON embeddings_domain_resources (field_name, provider);
1179
+
1180
+ -- HNSW index for vector similarity search (created in background)
1181
+ -- Note: This will be created by background thread after data load
1182
+ -- CREATE INDEX idx_embeddings_domain_resources_vector_hnsw ON embeddings_domain_resources
1183
+ -- USING hnsw (embedding vector_cosine_ops);
1184
+
1185
+ -- KV_STORE trigger for domain_resources
1186
+ -- Trigger function to maintain KV_STORE for domain_resources
1187
+ CREATE OR REPLACE FUNCTION fn_domain_resources_kv_store_upsert()
1188
+ RETURNS TRIGGER AS $$
1189
+ BEGIN
1190
+ IF (TG_OP = 'DELETE') THEN
1191
+ -- Remove from KV_STORE on delete
1192
+ DELETE FROM kv_store
1193
+ WHERE entity_id = OLD.id;
1194
+ RETURN OLD;
1195
+ ELSIF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
1196
+ -- Upsert to KV_STORE (O(1) lookup by entity_key)
1197
+ INSERT INTO kv_store (
1198
+ entity_key,
1199
+ entity_type,
1200
+ entity_id,
1201
+ tenant_id,
1202
+ user_id,
1203
+ metadata,
1204
+ graph_edges,
1205
+ updated_at
1206
+ ) VALUES (
1207
+ NEW.name::VARCHAR,
1208
+ 'domain_resources',
1209
+ NEW.id,
1210
+ NEW.tenant_id,
1211
+ NEW.user_id,
1212
+ NEW.metadata,
1213
+ COALESCE(NEW.graph_edges, '[]'::jsonb),
1214
+ CURRENT_TIMESTAMP
1215
+ )
1216
+ ON CONFLICT (tenant_id, entity_key)
1217
+ DO UPDATE SET
1218
+ entity_id = EXCLUDED.entity_id,
1219
+ user_id = EXCLUDED.user_id,
1220
+ metadata = EXCLUDED.metadata,
1221
+ graph_edges = EXCLUDED.graph_edges,
1222
+ updated_at = CURRENT_TIMESTAMP;
1223
+
1224
+ RETURN NEW;
1225
+ END IF;
1226
+ END;
1227
+ $$ LANGUAGE plpgsql;
1228
+
1229
+ -- Create trigger
1230
+ DROP TRIGGER IF EXISTS trg_domain_resources_kv_store ON domain_resources;
1231
+ CREATE TRIGGER trg_domain_resources_kv_store
1232
+ AFTER INSERT OR UPDATE OR DELETE ON domain_resources
1233
+ FOR EACH ROW EXECUTE FUNCTION fn_domain_resources_kv_store_upsert();
1234
+
935
1235
  -- ======================================================================
936
1236
  -- SCHEMAS (Model: Schema)
937
1237
  -- ======================================================================
@@ -954,11 +1254,11 @@ CREATE TABLE IF NOT EXISTS schemas (
954
1254
  tags TEXT[] DEFAULT ARRAY[]::TEXT[]
955
1255
  );
956
1256
 
957
- CREATE INDEX IF NOT EXISTS idx_schemas_tenant ON schemas (tenant_id);
958
- CREATE INDEX IF NOT EXISTS idx_schemas_user ON schemas (user_id);
959
- CREATE INDEX IF NOT EXISTS idx_schemas_graph_edges ON schemas USING GIN (graph_edges);
960
- CREATE INDEX IF NOT EXISTS idx_schemas_metadata ON schemas USING GIN (metadata);
961
- CREATE INDEX IF NOT EXISTS idx_schemas_tags ON schemas USING GIN (tags);
1257
+ CREATE INDEX idx_schemas_tenant ON schemas (tenant_id);
1258
+ CREATE INDEX idx_schemas_user ON schemas (user_id);
1259
+ CREATE INDEX idx_schemas_graph_edges ON schemas USING GIN (graph_edges);
1260
+ CREATE INDEX idx_schemas_metadata ON schemas USING GIN (metadata);
1261
+ CREATE INDEX idx_schemas_tags ON schemas USING GIN (tags);
962
1262
 
963
1263
  -- Embeddings for schemas
964
1264
  CREATE TABLE IF NOT EXISTS embeddings_schemas (
@@ -976,14 +1276,14 @@ CREATE TABLE IF NOT EXISTS embeddings_schemas (
976
1276
  );
977
1277
 
978
1278
  -- Index for entity lookup (get all embeddings for entity)
979
- CREATE INDEX IF NOT EXISTS idx_embeddings_schemas_entity ON embeddings_schemas (entity_id);
1279
+ CREATE INDEX idx_embeddings_schemas_entity ON embeddings_schemas (entity_id);
980
1280
 
981
1281
  -- Index for field + provider lookup
982
- CREATE INDEX IF NOT EXISTS idx_embeddings_schemas_field_provider ON embeddings_schemas (field_name, provider);
1282
+ CREATE INDEX idx_embeddings_schemas_field_provider ON embeddings_schemas (field_name, provider);
983
1283
 
984
1284
  -- HNSW index for vector similarity search (created in background)
985
1285
  -- Note: This will be created by background thread after data load
986
- -- CREATE INDEX IF NOT EXISTS idx_embeddings_schemas_vector_hnsw ON embeddings_schemas
1286
+ -- CREATE INDEX idx_embeddings_schemas_vector_hnsw ON embeddings_schemas
987
1287
  -- USING hnsw (embedding vector_cosine_ops);
988
1288
 
989
1289
  -- KV_STORE trigger for schemas
@@ -1008,7 +1308,7 @@ BEGIN
1008
1308
  graph_edges,
1009
1309
  updated_at
1010
1310
  ) VALUES (
1011
- NEW.name, -- Use name as entity_key (json_schema_extra)
1311
+ NEW.id::VARCHAR,
1012
1312
  'schemas',
1013
1313
  NEW.id,
1014
1314
  NEW.tenant_id,
@@ -1036,6 +1336,185 @@ CREATE TRIGGER trg_schemas_kv_store
1036
1336
  AFTER INSERT OR UPDATE OR DELETE ON schemas
1037
1337
  FOR EACH ROW EXECUTE FUNCTION fn_schemas_kv_store_upsert();
1038
1338
 
1339
+ -- ======================================================================
1340
+ -- SHARED_SESSIONS (Session sharing between users)
1341
+ -- ======================================================================
1342
+ -- Lightweight linking table for session sharing. NOT a CoreModel - no
1343
+ -- graph edges, metadata, or embeddings. Just tracks who shared what with whom.
1344
+ --
1345
+ -- See: src/rem/models/entities/shared_session.py for full documentation
1346
+
1347
+ CREATE TABLE IF NOT EXISTS shared_sessions (
1348
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
1349
+ session_id VARCHAR(256) NOT NULL,
1350
+ owner_user_id VARCHAR(256) NOT NULL,
1351
+ shared_with_user_id VARCHAR(256) NOT NULL,
1352
+ tenant_id VARCHAR(100) NOT NULL DEFAULT 'default',
1353
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1354
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1355
+ deleted_at TIMESTAMP,
1356
+
1357
+ -- Prevent duplicate shares (same session, same recipient, active only)
1358
+ CONSTRAINT uq_active_share UNIQUE NULLS NOT DISTINCT (
1359
+ tenant_id, session_id, owner_user_id, shared_with_user_id, deleted_at
1360
+ )
1361
+ );
1362
+
1363
+ -- Index for finding shares by recipient (who is sharing WITH me)
1364
+ CREATE INDEX IF NOT EXISTS idx_shared_sessions_recipient
1365
+ ON shared_sessions (tenant_id, shared_with_user_id)
1366
+ WHERE deleted_at IS NULL;
1367
+
1368
+ -- Index for finding shares by owner (what have I shared)
1369
+ CREATE INDEX IF NOT EXISTS idx_shared_sessions_owner
1370
+ ON shared_sessions (tenant_id, owner_user_id)
1371
+ WHERE deleted_at IS NULL;
1372
+
1373
+ -- Index for finding shares by session
1374
+ CREATE INDEX IF NOT EXISTS idx_shared_sessions_session
1375
+ ON shared_sessions (tenant_id, session_id)
1376
+ WHERE deleted_at IS NULL;
1377
+
1378
+ -- Aggregation function: Get users sharing with me
1379
+ CREATE OR REPLACE FUNCTION fn_get_shared_with_me(
1380
+ p_tenant_id VARCHAR(100),
1381
+ p_user_id VARCHAR(256),
1382
+ p_limit INTEGER DEFAULT 50,
1383
+ p_offset INTEGER DEFAULT 0
1384
+ )
1385
+ RETURNS TABLE (
1386
+ user_id VARCHAR(256),
1387
+ name VARCHAR(256),
1388
+ email VARCHAR(256),
1389
+ message_count BIGINT,
1390
+ session_count BIGINT,
1391
+ first_message_at TIMESTAMP,
1392
+ last_message_at TIMESTAMP
1393
+ ) AS $$
1394
+ BEGIN
1395
+ RETURN QUERY
1396
+ WITH shared_with_me AS (
1397
+ SELECT DISTINCT
1398
+ ss.session_id,
1399
+ ss.owner_user_id
1400
+ FROM shared_sessions ss
1401
+ WHERE ss.tenant_id = p_tenant_id
1402
+ AND ss.shared_with_user_id = p_user_id
1403
+ AND ss.deleted_at IS NULL
1404
+ ),
1405
+ message_stats AS (
1406
+ SELECT
1407
+ swm.owner_user_id,
1408
+ COUNT(DISTINCT m.id) AS msg_count,
1409
+ COUNT(DISTINCT m.session_id) AS sess_count,
1410
+ MIN(m.created_at) AS first_msg,
1411
+ MAX(m.created_at) AS last_msg
1412
+ FROM shared_with_me swm
1413
+ LEFT JOIN messages m ON m.session_id = swm.session_id
1414
+ AND m.tenant_id = p_tenant_id
1415
+ AND m.deleted_at IS NULL
1416
+ GROUP BY swm.owner_user_id
1417
+ )
1418
+ SELECT
1419
+ ms.owner_user_id AS user_id,
1420
+ u.name,
1421
+ u.email,
1422
+ COALESCE(ms.msg_count, 0) AS message_count,
1423
+ COALESCE(ms.sess_count, 0) AS session_count,
1424
+ ms.first_msg AS first_message_at,
1425
+ ms.last_msg AS last_message_at
1426
+ FROM message_stats ms
1427
+ LEFT JOIN users u ON u.user_id = ms.owner_user_id
1428
+ AND u.tenant_id = p_tenant_id
1429
+ AND u.deleted_at IS NULL
1430
+ ORDER BY ms.last_msg DESC NULLS LAST, ms.msg_count DESC
1431
+ LIMIT p_limit
1432
+ OFFSET p_offset;
1433
+ END;
1434
+ $$ LANGUAGE plpgsql;
1435
+
1436
+ -- Count function for pagination
1437
+ CREATE OR REPLACE FUNCTION fn_count_shared_with_me(
1438
+ p_tenant_id VARCHAR(100),
1439
+ p_user_id VARCHAR(256)
1440
+ )
1441
+ RETURNS BIGINT AS $$
1442
+ BEGIN
1443
+ RETURN (
1444
+ SELECT COUNT(DISTINCT owner_user_id)
1445
+ FROM shared_sessions
1446
+ WHERE tenant_id = p_tenant_id
1447
+ AND shared_with_user_id = p_user_id
1448
+ AND deleted_at IS NULL
1449
+ );
1450
+ END;
1451
+ $$ LANGUAGE plpgsql;
1452
+
1453
+ -- Get messages from sessions shared by a specific owner
1454
+ CREATE OR REPLACE FUNCTION fn_get_shared_messages(
1455
+ p_tenant_id VARCHAR(100),
1456
+ p_recipient_user_id VARCHAR(256),
1457
+ p_owner_user_id VARCHAR(256),
1458
+ p_limit INTEGER DEFAULT 50,
1459
+ p_offset INTEGER DEFAULT 0
1460
+ )
1461
+ RETURNS TABLE (
1462
+ id UUID,
1463
+ content TEXT,
1464
+ message_type VARCHAR(256),
1465
+ session_id VARCHAR(256),
1466
+ model VARCHAR(256),
1467
+ token_count INTEGER,
1468
+ created_at TIMESTAMP,
1469
+ metadata JSONB
1470
+ ) AS $$
1471
+ BEGIN
1472
+ RETURN QUERY
1473
+ SELECT
1474
+ m.id,
1475
+ m.content,
1476
+ m.message_type,
1477
+ m.session_id,
1478
+ m.model,
1479
+ m.token_count,
1480
+ m.created_at,
1481
+ m.metadata
1482
+ FROM messages m
1483
+ INNER JOIN shared_sessions ss ON ss.session_id = m.session_id
1484
+ AND ss.tenant_id = m.tenant_id
1485
+ AND ss.deleted_at IS NULL
1486
+ WHERE m.tenant_id = p_tenant_id
1487
+ AND ss.shared_with_user_id = p_recipient_user_id
1488
+ AND ss.owner_user_id = p_owner_user_id
1489
+ AND m.deleted_at IS NULL
1490
+ ORDER BY m.created_at DESC
1491
+ LIMIT p_limit
1492
+ OFFSET p_offset;
1493
+ END;
1494
+ $$ LANGUAGE plpgsql;
1495
+
1496
+ -- Count shared messages for pagination
1497
+ CREATE OR REPLACE FUNCTION fn_count_shared_messages(
1498
+ p_tenant_id VARCHAR(100),
1499
+ p_recipient_user_id VARCHAR(256),
1500
+ p_owner_user_id VARCHAR(256)
1501
+ )
1502
+ RETURNS BIGINT AS $$
1503
+ BEGIN
1504
+ RETURN (
1505
+ SELECT COUNT(m.id)
1506
+ FROM messages m
1507
+ INNER JOIN shared_sessions ss ON ss.session_id = m.session_id
1508
+ AND ss.tenant_id = m.tenant_id
1509
+ AND ss.deleted_at IS NULL
1510
+ WHERE m.tenant_id = p_tenant_id
1511
+ AND ss.shared_with_user_id = p_recipient_user_id
1512
+ AND ss.owner_user_id = p_owner_user_id
1513
+ AND m.deleted_at IS NULL
1514
+ );
1515
+ END;
1516
+ $$ LANGUAGE plpgsql;
1517
+
1039
1518
  -- ============================================================================
1040
1519
  -- RECORD MIGRATION
1041
1520
  -- ============================================================================
@@ -1049,8 +1528,10 @@ SET applied_at = CURRENT_TIMESTAMP,
1049
1528
  DO $$
1050
1529
  BEGIN
1051
1530
  RAISE NOTICE '============================================================';
1052
- RAISE NOTICE 'REM Model Schema Applied: 10 tables';
1531
+ RAISE NOTICE 'REM Model Schema Applied: 14 tables';
1053
1532
  RAISE NOTICE '============================================================';
1533
+ RAISE NOTICE ' ✓ domain_resources (1 embeddable fields)';
1534
+ RAISE NOTICE ' ✓ feedbacks';
1054
1535
  RAISE NOTICE ' ✓ files (1 embeddable fields)';
1055
1536
  RAISE NOTICE ' ✓ image_resources (1 embeddable fields)';
1056
1537
  RAISE NOTICE ' ✓ messages (1 embeddable fields)';
@@ -1060,143 +1541,11 @@ BEGIN
1060
1541
  RAISE NOTICE ' ✓ persons';
1061
1542
  RAISE NOTICE ' ✓ resources (1 embeddable fields)';
1062
1543
  RAISE NOTICE ' ✓ schemas (1 embeddable fields)';
1544
+ RAISE NOTICE ' ✓ sessions (1 embeddable fields)';
1545
+ RAISE NOTICE ' ✓ shared_sessions (session sharing)';
1063
1546
  RAISE NOTICE ' ✓ users (1 embeddable fields)';
1064
1547
  RAISE NOTICE '';
1065
1548
  RAISE NOTICE 'Next: Run background indexes if needed';
1066
1549
  RAISE NOTICE ' rem db migrate --background-indexes';
1067
1550
  RAISE NOTICE '============================================================';
1068
- END $$;
1069
- -- ============================================================================
1070
- -- REM TRAVERSE (Graph Traversal)
1071
- -- ============================================================================
1072
-
1073
- -- REM TRAVERSE: Recursive graph traversal following edges
1074
- -- Explores graph_edges starting from entity_key up to max_depth
1075
- -- Uses cached kv_store.graph_edges for fast traversal (no polymorphic view!)
1076
- -- When keys_only=false, automatically fetches full entity records
1077
- CREATE OR REPLACE FUNCTION rem_traverse(
1078
- p_entity_key VARCHAR(255),
1079
- p_tenant_id VARCHAR(100), -- Backward compat parameter (not used for filtering)
1080
- p_user_id VARCHAR(100),
1081
- p_max_depth INTEGER DEFAULT 1,
1082
- p_rel_type VARCHAR(100) DEFAULT NULL,
1083
- p_keys_only BOOLEAN DEFAULT FALSE
1084
- )
1085
- RETURNS TABLE(
1086
- depth INTEGER,
1087
- entity_key VARCHAR(255),
1088
- entity_type VARCHAR(100),
1089
- entity_id UUID,
1090
- rel_type VARCHAR(100),
1091
- rel_weight REAL,
1092
- path TEXT[],
1093
- entity_record JSONB
1094
- ) AS $$
1095
- DECLARE
1096
- graph_keys RECORD;
1097
- entities_by_table JSONB := '{}'::jsonb;
1098
- table_keys JSONB;
1099
- BEGIN
1100
- -- First, build graph structure from KV store
1101
- FOR graph_keys IN
1102
- WITH RECURSIVE graph_traversal AS (
1103
- -- Base case: Find starting entity
1104
- SELECT
1105
- 0 AS depth,
1106
- kv.entity_key,
1107
- kv.entity_type,
1108
- kv.entity_id,
1109
- NULL::VARCHAR(100) AS rel_type,
1110
- NULL::REAL AS rel_weight,
1111
- ARRAY[kv.entity_key]::TEXT[] AS path
1112
- FROM kv_store kv
1113
- WHERE kv.user_id = p_user_id
1114
- AND kv.entity_key = p_entity_key
1115
-
1116
- UNION ALL
1117
-
1118
- -- Recursive case: Follow outbound edges from discovered entities
1119
- SELECT
1120
- gt.depth + 1,
1121
- target_kv.entity_key,
1122
- target_kv.entity_type,
1123
- target_kv.entity_id,
1124
- (edge->>'rel_type')::VARCHAR(100) AS rel_type,
1125
- COALESCE((edge->>'weight')::REAL, 1.0) AS rel_weight,
1126
- gt.path || target_kv.entity_key AS path
1127
- FROM graph_traversal gt
1128
- -- Join to KV store to get source entity (with cached graph_edges!)
1129
- JOIN kv_store source_kv ON source_kv.entity_key = gt.entity_key
1130
- AND source_kv.user_id = p_user_id
1131
- -- Extract edges directly from cached kv_store.graph_edges (NO polymorphic view!)
1132
- CROSS JOIN LATERAL jsonb_array_elements(COALESCE(source_kv.graph_edges, '[]'::jsonb)) AS edge
1133
- -- Lookup target entity in KV store
1134
- JOIN kv_store target_kv ON target_kv.entity_key = (edge->>'dst')::VARCHAR(255)
1135
- AND target_kv.user_id = p_user_id
1136
- WHERE gt.depth < p_max_depth
1137
- -- Filter by relationship type if specified
1138
- AND (p_rel_type IS NULL OR (edge->>'rel_type')::VARCHAR(100) = p_rel_type)
1139
- -- Prevent cycles by checking path
1140
- AND NOT (target_kv.entity_key = ANY(gt.path))
1141
- )
1142
- SELECT DISTINCT ON (entity_key)
1143
- gt.depth,
1144
- gt.entity_key,
1145
- gt.entity_type,
1146
- gt.entity_id,
1147
- gt.rel_type,
1148
- gt.rel_weight,
1149
- gt.path
1150
- FROM graph_traversal gt
1151
- WHERE gt.depth > 0 -- Exclude starting entity
1152
- ORDER BY gt.entity_key, gt.depth
1153
- LOOP
1154
- IF p_keys_only THEN
1155
- -- Return just graph structure (no entity_record)
1156
- depth := graph_keys.depth;
1157
- entity_key := graph_keys.entity_key;
1158
- entity_type := graph_keys.entity_type;
1159
- entity_id := graph_keys.entity_id;
1160
- rel_type := graph_keys.rel_type;
1161
- rel_weight := graph_keys.rel_weight;
1162
- path := graph_keys.path;
1163
- entity_record := NULL;
1164
- RETURN NEXT;
1165
- ELSE
1166
- -- Build JSONB mapping {table: [keys]} for batch fetch
1167
- IF entities_by_table ? graph_keys.entity_type THEN
1168
- table_keys := entities_by_table->graph_keys.entity_type;
1169
- entities_by_table := jsonb_set(
1170
- entities_by_table,
1171
- ARRAY[graph_keys.entity_type],
1172
- table_keys || jsonb_build_array(graph_keys.entity_key)
1173
- );
1174
- ELSE
1175
- entities_by_table := jsonb_set(
1176
- entities_by_table,
1177
- ARRAY[graph_keys.entity_type],
1178
- jsonb_build_array(graph_keys.entity_key)
1179
- );
1180
- END IF;
1181
- END IF;
1182
- END LOOP;
1183
-
1184
- -- If keys_only=false, fetch full records using rem_fetch
1185
- IF NOT p_keys_only AND entities_by_table != '{}'::jsonb THEN
1186
- RETURN QUERY
1187
- SELECT
1188
- NULL::INTEGER AS depth,
1189
- f.entity_key::VARCHAR(255),
1190
- f.entity_type::VARCHAR(100),
1191
- NULL::UUID AS entity_id,
1192
- NULL::VARCHAR(100) AS rel_type,
1193
- NULL::REAL AS rel_weight,
1194
- NULL::TEXT[] AS path,
1195
- f.entity_record
1196
- FROM rem_fetch(entities_by_table, p_user_id) f;
1197
- END IF;
1198
- END;
1199
- $$ LANGUAGE plpgsql STABLE;
1200
-
1201
- COMMENT ON FUNCTION rem_traverse IS
1202
- 'REM TRAVERSE query: Recursive graph traversal using cached kv_store.graph_edges. When keys_only=false (default), automatically fetches full entity records via rem_fetch.';
1551
+ END $$;