remdb 0.3.114__py3-none-any.whl → 0.3.172__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 (83) hide show
  1. rem/agentic/agents/__init__.py +16 -0
  2. rem/agentic/agents/agent_manager.py +311 -0
  3. rem/agentic/agents/sse_simulator.py +2 -0
  4. rem/agentic/context.py +103 -5
  5. rem/agentic/context_builder.py +36 -9
  6. rem/agentic/mcp/tool_wrapper.py +161 -18
  7. rem/agentic/otel/setup.py +1 -0
  8. rem/agentic/providers/phoenix.py +371 -108
  9. rem/agentic/providers/pydantic_ai.py +172 -30
  10. rem/agentic/schema.py +8 -4
  11. rem/api/deps.py +3 -5
  12. rem/api/main.py +26 -4
  13. rem/api/mcp_router/resources.py +15 -10
  14. rem/api/mcp_router/server.py +11 -3
  15. rem/api/mcp_router/tools.py +418 -4
  16. rem/api/middleware/tracking.py +5 -5
  17. rem/api/routers/admin.py +218 -1
  18. rem/api/routers/auth.py +349 -6
  19. rem/api/routers/chat/completions.py +255 -7
  20. rem/api/routers/chat/models.py +81 -7
  21. rem/api/routers/chat/otel_utils.py +33 -0
  22. rem/api/routers/chat/sse_events.py +17 -1
  23. rem/api/routers/chat/streaming.py +126 -19
  24. rem/api/routers/feedback.py +134 -14
  25. rem/api/routers/messages.py +24 -15
  26. rem/api/routers/query.py +6 -3
  27. rem/auth/__init__.py +13 -3
  28. rem/auth/jwt.py +352 -0
  29. rem/auth/middleware.py +115 -10
  30. rem/auth/providers/__init__.py +4 -1
  31. rem/auth/providers/email.py +215 -0
  32. rem/cli/commands/README.md +42 -0
  33. rem/cli/commands/cluster.py +617 -168
  34. rem/cli/commands/configure.py +4 -7
  35. rem/cli/commands/db.py +66 -22
  36. rem/cli/commands/experiments.py +468 -76
  37. rem/cli/commands/schema.py +6 -5
  38. rem/cli/commands/session.py +336 -0
  39. rem/cli/dreaming.py +2 -2
  40. rem/cli/main.py +2 -0
  41. rem/config.py +8 -1
  42. rem/models/core/experiment.py +58 -14
  43. rem/models/entities/__init__.py +4 -0
  44. rem/models/entities/ontology.py +1 -1
  45. rem/models/entities/ontology_config.py +1 -1
  46. rem/models/entities/subscriber.py +175 -0
  47. rem/models/entities/user.py +1 -0
  48. rem/schemas/agents/core/agent-builder.yaml +235 -0
  49. rem/schemas/agents/examples/contract-analyzer.yaml +1 -1
  50. rem/schemas/agents/examples/contract-extractor.yaml +1 -1
  51. rem/schemas/agents/examples/cv-parser.yaml +1 -1
  52. rem/services/__init__.py +3 -1
  53. rem/services/content/service.py +4 -3
  54. rem/services/email/__init__.py +10 -0
  55. rem/services/email/service.py +513 -0
  56. rem/services/email/templates.py +360 -0
  57. rem/services/phoenix/client.py +59 -18
  58. rem/services/postgres/README.md +38 -0
  59. rem/services/postgres/diff_service.py +127 -6
  60. rem/services/postgres/pydantic_to_sqlalchemy.py +45 -13
  61. rem/services/postgres/repository.py +5 -4
  62. rem/services/postgres/schema_generator.py +205 -4
  63. rem/services/session/compression.py +120 -50
  64. rem/services/session/reload.py +14 -7
  65. rem/services/user_service.py +41 -9
  66. rem/settings.py +442 -23
  67. rem/sql/migrations/001_install.sql +156 -0
  68. rem/sql/migrations/002_install_models.sql +1951 -88
  69. rem/sql/migrations/004_cache_system.sql +548 -0
  70. rem/sql/migrations/005_schema_update.sql +145 -0
  71. rem/utils/README.md +45 -0
  72. rem/utils/__init__.py +18 -0
  73. rem/utils/files.py +157 -1
  74. rem/utils/schema_loader.py +139 -10
  75. rem/utils/sql_paths.py +146 -0
  76. rem/utils/vision.py +1 -1
  77. rem/workers/__init__.py +3 -1
  78. rem/workers/db_listener.py +579 -0
  79. rem/workers/unlogged_maintainer.py +463 -0
  80. {remdb-0.3.114.dist-info → remdb-0.3.172.dist-info}/METADATA +218 -180
  81. {remdb-0.3.114.dist-info → remdb-0.3.172.dist-info}/RECORD +83 -68
  82. {remdb-0.3.114.dist-info → remdb-0.3.172.dist-info}/WHEEL +0 -0
  83. {remdb-0.3.114.dist-info → remdb-0.3.172.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,360 @@
1
+ """
2
+ Email Templates.
3
+
4
+ HTML email templates for transactional emails.
5
+ Uses inline CSS for maximum email client compatibility.
6
+
7
+ Downstream apps can customize by:
8
+ 1. Overriding COLORS dict
9
+ 2. Setting custom LOGO_URL and TAGLINE
10
+ 3. Creating custom templates using base_template()
11
+ """
12
+
13
+ from dataclasses import dataclass
14
+ from typing import Optional
15
+
16
+
17
+ # Default colors - override in downstream apps
18
+ COLORS = {
19
+ "background": "#F5F5F5",
20
+ "foreground": "#333333",
21
+ "primary": "#4A90D9",
22
+ "accent": "#5CB85C",
23
+ "card": "#FFFFFF",
24
+ "muted": "#6b7280",
25
+ "border": "#E0E0E0",
26
+ }
27
+
28
+ # Branding - override in downstream apps
29
+ LOGO_URL: str | None = None
30
+ APP_NAME = "REM"
31
+ TAGLINE = "Your AI-powered platform"
32
+ WEBSITE_URL = "https://rem.ai"
33
+ PRIVACY_URL = "https://rem.ai/privacy"
34
+ TERMS_URL = "https://rem.ai/terms"
35
+
36
+
37
+ @dataclass
38
+ class EmailTemplate:
39
+ """Email template with subject and HTML body."""
40
+
41
+ subject: str
42
+ html_body: str
43
+
44
+
45
+ def base_template(
46
+ content: str,
47
+ preheader: Optional[str] = None,
48
+ colors: dict | None = None,
49
+ logo_url: str | None = None,
50
+ app_name: str | None = None,
51
+ tagline: str | None = None,
52
+ website_url: str | None = None,
53
+ privacy_url: str | None = None,
54
+ terms_url: str | None = None,
55
+ ) -> str:
56
+ """
57
+ Base email template.
58
+
59
+ Args:
60
+ content: The main content HTML
61
+ preheader: Optional preview text shown in email clients
62
+ colors: Color overrides (merges with COLORS)
63
+ logo_url: Logo image URL
64
+ app_name: Application name
65
+ tagline: Footer tagline
66
+ website_url: Main website URL
67
+ privacy_url: Privacy policy URL
68
+ terms_url: Terms of service URL
69
+
70
+ Returns:
71
+ Complete HTML email
72
+ """
73
+ # Merge colors
74
+ c = {**COLORS, **(colors or {})}
75
+
76
+ # Use provided values or module defaults
77
+ logo = logo_url or LOGO_URL
78
+ name = app_name or APP_NAME
79
+ tag = tagline or TAGLINE
80
+ web = website_url or WEBSITE_URL
81
+ privacy = privacy_url or PRIVACY_URL
82
+ terms = terms_url or TERMS_URL
83
+
84
+ preheader_html = ""
85
+ if preheader:
86
+ preheader_html = f'''
87
+ <div style="display: none; max-height: 0; overflow: hidden;">
88
+ {preheader}
89
+ </div>
90
+ '''
91
+
92
+ logo_html = ""
93
+ if logo:
94
+ logo_html = f'''
95
+ <img src="{logo}" alt="{name}" width="40" height="40" style="display: block; margin: 0 auto 16px auto; border-radius: 8px;">
96
+ '''
97
+
98
+ return f'''<!DOCTYPE html>
99
+ <html lang="en">
100
+ <head>
101
+ <meta charset="UTF-8">
102
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
103
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
104
+ <title>{name}</title>
105
+ <!--[if mso]>
106
+ <style type="text/css">
107
+ body, table, td {{font-family: Arial, sans-serif !important;}}
108
+ </style>
109
+ <![endif]-->
110
+ </head>
111
+ <body style="margin: 0; padding: 0; background-color: {c['background']}; font-family: 'Georgia', 'Times New Roman', serif;">
112
+ {preheader_html}
113
+
114
+ <!-- Email Container -->
115
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background-color: {c['background']};">
116
+ <tr>
117
+ <td style="padding: 40px 20px;">
118
+ <!-- Inner Container -->
119
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="max-width: 560px; margin: 0 auto;">
120
+
121
+ <!-- Main Content Card -->
122
+ <tr>
123
+ <td style="background-color: {c['card']}; border-radius: 16px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);">
124
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
125
+ <tr>
126
+ <td style="padding: 40px;">
127
+ {content}
128
+ </td>
129
+ </tr>
130
+ </table>
131
+ </td>
132
+ </tr>
133
+
134
+ <!-- Footer -->
135
+ <tr>
136
+ <td style="padding: 32px 20px; text-align: center;">
137
+ {logo_html}
138
+
139
+ <!-- Tagline -->
140
+ <p style="margin: 0 0 8px 0; font-family: 'Georgia', serif; font-size: 14px; color: {c['muted']}; font-style: italic;">
141
+ {tag}
142
+ </p>
143
+
144
+ <!-- Footer Links -->
145
+ <p style="margin: 0; font-family: Arial, sans-serif; font-size: 12px; color: {c['muted']};">
146
+ <a href="{web}" style="color: {c['primary']}; text-decoration: none;">{name}</a>
147
+ &nbsp;&bull;&nbsp;
148
+ <a href="{privacy}" style="color: {c['primary']}; text-decoration: none;">Privacy</a>
149
+ &nbsp;&bull;&nbsp;
150
+ <a href="{terms}" style="color: {c['primary']}; text-decoration: none;">Terms</a>
151
+ </p>
152
+
153
+ <!-- Copyright -->
154
+ <p style="margin: 16px 0 0 0; font-family: Arial, sans-serif; font-size: 11px; color: {c['muted']};">
155
+ &copy; 2025 {name}. All rights reserved.
156
+ </p>
157
+ </td>
158
+ </tr>
159
+
160
+ </table>
161
+ </td>
162
+ </tr>
163
+ </table>
164
+ </body>
165
+ </html>'''
166
+
167
+
168
+ def login_code_template(
169
+ code: str,
170
+ email: str,
171
+ colors: dict | None = None,
172
+ app_name: str | None = None,
173
+ **kwargs,
174
+ ) -> EmailTemplate:
175
+ """
176
+ Generate a login code email template.
177
+
178
+ Args:
179
+ code: The 6-digit login code
180
+ email: The recipient's email address
181
+ colors: Color overrides
182
+ app_name: Application name
183
+ **kwargs: Additional arguments passed to base_template
184
+
185
+ Returns:
186
+ EmailTemplate with subject and HTML body
187
+ """
188
+ c = {**COLORS, **(colors or {})}
189
+ name = app_name or APP_NAME
190
+
191
+ # Format code with spaces for readability (e.g., "123 456")
192
+ formatted_code = f"{code[:3]} {code[3:]}" if len(code) == 6 else code
193
+
194
+ content = f'''
195
+ <!-- Greeting -->
196
+ <h1 style="margin: 0 0 8px 0; font-family: 'Arial', sans-serif; font-size: 24px; font-weight: 600; color: {c['foreground']};">
197
+ Hi there!
198
+ </h1>
199
+
200
+ <p style="margin: 0 0 24px 0; font-family: 'Georgia', serif; font-size: 16px; line-height: 1.6; color: {c['foreground']};">
201
+ Here's your login code for {name}. Enter this code to securely access your account.
202
+ </p>
203
+
204
+ <!-- Code Box -->
205
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin: 0 0 24px 0;">
206
+ <tr>
207
+ <td align="center">
208
+ <div style="
209
+ display: inline-block;
210
+ padding: 20px 40px;
211
+ background-color: {c['background']};
212
+ border: 2px solid {c['border']};
213
+ border-radius: 12px;
214
+ ">
215
+ <span style="
216
+ font-family: 'Courier New', monospace;
217
+ font-size: 32px;
218
+ font-weight: 700;
219
+ letter-spacing: 6px;
220
+ color: {c['foreground']};
221
+ ">{formatted_code}</span>
222
+ </div>
223
+ </td>
224
+ </tr>
225
+ </table>
226
+
227
+ <!-- Instructions -->
228
+ <p style="margin: 0 0 8px 0; font-family: 'Georgia', serif; font-size: 14px; line-height: 1.6; color: {c['muted']};">
229
+ This code expires in <strong>10 minutes</strong>.
230
+ </p>
231
+
232
+ <p style="margin: 0 0 24px 0; font-family: 'Georgia', serif; font-size: 14px; line-height: 1.6; color: {c['muted']};">
233
+ If you didn't request this code, you can safely ignore this email.
234
+ </p>
235
+
236
+ <!-- Divider -->
237
+ <hr style="border: none; border-top: 1px solid {c['border']}; margin: 24px 0;">
238
+
239
+ <!-- Security Note -->
240
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
241
+ <tr>
242
+ <td style="padding: 16px; background-color: {c['background']}; border-radius: 8px;">
243
+ <p style="margin: 0; font-family: Arial, sans-serif; font-size: 12px; color: {c['muted']};">
244
+ <strong style="color: {c['primary']};">Security tip:</strong>
245
+ Never share your login code with anyone. {name} will never ask for your code via phone or text.
246
+ </p>
247
+ </td>
248
+ </tr>
249
+ </table>
250
+ '''
251
+
252
+ return EmailTemplate(
253
+ subject=f"Your {name} Login Code",
254
+ html_body=base_template(
255
+ content=content,
256
+ preheader=f"Your login code is {formatted_code}",
257
+ colors=colors,
258
+ app_name=app_name,
259
+ **kwargs,
260
+ ),
261
+ )
262
+
263
+
264
+ def welcome_template(
265
+ name: Optional[str] = None,
266
+ colors: dict | None = None,
267
+ app_name: str | None = None,
268
+ features: list[str] | None = None,
269
+ cta_url: str | None = None,
270
+ cta_text: str = "Get Started",
271
+ **kwargs,
272
+ ) -> EmailTemplate:
273
+ """
274
+ Generate a welcome email template for new users.
275
+
276
+ Args:
277
+ name: Optional user's name
278
+ colors: Color overrides
279
+ app_name: Application name
280
+ features: List of feature descriptions
281
+ cta_url: Call-to-action button URL
282
+ cta_text: Call-to-action button text
283
+ **kwargs: Additional arguments passed to base_template
284
+
285
+ Returns:
286
+ EmailTemplate with subject and HTML body
287
+ """
288
+ c = {**COLORS, **(colors or {})}
289
+ app = app_name or APP_NAME
290
+ greeting = f"Hi {name}!" if name else "Welcome!"
291
+ url = cta_url or WEBSITE_URL
292
+
293
+ # Default features if not provided
294
+ default_features = [
295
+ "Access powerful AI capabilities",
296
+ "Store and organize your data",
297
+ "Collaborate with your team",
298
+ ]
299
+ feature_list = features or default_features
300
+
301
+ features_html = "".join(f"<li>{f}</li>" for f in feature_list)
302
+
303
+ content = f'''
304
+ <!-- Greeting -->
305
+ <h1 style="margin: 0 0 8px 0; font-family: 'Arial', sans-serif; font-size: 24px; font-weight: 600; color: {c['foreground']};">
306
+ {greeting}
307
+ </h1>
308
+
309
+ <p style="margin: 0 0 24px 0; font-family: 'Georgia', serif; font-size: 16px; line-height: 1.6; color: {c['foreground']};">
310
+ Welcome to {app}! We're excited to have you on board.
311
+ </p>
312
+
313
+ <!-- Feature List -->
314
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin: 0 0 24px 0;">
315
+ <tr>
316
+ <td style="padding: 16px; background-color: {c['background']}; border-radius: 8px;">
317
+ <p style="margin: 0 0 12px 0; font-family: Arial, sans-serif; font-size: 14px; font-weight: 600; color: {c['primary']};">
318
+ What you can do with {app}:
319
+ </p>
320
+ <ul style="margin: 0; padding-left: 20px; font-family: 'Georgia', serif; font-size: 14px; line-height: 1.8; color: {c['foreground']};">
321
+ {features_html}
322
+ </ul>
323
+ </td>
324
+ </tr>
325
+ </table>
326
+
327
+ <!-- CTA Button -->
328
+ <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin: 0 0 24px 0;">
329
+ <tr>
330
+ <td align="center">
331
+ <a href="{url}" style="
332
+ display: inline-block;
333
+ padding: 14px 32px;
334
+ background-color: {c['primary']};
335
+ color: #FFFFFF;
336
+ font-family: Arial, sans-serif;
337
+ font-size: 16px;
338
+ font-weight: 600;
339
+ text-decoration: none;
340
+ border-radius: 8px;
341
+ ">{cta_text}</a>
342
+ </td>
343
+ </tr>
344
+ </table>
345
+
346
+ <p style="margin: 0; font-family: 'Georgia', serif; font-size: 14px; line-height: 1.6; color: {c['muted']}; text-align: center;">
347
+ We're glad you're here!
348
+ </p>
349
+ '''
350
+
351
+ return EmailTemplate(
352
+ subject=f"Welcome to {app}",
353
+ html_body=base_template(
354
+ content=content,
355
+ preheader=f"Welcome to {app}! Get started today.",
356
+ colors=colors,
357
+ app_name=app_name,
358
+ **kwargs,
359
+ ),
360
+ )
@@ -793,40 +793,72 @@ class PhoenixClient:
793
793
  score: float | None = None,
794
794
  explanation: str | None = None,
795
795
  metadata: dict[str, Any] | None = None,
796
+ trace_id: str | None = None,
796
797
  ) -> str | None:
797
- """Add feedback annotation to a span.
798
+ """Add feedback annotation to a span via Phoenix REST API.
799
+
800
+ Uses direct HTTP POST to /v1/span_annotations for reliability
801
+ (Phoenix Python client API changes frequently).
798
802
 
799
803
  Args:
800
- span_id: Span ID to annotate
804
+ span_id: Span ID to annotate (hex string)
801
805
  annotation_name: Name of the annotation (e.g., "correctness", "user_feedback")
802
806
  annotator_kind: Type of annotator ("HUMAN", "LLM", "CODE")
803
807
  label: Optional label (e.g., "correct", "incorrect", "helpful")
804
808
  score: Optional numeric score (0.0-1.0)
805
809
  explanation: Optional explanation text
806
810
  metadata: Optional additional metadata dict
811
+ trace_id: Optional trace ID (used if span lookup needed)
807
812
 
808
813
  Returns:
809
814
  Annotation ID if successful, None otherwise
810
815
  """
816
+ import httpx
817
+
811
818
  try:
812
- result = self._client.add_span_annotation( # type: ignore[attr-defined]
813
- span_id=span_id,
814
- name=annotation_name,
815
- annotator_kind=annotator_kind,
816
- label=label,
817
- score=score,
818
- explanation=explanation,
819
- metadata=metadata,
820
- )
819
+ # Build annotation payload for Phoenix REST API
820
+ annotation_data = {
821
+ "span_id": span_id,
822
+ "name": annotation_name,
823
+ "annotator_kind": annotator_kind,
824
+ "result": {
825
+ "label": label,
826
+ "score": score,
827
+ "explanation": explanation,
828
+ },
829
+ "metadata": metadata or {},
830
+ }
821
831
 
822
- annotation_id = getattr(result, "id", None) if result else None
823
- logger.info(f"Added {annotator_kind} feedback to span {span_id} -> {annotation_id}")
832
+ # Add trace_id if provided
833
+ if trace_id:
834
+ annotation_data["trace_id"] = trace_id
835
+
836
+ # POST to Phoenix REST API
837
+ annotations_endpoint = f"{self.config.base_url}/v1/span_annotations"
838
+ headers = {}
839
+ if self.config.api_key:
840
+ headers["Authorization"] = f"Bearer {self.config.api_key}"
841
+
842
+ with httpx.Client(timeout=5.0) as client:
843
+ response = client.post(
844
+ annotations_endpoint,
845
+ json={"data": [annotation_data]},
846
+ headers=headers,
847
+ )
848
+ response.raise_for_status()
824
849
 
825
- return annotation_id
850
+ logger.info(f"Added {annotator_kind} feedback to span {span_id}")
851
+ return span_id # Return span_id as annotation reference
826
852
 
853
+ except httpx.HTTPStatusError as e:
854
+ logger.error(
855
+ f"Failed to add span feedback (HTTP {e.response.status_code}): "
856
+ f"{e.response.text if hasattr(e, 'response') else 'N/A'}"
857
+ )
858
+ return None
827
859
  except Exception as e:
828
860
  logger.error(f"Failed to add span feedback: {e}")
829
- raise
861
+ return None
830
862
 
831
863
  def sync_user_feedback(
832
864
  self,
@@ -835,6 +867,7 @@ class PhoenixClient:
835
867
  categories: list[str] | None = None,
836
868
  comment: str | None = None,
837
869
  feedback_id: str | None = None,
870
+ trace_id: str | None = None,
838
871
  ) -> str | None:
839
872
  """Sync user feedback to Phoenix as a span annotation.
840
873
 
@@ -847,6 +880,7 @@ class PhoenixClient:
847
880
  categories: List of feedback categories
848
881
  comment: Free-text comment
849
882
  feedback_id: Optional REM feedback ID for reference
883
+ trace_id: Optional trace ID for the span
850
884
 
851
885
  Returns:
852
886
  Phoenix annotation ID if successful
@@ -860,12 +894,18 @@ class PhoenixClient:
860
894
  ... )
861
895
  """
862
896
  # Convert rating to 0-1 score
897
+ # Rating scheme:
898
+ # -1 = thumbs down → score 0.0
899
+ # 1 = thumbs up → score 1.0
900
+ # 2-5 = star rating → normalized to 0-1 range
863
901
  score = None
864
902
  if rating is not None:
865
903
  if rating == -1:
866
904
  score = 0.0
867
- elif 1 <= rating <= 5:
868
- score = rating / 5.0
905
+ elif rating == 1:
906
+ score = 1.0 # Thumbs up
907
+ elif 2 <= rating <= 5:
908
+ score = (rating - 1) / 4.0 # 2→0.25, 3→0.5, 4→0.75, 5→1.0
869
909
 
870
910
  # Use primary category as label
871
911
  label = categories[0] if categories else None
@@ -880,7 +920,7 @@ class PhoenixClient:
880
920
  explanation = f"Categories: {cats_str}"
881
921
 
882
922
  # Build metadata
883
- metadata = {
923
+ metadata: dict[str, Any] = {
884
924
  "rating": rating,
885
925
  "categories": categories or [],
886
926
  }
@@ -895,6 +935,7 @@ class PhoenixClient:
895
935
  score=score,
896
936
  explanation=explanation,
897
937
  metadata=metadata,
938
+ trace_id=trace_id,
898
939
  )
899
940
 
900
941
  def get_span_annotations(
@@ -688,6 +688,44 @@ from rem.api.main import app # Use REM's FastAPI app
688
688
  # Or build your own app using rem.services
689
689
  ```
690
690
 
691
+ ## Adding Models & Migrations
692
+
693
+ Quick workflow for adding new database models:
694
+
695
+ 1. **Create a model** in `models/__init__.py` (or a submodule):
696
+ ```python
697
+ import rem
698
+ from rem.models.core import CoreModel
699
+
700
+ @rem.register_model
701
+ class MyEntity(CoreModel):
702
+ name: str
703
+ description: str # Auto-embedded (common field name)
704
+ ```
705
+
706
+ 2. **Check for schema drift** - REM auto-detects `./models` directory:
707
+ ```bash
708
+ rem db diff # Show pending changes (additive only)
709
+ rem db diff --strategy full # Include destructive changes
710
+ ```
711
+
712
+ 3. **Generate migration** (optional - for version-controlled SQL):
713
+ ```bash
714
+ rem db diff --generate # Creates numbered .sql file
715
+ ```
716
+
717
+ 4. **Apply changes**:
718
+ ```bash
719
+ rem db migrate # Apply all pending migrations
720
+ ```
721
+
722
+ **Key points:**
723
+ - Models in `./models/` are auto-discovered (must have `__init__.py`)
724
+ - Or set `MODELS__IMPORT_MODULES=myapp.models` for custom paths
725
+ - `CoreModel` provides: `id`, `tenant_id`, `user_id`, `created_at`, `updated_at`, `deleted_at`, `graph_edges`, `metadata`, `tags`
726
+ - Fields named `content`, `description`, `summary`, `text`, `body`, `message`, `notes` get embeddings by default
727
+ - Use `Field(json_schema_extra={"embed": True})` to embed other fields
728
+
691
729
  ## Configuration
692
730
 
693
731
  Environment variables: