remdb 0.3.0__py3-none-any.whl → 0.3.114__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 (98) 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 +28 -22
  7. rem/agentic/llm_provider_models.py +301 -0
  8. rem/agentic/otel/setup.py +92 -4
  9. rem/agentic/providers/phoenix.py +32 -43
  10. rem/agentic/providers/pydantic_ai.py +142 -22
  11. rem/agentic/schema.py +358 -21
  12. rem/agentic/tools/rem_tools.py +3 -3
  13. rem/api/README.md +238 -1
  14. rem/api/deps.py +255 -0
  15. rem/api/main.py +151 -37
  16. rem/api/mcp_router/resources.py +1 -1
  17. rem/api/mcp_router/server.py +17 -2
  18. rem/api/mcp_router/tools.py +143 -7
  19. rem/api/middleware/tracking.py +172 -0
  20. rem/api/routers/admin.py +277 -0
  21. rem/api/routers/auth.py +124 -0
  22. rem/api/routers/chat/completions.py +152 -16
  23. rem/api/routers/chat/models.py +7 -3
  24. rem/api/routers/chat/sse_events.py +526 -0
  25. rem/api/routers/chat/streaming.py +608 -45
  26. rem/api/routers/dev.py +81 -0
  27. rem/api/routers/feedback.py +148 -0
  28. rem/api/routers/messages.py +473 -0
  29. rem/api/routers/models.py +78 -0
  30. rem/api/routers/query.py +357 -0
  31. rem/api/routers/shared_sessions.py +406 -0
  32. rem/auth/middleware.py +126 -27
  33. rem/cli/commands/README.md +201 -70
  34. rem/cli/commands/ask.py +13 -10
  35. rem/cli/commands/cluster.py +1359 -0
  36. rem/cli/commands/configure.py +4 -3
  37. rem/cli/commands/db.py +350 -137
  38. rem/cli/commands/experiments.py +76 -72
  39. rem/cli/commands/process.py +22 -15
  40. rem/cli/commands/scaffold.py +47 -0
  41. rem/cli/commands/schema.py +95 -49
  42. rem/cli/main.py +29 -6
  43. rem/config.py +2 -2
  44. rem/models/core/core_model.py +7 -1
  45. rem/models/core/rem_query.py +5 -2
  46. rem/models/entities/__init__.py +21 -0
  47. rem/models/entities/domain_resource.py +38 -0
  48. rem/models/entities/feedback.py +123 -0
  49. rem/models/entities/message.py +30 -1
  50. rem/models/entities/session.py +83 -0
  51. rem/models/entities/shared_session.py +180 -0
  52. rem/models/entities/user.py +10 -3
  53. rem/registry.py +373 -0
  54. rem/schemas/agents/rem.yaml +7 -3
  55. rem/services/content/providers.py +94 -140
  56. rem/services/content/service.py +92 -20
  57. rem/services/dreaming/affinity_service.py +2 -16
  58. rem/services/dreaming/moment_service.py +2 -15
  59. rem/services/embeddings/api.py +24 -17
  60. rem/services/embeddings/worker.py +16 -16
  61. rem/services/phoenix/EXPERIMENT_DESIGN.md +3 -3
  62. rem/services/phoenix/client.py +252 -19
  63. rem/services/postgres/README.md +159 -15
  64. rem/services/postgres/__init__.py +2 -1
  65. rem/services/postgres/diff_service.py +426 -0
  66. rem/services/postgres/pydantic_to_sqlalchemy.py +427 -129
  67. rem/services/postgres/repository.py +132 -0
  68. rem/services/postgres/schema_generator.py +86 -5
  69. rem/services/postgres/service.py +6 -6
  70. rem/services/rate_limit.py +113 -0
  71. rem/services/rem/README.md +14 -0
  72. rem/services/rem/parser.py +44 -9
  73. rem/services/rem/service.py +36 -2
  74. rem/services/session/compression.py +17 -1
  75. rem/services/session/reload.py +1 -1
  76. rem/services/user_service.py +98 -0
  77. rem/settings.py +169 -17
  78. rem/sql/background_indexes.sql +21 -16
  79. rem/sql/migrations/001_install.sql +231 -54
  80. rem/sql/migrations/002_install_models.sql +457 -393
  81. rem/sql/migrations/003_optional_extensions.sql +326 -0
  82. rem/utils/constants.py +97 -0
  83. rem/utils/date_utils.py +228 -0
  84. rem/utils/embeddings.py +17 -4
  85. rem/utils/files.py +167 -0
  86. rem/utils/mime_types.py +158 -0
  87. rem/utils/model_helpers.py +156 -1
  88. rem/utils/schema_loader.py +191 -35
  89. rem/utils/sql_types.py +3 -1
  90. rem/utils/vision.py +9 -14
  91. rem/workers/README.md +14 -14
  92. rem/workers/db_maintainer.py +74 -0
  93. {remdb-0.3.0.dist-info → remdb-0.3.114.dist-info}/METADATA +303 -164
  94. {remdb-0.3.0.dist-info → remdb-0.3.114.dist-info}/RECORD +96 -70
  95. {remdb-0.3.0.dist-info → remdb-0.3.114.dist-info}/WHEEL +1 -1
  96. rem/sql/002_install_models.sql +0 -1068
  97. rem/sql/install_models.sql +0 -1038
  98. {remdb-0.3.0.dist-info → remdb-0.3.114.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: model registry
4
+ -- Generated at: 2025-11-29T11:08:16.713884
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,34 +19,113 @@ 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';
30
30
  END $$;
31
31
 
32
32
  -- ======================================================================
33
- -- USERS (Model: User)
33
+ -- FEEDBACKS (Model: Feedback)
34
34
  -- ======================================================================
35
35
 
36
- CREATE TABLE IF NOT EXISTS users (
36
+ CREATE TABLE IF NOT EXISTS feedbacks (
37
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
38
+ tenant_id VARCHAR(100) NOT NULL,
39
+ user_id VARCHAR(256),
40
+ session_id VARCHAR(256) NOT NULL,
41
+ message_id VARCHAR(256),
42
+ rating INTEGER,
43
+ categories TEXT[] DEFAULT ARRAY[]::TEXT[],
44
+ comment TEXT,
45
+ trace_id VARCHAR(256),
46
+ span_id VARCHAR(256),
47
+ phoenix_synced BOOLEAN,
48
+ phoenix_annotation_id VARCHAR(256),
49
+ annotator_kind VARCHAR(256),
50
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
51
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
52
+ deleted_at TIMESTAMP,
53
+ graph_edges JSONB DEFAULT '[]'::jsonb,
54
+ metadata JSONB DEFAULT '{}'::jsonb,
55
+ tags TEXT[] DEFAULT ARRAY[]::TEXT[]
56
+ );
57
+
58
+ CREATE INDEX idx_feedbacks_tenant ON feedbacks (tenant_id);
59
+ CREATE INDEX idx_feedbacks_user ON feedbacks (user_id);
60
+ CREATE INDEX idx_feedbacks_graph_edges ON feedbacks USING GIN (graph_edges);
61
+ CREATE INDEX idx_feedbacks_metadata ON feedbacks USING GIN (metadata);
62
+ CREATE INDEX idx_feedbacks_tags ON feedbacks USING GIN (tags);
63
+
64
+ -- KV_STORE trigger for feedbacks
65
+ -- Trigger function to maintain KV_STORE for feedbacks
66
+ CREATE OR REPLACE FUNCTION fn_feedbacks_kv_store_upsert()
67
+ RETURNS TRIGGER AS $$
68
+ BEGIN
69
+ IF (TG_OP = 'DELETE') THEN
70
+ -- Remove from KV_STORE on delete
71
+ DELETE FROM kv_store
72
+ WHERE entity_id = OLD.id;
73
+ RETURN OLD;
74
+ ELSIF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
75
+ -- Upsert to KV_STORE (O(1) lookup by entity_key)
76
+ INSERT INTO kv_store (
77
+ entity_key,
78
+ entity_type,
79
+ entity_id,
80
+ tenant_id,
81
+ user_id,
82
+ metadata,
83
+ graph_edges,
84
+ updated_at
85
+ ) VALUES (
86
+ NEW.id::VARCHAR,
87
+ 'feedbacks',
88
+ NEW.id,
89
+ NEW.tenant_id,
90
+ NEW.user_id,
91
+ NEW.metadata,
92
+ COALESCE(NEW.graph_edges, '[]'::jsonb),
93
+ CURRENT_TIMESTAMP
94
+ )
95
+ ON CONFLICT (tenant_id, entity_key)
96
+ DO UPDATE SET
97
+ entity_id = EXCLUDED.entity_id,
98
+ user_id = EXCLUDED.user_id,
99
+ metadata = EXCLUDED.metadata,
100
+ graph_edges = EXCLUDED.graph_edges,
101
+ updated_at = CURRENT_TIMESTAMP;
102
+
103
+ RETURN NEW;
104
+ END IF;
105
+ END;
106
+ $$ LANGUAGE plpgsql;
107
+
108
+ -- Create trigger
109
+ DROP TRIGGER IF EXISTS trg_feedbacks_kv_store ON feedbacks;
110
+ CREATE TRIGGER trg_feedbacks_kv_store
111
+ AFTER INSERT OR UPDATE OR DELETE ON feedbacks
112
+ FOR EACH ROW EXECUTE FUNCTION fn_feedbacks_kv_store_upsert();
113
+
114
+ -- ======================================================================
115
+ -- FILES (Model: File)
116
+ -- ======================================================================
117
+
118
+ CREATE TABLE IF NOT EXISTS files (
37
119
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
38
120
  tenant_id VARCHAR(100) NOT NULL,
39
121
  user_id VARCHAR(256),
40
122
  name VARCHAR(256) NOT NULL,
41
- email VARCHAR(256),
42
- role VARCHAR(256),
43
- tier TEXT,
44
- sec_policy JSONB DEFAULT '{}'::jsonb,
45
- summary TEXT,
46
- interests TEXT[] DEFAULT ARRAY[]::TEXT[],
47
- preferred_topics TEXT[] DEFAULT ARRAY[]::TEXT[],
48
- activity_level VARCHAR(256),
49
- last_active_at TIMESTAMP,
123
+ uri VARCHAR(256) NOT NULL,
124
+ content TEXT,
125
+ timestamp VARCHAR(256),
126
+ size_bytes INTEGER,
127
+ mime_type VARCHAR(256),
128
+ processing_status VARCHAR(256),
50
129
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
51
130
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
52
131
  deleted_at TIMESTAMP,
@@ -55,16 +134,16 @@ CREATE TABLE IF NOT EXISTS users (
55
134
  tags TEXT[] DEFAULT ARRAY[]::TEXT[]
56
135
  );
57
136
 
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);
137
+ CREATE INDEX idx_files_tenant ON files (tenant_id);
138
+ CREATE INDEX idx_files_user ON files (user_id);
139
+ CREATE INDEX idx_files_graph_edges ON files USING GIN (graph_edges);
140
+ CREATE INDEX idx_files_metadata ON files USING GIN (metadata);
141
+ CREATE INDEX idx_files_tags ON files USING GIN (tags);
63
142
 
64
- -- Embeddings for users
65
- CREATE TABLE IF NOT EXISTS embeddings_users (
143
+ -- Embeddings for files
144
+ CREATE TABLE IF NOT EXISTS embeddings_files (
66
145
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
67
- entity_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
146
+ entity_id UUID NOT NULL REFERENCES files(id) ON DELETE CASCADE,
68
147
  field_name VARCHAR(100) NOT NULL,
69
148
  provider VARCHAR(50) NOT NULL DEFAULT 'openai',
70
149
  model VARCHAR(100) NOT NULL DEFAULT 'text-embedding-3-small',
@@ -77,19 +156,19 @@ CREATE TABLE IF NOT EXISTS embeddings_users (
77
156
  );
78
157
 
79
158
  -- Index for entity lookup (get all embeddings for entity)
80
- CREATE INDEX IF NOT EXISTS idx_embeddings_users_entity ON embeddings_users (entity_id);
159
+ CREATE INDEX idx_embeddings_files_entity ON embeddings_files (entity_id);
81
160
 
82
161
  -- Index for field + provider lookup
83
- CREATE INDEX IF NOT EXISTS idx_embeddings_users_field_provider ON embeddings_users (field_name, provider);
162
+ CREATE INDEX idx_embeddings_files_field_provider ON embeddings_files (field_name, provider);
84
163
 
85
164
  -- HNSW index for vector similarity search (created in background)
86
165
  -- 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
166
+ -- CREATE INDEX idx_embeddings_files_vector_hnsw ON embeddings_files
88
167
  -- USING hnsw (embedding vector_cosine_ops);
89
168
 
90
- -- KV_STORE trigger for users
91
- -- Trigger function to maintain KV_STORE for users
92
- CREATE OR REPLACE FUNCTION fn_users_kv_store_upsert()
169
+ -- KV_STORE trigger for files
170
+ -- Trigger function to maintain KV_STORE for files
171
+ CREATE OR REPLACE FUNCTION fn_files_kv_store_upsert()
93
172
  RETURNS TRIGGER AS $$
94
173
  BEGIN
95
174
  IF (TG_OP = 'DELETE') THEN
@@ -109,8 +188,8 @@ BEGIN
109
188
  graph_edges,
110
189
  updated_at
111
190
  ) VALUES (
112
- NEW.name, -- Use name as entity_key (json_schema_extra)
113
- 'users',
191
+ NEW.id::VARCHAR,
192
+ 'files',
114
193
  NEW.id,
115
194
  NEW.tenant_id,
116
195
  NEW.user_id,
@@ -132,10 +211,10 @@ END;
132
211
  $$ LANGUAGE plpgsql;
133
212
 
134
213
  -- Create trigger
135
- DROP TRIGGER IF EXISTS trg_users_kv_store ON users;
136
- CREATE TRIGGER trg_users_kv_store
137
- AFTER INSERT OR UPDATE OR DELETE ON users
138
- FOR EACH ROW EXECUTE FUNCTION fn_users_kv_store_upsert();
214
+ DROP TRIGGER IF EXISTS trg_files_kv_store ON files;
215
+ CREATE TRIGGER trg_files_kv_store
216
+ AFTER INSERT OR UPDATE OR DELETE ON files
217
+ FOR EACH ROW EXECUTE FUNCTION fn_files_kv_store_upsert();
139
218
 
140
219
  -- ======================================================================
141
220
  -- IMAGE_RESOURCES (Model: ImageResource)
@@ -145,7 +224,7 @@ CREATE TABLE IF NOT EXISTS image_resources (
145
224
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
146
225
  tenant_id VARCHAR(100) NOT NULL,
147
226
  user_id VARCHAR(256),
148
- name VARCHAR(256) NOT NULL,
227
+ name VARCHAR(256),
149
228
  uri VARCHAR(256),
150
229
  ordinal INTEGER,
151
230
  content TEXT,
@@ -168,11 +247,11 @@ CREATE TABLE IF NOT EXISTS image_resources (
168
247
  tags TEXT[] DEFAULT ARRAY[]::TEXT[]
169
248
  );
170
249
 
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);
250
+ CREATE INDEX idx_image_resources_tenant ON image_resources (tenant_id);
251
+ CREATE INDEX idx_image_resources_user ON image_resources (user_id);
252
+ CREATE INDEX idx_image_resources_graph_edges ON image_resources USING GIN (graph_edges);
253
+ CREATE INDEX idx_image_resources_metadata ON image_resources USING GIN (metadata);
254
+ CREATE INDEX idx_image_resources_tags ON image_resources USING GIN (tags);
176
255
 
177
256
  -- Embeddings for image_resources
178
257
  CREATE TABLE IF NOT EXISTS embeddings_image_resources (
@@ -190,14 +269,14 @@ CREATE TABLE IF NOT EXISTS embeddings_image_resources (
190
269
  );
191
270
 
192
271
  -- 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);
272
+ CREATE INDEX idx_embeddings_image_resources_entity ON embeddings_image_resources (entity_id);
194
273
 
195
274
  -- Index for field + provider lookup
196
- CREATE INDEX IF NOT EXISTS idx_embeddings_image_resources_field_provider ON embeddings_image_resources (field_name, provider);
275
+ CREATE INDEX idx_embeddings_image_resources_field_provider ON embeddings_image_resources (field_name, provider);
197
276
 
198
277
  -- HNSW index for vector similarity search (created in background)
199
278
  -- 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
279
+ -- CREATE INDEX idx_embeddings_image_resources_vector_hnsw ON embeddings_image_resources
201
280
  -- USING hnsw (embedding vector_cosine_ops);
202
281
 
203
282
  -- KV_STORE trigger for image_resources
@@ -222,7 +301,7 @@ BEGIN
222
301
  graph_edges,
223
302
  updated_at
224
303
  ) VALUES (
225
- NEW.name, -- Use name as entity_key (json_schema_extra)
304
+ NEW.name::VARCHAR,
226
305
  'image_resources',
227
306
  NEW.id,
228
307
  NEW.tenant_id,
@@ -250,6 +329,112 @@ CREATE TRIGGER trg_image_resources_kv_store
250
329
  AFTER INSERT OR UPDATE OR DELETE ON image_resources
251
330
  FOR EACH ROW EXECUTE FUNCTION fn_image_resources_kv_store_upsert();
252
331
 
332
+ -- ======================================================================
333
+ -- MESSAGES (Model: Message)
334
+ -- ======================================================================
335
+
336
+ CREATE TABLE IF NOT EXISTS messages (
337
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
338
+ tenant_id VARCHAR(100) NOT NULL,
339
+ user_id VARCHAR(256),
340
+ content TEXT NOT NULL,
341
+ message_type VARCHAR(256),
342
+ session_id VARCHAR(256),
343
+ prompt TEXT,
344
+ model VARCHAR(256),
345
+ token_count INTEGER,
346
+ trace_id VARCHAR(256),
347
+ span_id VARCHAR(256),
348
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
349
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
350
+ deleted_at TIMESTAMP,
351
+ graph_edges JSONB DEFAULT '[]'::jsonb,
352
+ metadata JSONB DEFAULT '{}'::jsonb,
353
+ tags TEXT[] DEFAULT ARRAY[]::TEXT[]
354
+ );
355
+
356
+ CREATE INDEX idx_messages_tenant ON messages (tenant_id);
357
+ CREATE INDEX idx_messages_user ON messages (user_id);
358
+ CREATE INDEX idx_messages_graph_edges ON messages USING GIN (graph_edges);
359
+ CREATE INDEX idx_messages_metadata ON messages USING GIN (metadata);
360
+ CREATE INDEX idx_messages_tags ON messages USING GIN (tags);
361
+
362
+ -- Embeddings for messages
363
+ CREATE TABLE IF NOT EXISTS embeddings_messages (
364
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
365
+ entity_id UUID NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
366
+ field_name VARCHAR(100) NOT NULL,
367
+ provider VARCHAR(50) NOT NULL DEFAULT 'openai',
368
+ model VARCHAR(100) NOT NULL DEFAULT 'text-embedding-3-small',
369
+ embedding vector(1536) NOT NULL,
370
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
371
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
372
+
373
+ -- Unique: one embedding per entity per field per provider
374
+ UNIQUE (entity_id, field_name, provider)
375
+ );
376
+
377
+ -- Index for entity lookup (get all embeddings for entity)
378
+ CREATE INDEX idx_embeddings_messages_entity ON embeddings_messages (entity_id);
379
+
380
+ -- Index for field + provider lookup
381
+ CREATE INDEX idx_embeddings_messages_field_provider ON embeddings_messages (field_name, provider);
382
+
383
+ -- HNSW index for vector similarity search (created in background)
384
+ -- Note: This will be created by background thread after data load
385
+ -- CREATE INDEX idx_embeddings_messages_vector_hnsw ON embeddings_messages
386
+ -- USING hnsw (embedding vector_cosine_ops);
387
+
388
+ -- KV_STORE trigger for messages
389
+ -- Trigger function to maintain KV_STORE for messages
390
+ CREATE OR REPLACE FUNCTION fn_messages_kv_store_upsert()
391
+ RETURNS TRIGGER AS $$
392
+ BEGIN
393
+ IF (TG_OP = 'DELETE') THEN
394
+ -- Remove from KV_STORE on delete
395
+ DELETE FROM kv_store
396
+ WHERE entity_id = OLD.id;
397
+ RETURN OLD;
398
+ ELSIF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
399
+ -- Upsert to KV_STORE (O(1) lookup by entity_key)
400
+ INSERT INTO kv_store (
401
+ entity_key,
402
+ entity_type,
403
+ entity_id,
404
+ tenant_id,
405
+ user_id,
406
+ metadata,
407
+ graph_edges,
408
+ updated_at
409
+ ) VALUES (
410
+ NEW.id::VARCHAR,
411
+ 'messages',
412
+ NEW.id,
413
+ NEW.tenant_id,
414
+ NEW.user_id,
415
+ NEW.metadata,
416
+ COALESCE(NEW.graph_edges, '[]'::jsonb),
417
+ CURRENT_TIMESTAMP
418
+ )
419
+ ON CONFLICT (tenant_id, entity_key)
420
+ DO UPDATE SET
421
+ entity_id = EXCLUDED.entity_id,
422
+ user_id = EXCLUDED.user_id,
423
+ metadata = EXCLUDED.metadata,
424
+ graph_edges = EXCLUDED.graph_edges,
425
+ updated_at = CURRENT_TIMESTAMP;
426
+
427
+ RETURN NEW;
428
+ END IF;
429
+ END;
430
+ $$ LANGUAGE plpgsql;
431
+
432
+ -- Create trigger
433
+ DROP TRIGGER IF EXISTS trg_messages_kv_store ON messages;
434
+ CREATE TRIGGER trg_messages_kv_store
435
+ AFTER INSERT OR UPDATE OR DELETE ON messages
436
+ FOR EACH ROW EXECUTE FUNCTION fn_messages_kv_store_upsert();
437
+
253
438
  -- ======================================================================
254
439
  -- MOMENTS (Model: Moment)
255
440
  -- ======================================================================
@@ -258,7 +443,7 @@ CREATE TABLE IF NOT EXISTS moments (
258
443
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
259
444
  tenant_id VARCHAR(100) NOT NULL,
260
445
  user_id VARCHAR(256),
261
- name VARCHAR(256) NOT NULL,
446
+ name VARCHAR(256),
262
447
  moment_type VARCHAR(256),
263
448
  category VARCHAR(256),
264
449
  starts_timestamp TIMESTAMP NOT NULL,
@@ -276,11 +461,11 @@ CREATE TABLE IF NOT EXISTS moments (
276
461
  tags TEXT[] DEFAULT ARRAY[]::TEXT[]
277
462
  );
278
463
 
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);
464
+ CREATE INDEX idx_moments_tenant ON moments (tenant_id);
465
+ CREATE INDEX idx_moments_user ON moments (user_id);
466
+ CREATE INDEX idx_moments_graph_edges ON moments USING GIN (graph_edges);
467
+ CREATE INDEX idx_moments_metadata ON moments USING GIN (metadata);
468
+ CREATE INDEX idx_moments_tags ON moments USING GIN (tags);
284
469
 
285
470
  -- Embeddings for moments
286
471
  CREATE TABLE IF NOT EXISTS embeddings_moments (
@@ -298,14 +483,14 @@ CREATE TABLE IF NOT EXISTS embeddings_moments (
298
483
  );
299
484
 
300
485
  -- Index for entity lookup (get all embeddings for entity)
301
- CREATE INDEX IF NOT EXISTS idx_embeddings_moments_entity ON embeddings_moments (entity_id);
486
+ CREATE INDEX idx_embeddings_moments_entity ON embeddings_moments (entity_id);
302
487
 
303
488
  -- Index for field + provider lookup
304
- CREATE INDEX IF NOT EXISTS idx_embeddings_moments_field_provider ON embeddings_moments (field_name, provider);
489
+ CREATE INDEX idx_embeddings_moments_field_provider ON embeddings_moments (field_name, provider);
305
490
 
306
491
  -- HNSW index for vector similarity search (created in background)
307
492
  -- 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
493
+ -- CREATE INDEX idx_embeddings_moments_vector_hnsw ON embeddings_moments
309
494
  -- USING hnsw (embedding vector_cosine_ops);
310
495
 
311
496
  -- KV_STORE trigger for moments
@@ -330,7 +515,7 @@ BEGIN
330
515
  graph_edges,
331
516
  updated_at
332
517
  ) VALUES (
333
- NEW.name, -- Use name as entity_key (json_schema_extra)
518
+ NEW.name::VARCHAR,
334
519
  'moments',
335
520
  NEW.id,
336
521
  NEW.tenant_id,
@@ -359,15 +544,22 @@ AFTER INSERT OR UPDATE OR DELETE ON moments
359
544
  FOR EACH ROW EXECUTE FUNCTION fn_moments_kv_store_upsert();
360
545
 
361
546
  -- ======================================================================
362
- -- PERSONS (Model: Person)
547
+ -- ONTOLOGIES (Model: Ontology)
363
548
  -- ======================================================================
364
549
 
365
- CREATE TABLE IF NOT EXISTS persons (
550
+ CREATE TABLE IF NOT EXISTS ontologies (
366
551
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
367
552
  tenant_id VARCHAR(100) NOT NULL,
368
553
  user_id VARCHAR(256),
369
554
  name VARCHAR(256) NOT NULL,
370
- role VARCHAR(256),
555
+ file_id UUID NOT NULL,
556
+ agent_schema_id VARCHAR(256) NOT NULL,
557
+ provider_name VARCHAR(256) NOT NULL,
558
+ model_name VARCHAR(256) NOT NULL,
559
+ extracted_data JSONB NOT NULL,
560
+ confidence_score DOUBLE PRECISION,
561
+ extraction_timestamp VARCHAR(256),
562
+ embedding_text TEXT,
371
563
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
372
564
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
373
565
  deleted_at TIMESTAMP,
@@ -376,15 +568,15 @@ CREATE TABLE IF NOT EXISTS persons (
376
568
  tags TEXT[] DEFAULT ARRAY[]::TEXT[]
377
569
  );
378
570
 
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);
571
+ CREATE INDEX idx_ontologies_tenant ON ontologies (tenant_id);
572
+ CREATE INDEX idx_ontologies_user ON ontologies (user_id);
573
+ CREATE INDEX idx_ontologies_graph_edges ON ontologies USING GIN (graph_edges);
574
+ CREATE INDEX idx_ontologies_metadata ON ontologies USING GIN (metadata);
575
+ CREATE INDEX idx_ontologies_tags ON ontologies USING GIN (tags);
384
576
 
385
- -- KV_STORE trigger for persons
386
- -- Trigger function to maintain KV_STORE for persons
387
- CREATE OR REPLACE FUNCTION fn_persons_kv_store_upsert()
577
+ -- KV_STORE trigger for ontologies
578
+ -- Trigger function to maintain KV_STORE for ontologies
579
+ CREATE OR REPLACE FUNCTION fn_ontologies_kv_store_upsert()
388
580
  RETURNS TRIGGER AS $$
389
581
  BEGIN
390
582
  IF (TG_OP = 'DELETE') THEN
@@ -404,8 +596,8 @@ BEGIN
404
596
  graph_edges,
405
597
  updated_at
406
598
  ) VALUES (
407
- NEW.name, -- Use name as entity_key (json_schema_extra)
408
- 'persons',
599
+ NEW.id::VARCHAR,
600
+ 'ontologies',
409
601
  NEW.id,
410
602
  NEW.tenant_id,
411
603
  NEW.user_id,
@@ -427,26 +619,29 @@ END;
427
619
  $$ LANGUAGE plpgsql;
428
620
 
429
621
  -- Create trigger
430
- DROP TRIGGER IF EXISTS trg_persons_kv_store ON persons;
431
- CREATE TRIGGER trg_persons_kv_store
432
- AFTER INSERT OR UPDATE OR DELETE ON persons
433
- FOR EACH ROW EXECUTE FUNCTION fn_persons_kv_store_upsert();
622
+ DROP TRIGGER IF EXISTS trg_ontologies_kv_store ON ontologies;
623
+ CREATE TRIGGER trg_ontologies_kv_store
624
+ AFTER INSERT OR UPDATE OR DELETE ON ontologies
625
+ FOR EACH ROW EXECUTE FUNCTION fn_ontologies_kv_store_upsert();
434
626
 
435
627
  -- ======================================================================
436
- -- RESOURCES (Model: Resource)
628
+ -- ONTOLOGY_CONFIGS (Model: OntologyConfig)
437
629
  -- ======================================================================
438
630
 
439
- CREATE TABLE IF NOT EXISTS resources (
631
+ CREATE TABLE IF NOT EXISTS ontology_configs (
440
632
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
441
633
  tenant_id VARCHAR(100) NOT NULL,
442
634
  user_id VARCHAR(256),
443
635
  name VARCHAR(256) NOT NULL,
444
- uri VARCHAR(256),
445
- ordinal INTEGER,
446
- content TEXT,
447
- timestamp TIMESTAMP,
448
- category VARCHAR(256),
449
- related_entities JSONB DEFAULT '{}'::jsonb,
636
+ agent_schema_id VARCHAR(256) NOT NULL,
637
+ description TEXT,
638
+ mime_type_pattern VARCHAR(256),
639
+ uri_pattern VARCHAR(256),
640
+ tag_filter TEXT[],
641
+ priority INTEGER,
642
+ enabled BOOLEAN,
643
+ provider_name VARCHAR(256),
644
+ model_name VARCHAR(256),
450
645
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
451
646
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
452
647
  deleted_at TIMESTAMP,
@@ -455,16 +650,16 @@ CREATE TABLE IF NOT EXISTS resources (
455
650
  tags TEXT[] DEFAULT ARRAY[]::TEXT[]
456
651
  );
457
652
 
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);
653
+ CREATE INDEX idx_ontology_configs_tenant ON ontology_configs (tenant_id);
654
+ CREATE INDEX idx_ontology_configs_user ON ontology_configs (user_id);
655
+ CREATE INDEX idx_ontology_configs_graph_edges ON ontology_configs USING GIN (graph_edges);
656
+ CREATE INDEX idx_ontology_configs_metadata ON ontology_configs USING GIN (metadata);
657
+ CREATE INDEX idx_ontology_configs_tags ON ontology_configs USING GIN (tags);
463
658
 
464
- -- Embeddings for resources
465
- CREATE TABLE IF NOT EXISTS embeddings_resources (
659
+ -- Embeddings for ontology_configs
660
+ CREATE TABLE IF NOT EXISTS embeddings_ontology_configs (
466
661
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
467
- entity_id UUID NOT NULL REFERENCES resources(id) ON DELETE CASCADE,
662
+ entity_id UUID NOT NULL REFERENCES ontology_configs(id) ON DELETE CASCADE,
468
663
  field_name VARCHAR(100) NOT NULL,
469
664
  provider VARCHAR(50) NOT NULL DEFAULT 'openai',
470
665
  model VARCHAR(100) NOT NULL DEFAULT 'text-embedding-3-small',
@@ -477,19 +672,19 @@ CREATE TABLE IF NOT EXISTS embeddings_resources (
477
672
  );
478
673
 
479
674
  -- Index for entity lookup (get all embeddings for entity)
480
- CREATE INDEX IF NOT EXISTS idx_embeddings_resources_entity ON embeddings_resources (entity_id);
675
+ CREATE INDEX idx_embeddings_ontology_configs_entity ON embeddings_ontology_configs (entity_id);
481
676
 
482
677
  -- Index for field + provider lookup
483
- CREATE INDEX IF NOT EXISTS idx_embeddings_resources_field_provider ON embeddings_resources (field_name, provider);
678
+ CREATE INDEX idx_embeddings_ontology_configs_field_provider ON embeddings_ontology_configs (field_name, provider);
484
679
 
485
680
  -- HNSW index for vector similarity search (created in background)
486
681
  -- 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
682
+ -- CREATE INDEX idx_embeddings_ontology_configs_vector_hnsw ON embeddings_ontology_configs
488
683
  -- USING hnsw (embedding vector_cosine_ops);
489
684
 
490
- -- KV_STORE trigger for resources
491
- -- Trigger function to maintain KV_STORE for resources
492
- CREATE OR REPLACE FUNCTION fn_resources_kv_store_upsert()
685
+ -- KV_STORE trigger for ontology_configs
686
+ -- Trigger function to maintain KV_STORE for ontology_configs
687
+ CREATE OR REPLACE FUNCTION fn_ontology_configs_kv_store_upsert()
493
688
  RETURNS TRIGGER AS $$
494
689
  BEGIN
495
690
  IF (TG_OP = 'DELETE') THEN
@@ -509,8 +704,8 @@ BEGIN
509
704
  graph_edges,
510
705
  updated_at
511
706
  ) VALUES (
512
- NEW.name, -- Use name as entity_key (json_schema_extra)
513
- 'resources',
707
+ NEW.id::VARCHAR,
708
+ 'ontology_configs',
514
709
  NEW.id,
515
710
  NEW.tenant_id,
516
711
  NEW.user_id,
@@ -532,22 +727,26 @@ END;
532
727
  $$ LANGUAGE plpgsql;
533
728
 
534
729
  -- Create trigger
535
- DROP TRIGGER IF EXISTS trg_resources_kv_store ON resources;
536
- CREATE TRIGGER trg_resources_kv_store
537
- AFTER INSERT OR UPDATE OR DELETE ON resources
538
- FOR EACH ROW EXECUTE FUNCTION fn_resources_kv_store_upsert();
730
+ DROP TRIGGER IF EXISTS trg_ontology_configs_kv_store ON ontology_configs;
731
+ CREATE TRIGGER trg_ontology_configs_kv_store
732
+ AFTER INSERT OR UPDATE OR DELETE ON ontology_configs
733
+ FOR EACH ROW EXECUTE FUNCTION fn_ontology_configs_kv_store_upsert();
539
734
 
540
735
  -- ======================================================================
541
- -- MESSAGES (Model: Message)
736
+ -- RESOURCES (Model: Resource)
542
737
  -- ======================================================================
543
738
 
544
- CREATE TABLE IF NOT EXISTS messages (
739
+ CREATE TABLE IF NOT EXISTS resources (
545
740
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
546
741
  tenant_id VARCHAR(100) NOT NULL,
547
742
  user_id VARCHAR(256),
548
- content TEXT NOT NULL,
549
- message_type TEXT,
550
- session_id TEXT,
743
+ name VARCHAR(256),
744
+ uri VARCHAR(256),
745
+ ordinal INTEGER,
746
+ content TEXT,
747
+ timestamp TIMESTAMP,
748
+ category VARCHAR(256),
749
+ related_entities JSONB DEFAULT '{}'::jsonb,
551
750
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
552
751
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
553
752
  deleted_at TIMESTAMP,
@@ -556,16 +755,16 @@ CREATE TABLE IF NOT EXISTS messages (
556
755
  tags TEXT[] DEFAULT ARRAY[]::TEXT[]
557
756
  );
558
757
 
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);
758
+ CREATE INDEX idx_resources_tenant ON resources (tenant_id);
759
+ CREATE INDEX idx_resources_user ON resources (user_id);
760
+ CREATE INDEX idx_resources_graph_edges ON resources USING GIN (graph_edges);
761
+ CREATE INDEX idx_resources_metadata ON resources USING GIN (metadata);
762
+ CREATE INDEX idx_resources_tags ON resources USING GIN (tags);
564
763
 
565
- -- Embeddings for messages
566
- CREATE TABLE IF NOT EXISTS embeddings_messages (
764
+ -- Embeddings for resources
765
+ CREATE TABLE IF NOT EXISTS embeddings_resources (
567
766
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
568
- entity_id UUID NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
767
+ entity_id UUID NOT NULL REFERENCES resources(id) ON DELETE CASCADE,
569
768
  field_name VARCHAR(100) NOT NULL,
570
769
  provider VARCHAR(50) NOT NULL DEFAULT 'openai',
571
770
  model VARCHAR(100) NOT NULL DEFAULT 'text-embedding-3-small',
@@ -578,19 +777,19 @@ CREATE TABLE IF NOT EXISTS embeddings_messages (
578
777
  );
579
778
 
580
779
  -- Index for entity lookup (get all embeddings for entity)
581
- CREATE INDEX IF NOT EXISTS idx_embeddings_messages_entity ON embeddings_messages (entity_id);
780
+ CREATE INDEX idx_embeddings_resources_entity ON embeddings_resources (entity_id);
582
781
 
583
782
  -- Index for field + provider lookup
584
- CREATE INDEX IF NOT EXISTS idx_embeddings_messages_field_provider ON embeddings_messages (field_name, provider);
783
+ CREATE INDEX idx_embeddings_resources_field_provider ON embeddings_resources (field_name, provider);
585
784
 
586
785
  -- HNSW index for vector similarity search (created in background)
587
786
  -- 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
787
+ -- CREATE INDEX idx_embeddings_resources_vector_hnsw ON embeddings_resources
589
788
  -- USING hnsw (embedding vector_cosine_ops);
590
789
 
591
- -- KV_STORE trigger for messages
592
- -- Trigger function to maintain KV_STORE for messages
593
- CREATE OR REPLACE FUNCTION fn_messages_kv_store_upsert()
790
+ -- KV_STORE trigger for resources
791
+ -- Trigger function to maintain KV_STORE for resources
792
+ CREATE OR REPLACE FUNCTION fn_resources_kv_store_upsert()
594
793
  RETURNS TRIGGER AS $$
595
794
  BEGIN
596
795
  IF (TG_OP = 'DELETE') THEN
@@ -610,8 +809,8 @@ BEGIN
610
809
  graph_edges,
611
810
  updated_at
612
811
  ) VALUES (
613
- NEW.id::VARCHAR, -- Messages use id as entity_key (no name field)
614
- 'messages',
812
+ NEW.name::VARCHAR,
813
+ 'resources',
615
814
  NEW.id,
616
815
  NEW.tenant_id,
617
816
  NEW.user_id,
@@ -633,26 +832,25 @@ END;
633
832
  $$ LANGUAGE plpgsql;
634
833
 
635
834
  -- Create trigger
636
- DROP TRIGGER IF EXISTS trg_messages_kv_store ON messages;
637
- CREATE TRIGGER trg_messages_kv_store
638
- AFTER INSERT OR UPDATE OR DELETE ON messages
639
- FOR EACH ROW EXECUTE FUNCTION fn_messages_kv_store_upsert();
835
+ DROP TRIGGER IF EXISTS trg_resources_kv_store ON resources;
836
+ CREATE TRIGGER trg_resources_kv_store
837
+ AFTER INSERT OR UPDATE OR DELETE ON resources
838
+ FOR EACH ROW EXECUTE FUNCTION fn_resources_kv_store_upsert();
640
839
 
641
840
  -- ======================================================================
642
- -- FILES (Model: File)
841
+ -- SCHEMAS (Model: Schema)
643
842
  -- ======================================================================
644
843
 
645
- CREATE TABLE IF NOT EXISTS files (
844
+ CREATE TABLE IF NOT EXISTS schemas (
646
845
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
647
846
  tenant_id VARCHAR(100) NOT NULL,
648
847
  user_id VARCHAR(256),
649
848
  name VARCHAR(256) NOT NULL,
650
- uri VARCHAR(256) NOT NULL,
651
849
  content TEXT,
652
- timestamp VARCHAR(256),
653
- size_bytes INTEGER,
654
- mime_type VARCHAR(256),
655
- processing_status VARCHAR(256),
850
+ spec JSONB NOT NULL,
851
+ category VARCHAR(256),
852
+ provider_configs JSONB DEFAULT '{}'::jsonb,
853
+ embedding_fields TEXT[] DEFAULT ARRAY[]::TEXT[],
656
854
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
657
855
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
658
856
  deleted_at TIMESTAMP,
@@ -661,16 +859,16 @@ CREATE TABLE IF NOT EXISTS files (
661
859
  tags TEXT[] DEFAULT ARRAY[]::TEXT[]
662
860
  );
663
861
 
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);
862
+ CREATE INDEX idx_schemas_tenant ON schemas (tenant_id);
863
+ CREATE INDEX idx_schemas_user ON schemas (user_id);
864
+ CREATE INDEX idx_schemas_graph_edges ON schemas USING GIN (graph_edges);
865
+ CREATE INDEX idx_schemas_metadata ON schemas USING GIN (metadata);
866
+ CREATE INDEX idx_schemas_tags ON schemas USING GIN (tags);
669
867
 
670
- -- Embeddings for files
671
- CREATE TABLE IF NOT EXISTS embeddings_files (
868
+ -- Embeddings for schemas
869
+ CREATE TABLE IF NOT EXISTS embeddings_schemas (
672
870
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
673
- entity_id UUID NOT NULL REFERENCES files(id) ON DELETE CASCADE,
871
+ entity_id UUID NOT NULL REFERENCES schemas(id) ON DELETE CASCADE,
674
872
  field_name VARCHAR(100) NOT NULL,
675
873
  provider VARCHAR(50) NOT NULL DEFAULT 'openai',
676
874
  model VARCHAR(100) NOT NULL DEFAULT 'text-embedding-3-small',
@@ -683,19 +881,19 @@ CREATE TABLE IF NOT EXISTS embeddings_files (
683
881
  );
684
882
 
685
883
  -- Index for entity lookup (get all embeddings for entity)
686
- CREATE INDEX IF NOT EXISTS idx_embeddings_files_entity ON embeddings_files (entity_id);
884
+ CREATE INDEX idx_embeddings_schemas_entity ON embeddings_schemas (entity_id);
687
885
 
688
886
  -- Index for field + provider lookup
689
- CREATE INDEX IF NOT EXISTS idx_embeddings_files_field_provider ON embeddings_files (field_name, provider);
887
+ CREATE INDEX idx_embeddings_schemas_field_provider ON embeddings_schemas (field_name, provider);
690
888
 
691
889
  -- HNSW index for vector similarity search (created in background)
692
890
  -- 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
891
+ -- CREATE INDEX idx_embeddings_schemas_vector_hnsw ON embeddings_schemas
694
892
  -- USING hnsw (embedding vector_cosine_ops);
695
893
 
696
- -- KV_STORE trigger for files
697
- -- Trigger function to maintain KV_STORE for files
698
- CREATE OR REPLACE FUNCTION fn_files_kv_store_upsert()
894
+ -- KV_STORE trigger for schemas
895
+ -- Trigger function to maintain KV_STORE for schemas
896
+ CREATE OR REPLACE FUNCTION fn_schemas_kv_store_upsert()
699
897
  RETURNS TRIGGER AS $$
700
898
  BEGIN
701
899
  IF (TG_OP = 'DELETE') THEN
@@ -715,8 +913,8 @@ BEGIN
715
913
  graph_edges,
716
914
  updated_at
717
915
  ) VALUES (
718
- NEW.name, -- Use name as entity_key (json_schema_extra)
719
- 'files',
916
+ NEW.id::VARCHAR,
917
+ 'schemas',
720
918
  NEW.id,
721
919
  NEW.tenant_id,
722
920
  NEW.user_id,
@@ -738,28 +936,28 @@ END;
738
936
  $$ LANGUAGE plpgsql;
739
937
 
740
938
  -- Create trigger
741
- DROP TRIGGER IF EXISTS trg_files_kv_store ON files;
742
- CREATE TRIGGER trg_files_kv_store
743
- AFTER INSERT OR UPDATE OR DELETE ON files
744
- FOR EACH ROW EXECUTE FUNCTION fn_files_kv_store_upsert();
939
+ DROP TRIGGER IF EXISTS trg_schemas_kv_store ON schemas;
940
+ CREATE TRIGGER trg_schemas_kv_store
941
+ AFTER INSERT OR UPDATE OR DELETE ON schemas
942
+ FOR EACH ROW EXECUTE FUNCTION fn_schemas_kv_store_upsert();
745
943
 
746
944
  -- ======================================================================
747
- -- ONTOLOGIES (Model: Ontology)
945
+ -- SESSIONS (Model: Session)
748
946
  -- ======================================================================
749
947
 
750
- CREATE TABLE IF NOT EXISTS ontologies (
948
+ CREATE TABLE IF NOT EXISTS sessions (
751
949
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
752
950
  tenant_id VARCHAR(100) NOT NULL,
753
951
  user_id VARCHAR(256),
754
952
  name VARCHAR(256) NOT NULL,
755
- file_id TEXT NOT NULL,
756
- agent_schema_id VARCHAR(256) NOT NULL,
757
- provider_name VARCHAR(256) NOT NULL,
758
- model_name VARCHAR(256) NOT NULL,
759
- extracted_data JSONB NOT NULL,
760
- confidence_score DOUBLE PRECISION,
761
- extraction_timestamp VARCHAR(256),
762
- embedding_text TEXT,
953
+ mode TEXT,
954
+ description TEXT,
955
+ original_trace_id VARCHAR(256),
956
+ settings_overrides JSONB,
957
+ prompt TEXT,
958
+ agent_schema_uri VARCHAR(256),
959
+ message_count INTEGER,
960
+ total_tokens INTEGER,
763
961
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
764
962
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
765
963
  deleted_at TIMESTAMP,
@@ -768,15 +966,41 @@ CREATE TABLE IF NOT EXISTS ontologies (
768
966
  tags TEXT[] DEFAULT ARRAY[]::TEXT[]
769
967
  );
770
968
 
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);
969
+ CREATE INDEX idx_sessions_tenant ON sessions (tenant_id);
970
+ CREATE INDEX idx_sessions_user ON sessions (user_id);
971
+ CREATE INDEX idx_sessions_graph_edges ON sessions USING GIN (graph_edges);
972
+ CREATE INDEX idx_sessions_metadata ON sessions USING GIN (metadata);
973
+ CREATE INDEX idx_sessions_tags ON sessions USING GIN (tags);
776
974
 
777
- -- KV_STORE trigger for ontologies
778
- -- Trigger function to maintain KV_STORE for ontologies
779
- CREATE OR REPLACE FUNCTION fn_ontologies_kv_store_upsert()
975
+ -- Embeddings for sessions
976
+ CREATE TABLE IF NOT EXISTS embeddings_sessions (
977
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
978
+ entity_id UUID NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
979
+ field_name VARCHAR(100) NOT NULL,
980
+ provider VARCHAR(50) NOT NULL DEFAULT 'openai',
981
+ model VARCHAR(100) NOT NULL DEFAULT 'text-embedding-3-small',
982
+ embedding vector(1536) NOT NULL,
983
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
984
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
985
+
986
+ -- Unique: one embedding per entity per field per provider
987
+ UNIQUE (entity_id, field_name, provider)
988
+ );
989
+
990
+ -- Index for entity lookup (get all embeddings for entity)
991
+ CREATE INDEX idx_embeddings_sessions_entity ON embeddings_sessions (entity_id);
992
+
993
+ -- Index for field + provider lookup
994
+ CREATE INDEX idx_embeddings_sessions_field_provider ON embeddings_sessions (field_name, provider);
995
+
996
+ -- HNSW index for vector similarity search (created in background)
997
+ -- Note: This will be created by background thread after data load
998
+ -- CREATE INDEX idx_embeddings_sessions_vector_hnsw ON embeddings_sessions
999
+ -- USING hnsw (embedding vector_cosine_ops);
1000
+
1001
+ -- KV_STORE trigger for sessions
1002
+ -- Trigger function to maintain KV_STORE for sessions
1003
+ CREATE OR REPLACE FUNCTION fn_sessions_kv_store_upsert()
780
1004
  RETURNS TRIGGER AS $$
781
1005
  BEGIN
782
1006
  IF (TG_OP = 'DELETE') THEN
@@ -796,8 +1020,8 @@ BEGIN
796
1020
  graph_edges,
797
1021
  updated_at
798
1022
  ) VALUES (
799
- NEW.name, -- Use name as entity_key (json_schema_extra)
800
- 'ontologies',
1023
+ NEW.name::VARCHAR,
1024
+ 'sessions',
801
1025
  NEW.id,
802
1026
  NEW.tenant_id,
803
1027
  NEW.user_id,
@@ -819,29 +1043,22 @@ END;
819
1043
  $$ LANGUAGE plpgsql;
820
1044
 
821
1045
  -- Create trigger
822
- DROP TRIGGER IF EXISTS trg_ontologies_kv_store ON ontologies;
823
- CREATE TRIGGER trg_ontologies_kv_store
824
- AFTER INSERT OR UPDATE OR DELETE ON ontologies
825
- FOR EACH ROW EXECUTE FUNCTION fn_ontologies_kv_store_upsert();
1046
+ DROP TRIGGER IF EXISTS trg_sessions_kv_store ON sessions;
1047
+ CREATE TRIGGER trg_sessions_kv_store
1048
+ AFTER INSERT OR UPDATE OR DELETE ON sessions
1049
+ FOR EACH ROW EXECUTE FUNCTION fn_sessions_kv_store_upsert();
826
1050
 
827
1051
  -- ======================================================================
828
- -- ONTOLOGY_CONFIGS (Model: OntologyConfig)
1052
+ -- SHARED_SESSIONS (Model: SharedSession)
829
1053
  -- ======================================================================
830
1054
 
831
- CREATE TABLE IF NOT EXISTS ontology_configs (
1055
+ CREATE TABLE IF NOT EXISTS shared_sessions (
832
1056
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
833
1057
  tenant_id VARCHAR(100) NOT NULL,
834
1058
  user_id VARCHAR(256),
835
- name VARCHAR(256) NOT NULL,
836
- agent_schema_id VARCHAR(256) NOT NULL,
837
- description TEXT,
838
- mime_type_pattern VARCHAR(256),
839
- uri_pattern VARCHAR(256),
840
- tag_filter TEXT[],
841
- priority INTEGER,
842
- enabled BOOLEAN,
843
- provider_name VARCHAR(256),
844
- model_name VARCHAR(256),
1059
+ session_id VARCHAR(256) NOT NULL,
1060
+ owner_user_id VARCHAR(256) NOT NULL,
1061
+ shared_with_user_id VARCHAR(256) NOT NULL,
845
1062
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
846
1063
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
847
1064
  deleted_at TIMESTAMP,
@@ -850,41 +1067,15 @@ CREATE TABLE IF NOT EXISTS ontology_configs (
850
1067
  tags TEXT[] DEFAULT ARRAY[]::TEXT[]
851
1068
  );
852
1069
 
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);
858
-
859
- -- Embeddings for ontology_configs
860
- CREATE TABLE IF NOT EXISTS embeddings_ontology_configs (
861
- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
862
- entity_id UUID NOT NULL REFERENCES ontology_configs(id) ON DELETE CASCADE,
863
- field_name VARCHAR(100) NOT NULL,
864
- provider VARCHAR(50) NOT NULL DEFAULT 'openai',
865
- model VARCHAR(100) NOT NULL DEFAULT 'text-embedding-3-small',
866
- embedding vector(1536) NOT NULL,
867
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
868
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
869
-
870
- -- Unique: one embedding per entity per field per provider
871
- UNIQUE (entity_id, field_name, provider)
872
- );
873
-
874
- -- 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);
876
-
877
- -- Index for field + provider lookup
878
- CREATE INDEX IF NOT EXISTS idx_embeddings_ontology_configs_field_provider ON embeddings_ontology_configs (field_name, provider);
1070
+ CREATE INDEX idx_shared_sessions_tenant ON shared_sessions (tenant_id);
1071
+ CREATE INDEX idx_shared_sessions_user ON shared_sessions (user_id);
1072
+ CREATE INDEX idx_shared_sessions_graph_edges ON shared_sessions USING GIN (graph_edges);
1073
+ CREATE INDEX idx_shared_sessions_metadata ON shared_sessions USING GIN (metadata);
1074
+ CREATE INDEX idx_shared_sessions_tags ON shared_sessions USING GIN (tags);
879
1075
 
880
- -- HNSW index for vector similarity search (created in background)
881
- -- 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
883
- -- USING hnsw (embedding vector_cosine_ops);
884
-
885
- -- KV_STORE trigger for ontology_configs
886
- -- Trigger function to maintain KV_STORE for ontology_configs
887
- CREATE OR REPLACE FUNCTION fn_ontology_configs_kv_store_upsert()
1076
+ -- KV_STORE trigger for shared_sessions
1077
+ -- Trigger function to maintain KV_STORE for shared_sessions
1078
+ CREATE OR REPLACE FUNCTION fn_shared_sessions_kv_store_upsert()
888
1079
  RETURNS TRIGGER AS $$
889
1080
  BEGIN
890
1081
  IF (TG_OP = 'DELETE') THEN
@@ -904,8 +1095,8 @@ BEGIN
904
1095
  graph_edges,
905
1096
  updated_at
906
1097
  ) VALUES (
907
- NEW.name, -- Use name as entity_key (json_schema_extra)
908
- 'ontology_configs',
1098
+ NEW.id::VARCHAR,
1099
+ 'shared_sessions',
909
1100
  NEW.id,
910
1101
  NEW.tenant_id,
911
1102
  NEW.user_id,
@@ -927,25 +1118,30 @@ END;
927
1118
  $$ LANGUAGE plpgsql;
928
1119
 
929
1120
  -- Create trigger
930
- DROP TRIGGER IF EXISTS trg_ontology_configs_kv_store ON ontology_configs;
931
- CREATE TRIGGER trg_ontology_configs_kv_store
932
- AFTER INSERT OR UPDATE OR DELETE ON ontology_configs
933
- FOR EACH ROW EXECUTE FUNCTION fn_ontology_configs_kv_store_upsert();
1121
+ DROP TRIGGER IF EXISTS trg_shared_sessions_kv_store ON shared_sessions;
1122
+ CREATE TRIGGER trg_shared_sessions_kv_store
1123
+ AFTER INSERT OR UPDATE OR DELETE ON shared_sessions
1124
+ FOR EACH ROW EXECUTE FUNCTION fn_shared_sessions_kv_store_upsert();
934
1125
 
935
1126
  -- ======================================================================
936
- -- SCHEMAS (Model: Schema)
1127
+ -- USERS (Model: User)
937
1128
  -- ======================================================================
938
1129
 
939
- CREATE TABLE IF NOT EXISTS schemas (
1130
+ CREATE TABLE IF NOT EXISTS users (
940
1131
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
941
1132
  tenant_id VARCHAR(100) NOT NULL,
942
1133
  user_id VARCHAR(256),
943
1134
  name VARCHAR(256) NOT NULL,
944
- content TEXT,
945
- spec JSONB NOT NULL,
946
- category VARCHAR(256),
947
- provider_configs JSONB DEFAULT '{}'::jsonb,
948
- embedding_fields TEXT[] DEFAULT ARRAY[]::TEXT[],
1135
+ email VARCHAR(256),
1136
+ role VARCHAR(256),
1137
+ tier TEXT,
1138
+ anonymous_ids TEXT[] DEFAULT ARRAY[]::TEXT[],
1139
+ sec_policy JSONB DEFAULT '{}'::jsonb,
1140
+ summary TEXT,
1141
+ interests TEXT[] DEFAULT ARRAY[]::TEXT[],
1142
+ preferred_topics TEXT[] DEFAULT ARRAY[]::TEXT[],
1143
+ activity_level VARCHAR(256),
1144
+ last_active_at TIMESTAMP,
949
1145
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
950
1146
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
951
1147
  deleted_at TIMESTAMP,
@@ -954,16 +1150,16 @@ CREATE TABLE IF NOT EXISTS schemas (
954
1150
  tags TEXT[] DEFAULT ARRAY[]::TEXT[]
955
1151
  );
956
1152
 
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);
1153
+ CREATE INDEX idx_users_tenant ON users (tenant_id);
1154
+ CREATE INDEX idx_users_user ON users (user_id);
1155
+ CREATE INDEX idx_users_graph_edges ON users USING GIN (graph_edges);
1156
+ CREATE INDEX idx_users_metadata ON users USING GIN (metadata);
1157
+ CREATE INDEX idx_users_tags ON users USING GIN (tags);
962
1158
 
963
- -- Embeddings for schemas
964
- CREATE TABLE IF NOT EXISTS embeddings_schemas (
1159
+ -- Embeddings for users
1160
+ CREATE TABLE IF NOT EXISTS embeddings_users (
965
1161
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
966
- entity_id UUID NOT NULL REFERENCES schemas(id) ON DELETE CASCADE,
1162
+ entity_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
967
1163
  field_name VARCHAR(100) NOT NULL,
968
1164
  provider VARCHAR(50) NOT NULL DEFAULT 'openai',
969
1165
  model VARCHAR(100) NOT NULL DEFAULT 'text-embedding-3-small',
@@ -976,19 +1172,19 @@ CREATE TABLE IF NOT EXISTS embeddings_schemas (
976
1172
  );
977
1173
 
978
1174
  -- Index for entity lookup (get all embeddings for entity)
979
- CREATE INDEX IF NOT EXISTS idx_embeddings_schemas_entity ON embeddings_schemas (entity_id);
1175
+ CREATE INDEX idx_embeddings_users_entity ON embeddings_users (entity_id);
980
1176
 
981
1177
  -- Index for field + provider lookup
982
- CREATE INDEX IF NOT EXISTS idx_embeddings_schemas_field_provider ON embeddings_schemas (field_name, provider);
1178
+ CREATE INDEX idx_embeddings_users_field_provider ON embeddings_users (field_name, provider);
983
1179
 
984
1180
  -- HNSW index for vector similarity search (created in background)
985
1181
  -- 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
1182
+ -- CREATE INDEX idx_embeddings_users_vector_hnsw ON embeddings_users
987
1183
  -- USING hnsw (embedding vector_cosine_ops);
988
1184
 
989
- -- KV_STORE trigger for schemas
990
- -- Trigger function to maintain KV_STORE for schemas
991
- CREATE OR REPLACE FUNCTION fn_schemas_kv_store_upsert()
1185
+ -- KV_STORE trigger for users
1186
+ -- Trigger function to maintain KV_STORE for users
1187
+ CREATE OR REPLACE FUNCTION fn_users_kv_store_upsert()
992
1188
  RETURNS TRIGGER AS $$
993
1189
  BEGIN
994
1190
  IF (TG_OP = 'DELETE') THEN
@@ -1008,8 +1204,8 @@ BEGIN
1008
1204
  graph_edges,
1009
1205
  updated_at
1010
1206
  ) VALUES (
1011
- NEW.name, -- Use name as entity_key (json_schema_extra)
1012
- 'schemas',
1207
+ NEW.name::VARCHAR,
1208
+ 'users',
1013
1209
  NEW.id,
1014
1210
  NEW.tenant_id,
1015
1211
  NEW.user_id,
@@ -1031,10 +1227,10 @@ END;
1031
1227
  $$ LANGUAGE plpgsql;
1032
1228
 
1033
1229
  -- Create trigger
1034
- DROP TRIGGER IF EXISTS trg_schemas_kv_store ON schemas;
1035
- CREATE TRIGGER trg_schemas_kv_store
1036
- AFTER INSERT OR UPDATE OR DELETE ON schemas
1037
- FOR EACH ROW EXECUTE FUNCTION fn_schemas_kv_store_upsert();
1230
+ DROP TRIGGER IF EXISTS trg_users_kv_store ON users;
1231
+ CREATE TRIGGER trg_users_kv_store
1232
+ AFTER INSERT OR UPDATE OR DELETE ON users
1233
+ FOR EACH ROW EXECUTE FUNCTION fn_users_kv_store_upsert();
1038
1234
 
1039
1235
  -- ============================================================================
1040
1236
  -- RECORD MIGRATION
@@ -1049,154 +1245,22 @@ SET applied_at = CURRENT_TIMESTAMP,
1049
1245
  DO $$
1050
1246
  BEGIN
1051
1247
  RAISE NOTICE '============================================================';
1052
- RAISE NOTICE 'REM Model Schema Applied: 10 tables';
1248
+ RAISE NOTICE 'REM Model Schema Applied: 12 tables';
1053
1249
  RAISE NOTICE '============================================================';
1250
+ RAISE NOTICE ' ✓ feedbacks';
1054
1251
  RAISE NOTICE ' ✓ files (1 embeddable fields)';
1055
1252
  RAISE NOTICE ' ✓ image_resources (1 embeddable fields)';
1056
1253
  RAISE NOTICE ' ✓ messages (1 embeddable fields)';
1057
1254
  RAISE NOTICE ' ✓ moments (1 embeddable fields)';
1058
1255
  RAISE NOTICE ' ✓ ontologies';
1059
1256
  RAISE NOTICE ' ✓ ontology_configs (1 embeddable fields)';
1060
- RAISE NOTICE ' ✓ persons';
1061
1257
  RAISE NOTICE ' ✓ resources (1 embeddable fields)';
1062
1258
  RAISE NOTICE ' ✓ schemas (1 embeddable fields)';
1259
+ RAISE NOTICE ' ✓ sessions (1 embeddable fields)';
1260
+ RAISE NOTICE ' ✓ shared_sessions';
1063
1261
  RAISE NOTICE ' ✓ users (1 embeddable fields)';
1064
1262
  RAISE NOTICE '';
1065
1263
  RAISE NOTICE 'Next: Run background indexes if needed';
1066
1264
  RAISE NOTICE ' rem db migrate --background-indexes';
1067
1265
  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.';
1266
+ END $$;