remdb 0.3.0__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 (187) hide show
  1. rem/__init__.py +2 -0
  2. rem/agentic/README.md +650 -0
  3. rem/agentic/__init__.py +39 -0
  4. rem/agentic/agents/README.md +155 -0
  5. rem/agentic/agents/__init__.py +8 -0
  6. rem/agentic/context.py +148 -0
  7. rem/agentic/context_builder.py +329 -0
  8. rem/agentic/mcp/__init__.py +0 -0
  9. rem/agentic/mcp/tool_wrapper.py +107 -0
  10. rem/agentic/otel/__init__.py +5 -0
  11. rem/agentic/otel/setup.py +151 -0
  12. rem/agentic/providers/phoenix.py +674 -0
  13. rem/agentic/providers/pydantic_ai.py +572 -0
  14. rem/agentic/query.py +117 -0
  15. rem/agentic/query_helper.py +89 -0
  16. rem/agentic/schema.py +396 -0
  17. rem/agentic/serialization.py +245 -0
  18. rem/agentic/tools/__init__.py +5 -0
  19. rem/agentic/tools/rem_tools.py +231 -0
  20. rem/api/README.md +420 -0
  21. rem/api/main.py +324 -0
  22. rem/api/mcp_router/prompts.py +182 -0
  23. rem/api/mcp_router/resources.py +536 -0
  24. rem/api/mcp_router/server.py +213 -0
  25. rem/api/mcp_router/tools.py +584 -0
  26. rem/api/routers/auth.py +229 -0
  27. rem/api/routers/chat/__init__.py +5 -0
  28. rem/api/routers/chat/completions.py +281 -0
  29. rem/api/routers/chat/json_utils.py +76 -0
  30. rem/api/routers/chat/models.py +124 -0
  31. rem/api/routers/chat/streaming.py +185 -0
  32. rem/auth/README.md +258 -0
  33. rem/auth/__init__.py +26 -0
  34. rem/auth/middleware.py +100 -0
  35. rem/auth/providers/__init__.py +13 -0
  36. rem/auth/providers/base.py +376 -0
  37. rem/auth/providers/google.py +163 -0
  38. rem/auth/providers/microsoft.py +237 -0
  39. rem/cli/README.md +455 -0
  40. rem/cli/__init__.py +8 -0
  41. rem/cli/commands/README.md +126 -0
  42. rem/cli/commands/__init__.py +3 -0
  43. rem/cli/commands/ask.py +566 -0
  44. rem/cli/commands/configure.py +497 -0
  45. rem/cli/commands/db.py +493 -0
  46. rem/cli/commands/dreaming.py +324 -0
  47. rem/cli/commands/experiments.py +1302 -0
  48. rem/cli/commands/mcp.py +66 -0
  49. rem/cli/commands/process.py +245 -0
  50. rem/cli/commands/schema.py +183 -0
  51. rem/cli/commands/serve.py +106 -0
  52. rem/cli/dreaming.py +363 -0
  53. rem/cli/main.py +96 -0
  54. rem/config.py +237 -0
  55. rem/mcp_server.py +41 -0
  56. rem/models/core/__init__.py +49 -0
  57. rem/models/core/core_model.py +64 -0
  58. rem/models/core/engram.py +333 -0
  59. rem/models/core/experiment.py +628 -0
  60. rem/models/core/inline_edge.py +132 -0
  61. rem/models/core/rem_query.py +243 -0
  62. rem/models/entities/__init__.py +43 -0
  63. rem/models/entities/file.py +57 -0
  64. rem/models/entities/image_resource.py +88 -0
  65. rem/models/entities/message.py +35 -0
  66. rem/models/entities/moment.py +123 -0
  67. rem/models/entities/ontology.py +191 -0
  68. rem/models/entities/ontology_config.py +131 -0
  69. rem/models/entities/resource.py +95 -0
  70. rem/models/entities/schema.py +87 -0
  71. rem/models/entities/user.py +85 -0
  72. rem/py.typed +0 -0
  73. rem/schemas/README.md +507 -0
  74. rem/schemas/__init__.py +6 -0
  75. rem/schemas/agents/README.md +92 -0
  76. rem/schemas/agents/core/moment-builder.yaml +178 -0
  77. rem/schemas/agents/core/rem-query-agent.yaml +226 -0
  78. rem/schemas/agents/core/resource-affinity-assessor.yaml +99 -0
  79. rem/schemas/agents/core/simple-assistant.yaml +19 -0
  80. rem/schemas/agents/core/user-profile-builder.yaml +163 -0
  81. rem/schemas/agents/examples/contract-analyzer.yaml +317 -0
  82. rem/schemas/agents/examples/contract-extractor.yaml +134 -0
  83. rem/schemas/agents/examples/cv-parser.yaml +263 -0
  84. rem/schemas/agents/examples/hello-world.yaml +37 -0
  85. rem/schemas/agents/examples/query.yaml +54 -0
  86. rem/schemas/agents/examples/simple.yaml +21 -0
  87. rem/schemas/agents/examples/test.yaml +29 -0
  88. rem/schemas/agents/rem.yaml +128 -0
  89. rem/schemas/evaluators/hello-world/default.yaml +77 -0
  90. rem/schemas/evaluators/rem/faithfulness.yaml +219 -0
  91. rem/schemas/evaluators/rem/lookup-correctness.yaml +182 -0
  92. rem/schemas/evaluators/rem/retrieval-precision.yaml +199 -0
  93. rem/schemas/evaluators/rem/retrieval-recall.yaml +211 -0
  94. rem/schemas/evaluators/rem/search-correctness.yaml +192 -0
  95. rem/services/__init__.py +16 -0
  96. rem/services/audio/INTEGRATION.md +308 -0
  97. rem/services/audio/README.md +376 -0
  98. rem/services/audio/__init__.py +15 -0
  99. rem/services/audio/chunker.py +354 -0
  100. rem/services/audio/transcriber.py +259 -0
  101. rem/services/content/README.md +1269 -0
  102. rem/services/content/__init__.py +5 -0
  103. rem/services/content/providers.py +806 -0
  104. rem/services/content/service.py +676 -0
  105. rem/services/dreaming/README.md +230 -0
  106. rem/services/dreaming/__init__.py +53 -0
  107. rem/services/dreaming/affinity_service.py +336 -0
  108. rem/services/dreaming/moment_service.py +264 -0
  109. rem/services/dreaming/ontology_service.py +54 -0
  110. rem/services/dreaming/user_model_service.py +297 -0
  111. rem/services/dreaming/utils.py +39 -0
  112. rem/services/embeddings/__init__.py +11 -0
  113. rem/services/embeddings/api.py +120 -0
  114. rem/services/embeddings/worker.py +421 -0
  115. rem/services/fs/README.md +662 -0
  116. rem/services/fs/__init__.py +62 -0
  117. rem/services/fs/examples.py +206 -0
  118. rem/services/fs/examples_paths.py +204 -0
  119. rem/services/fs/git_provider.py +935 -0
  120. rem/services/fs/local_provider.py +760 -0
  121. rem/services/fs/parsing-hooks-examples.md +172 -0
  122. rem/services/fs/paths.py +276 -0
  123. rem/services/fs/provider.py +460 -0
  124. rem/services/fs/s3_provider.py +1042 -0
  125. rem/services/fs/service.py +186 -0
  126. rem/services/git/README.md +1075 -0
  127. rem/services/git/__init__.py +17 -0
  128. rem/services/git/service.py +469 -0
  129. rem/services/phoenix/EXPERIMENT_DESIGN.md +1146 -0
  130. rem/services/phoenix/README.md +453 -0
  131. rem/services/phoenix/__init__.py +46 -0
  132. rem/services/phoenix/client.py +686 -0
  133. rem/services/phoenix/config.py +88 -0
  134. rem/services/phoenix/prompt_labels.py +477 -0
  135. rem/services/postgres/README.md +575 -0
  136. rem/services/postgres/__init__.py +23 -0
  137. rem/services/postgres/migration_service.py +427 -0
  138. rem/services/postgres/pydantic_to_sqlalchemy.py +232 -0
  139. rem/services/postgres/register_type.py +352 -0
  140. rem/services/postgres/repository.py +337 -0
  141. rem/services/postgres/schema_generator.py +379 -0
  142. rem/services/postgres/service.py +802 -0
  143. rem/services/postgres/sql_builder.py +354 -0
  144. rem/services/rem/README.md +304 -0
  145. rem/services/rem/__init__.py +23 -0
  146. rem/services/rem/exceptions.py +71 -0
  147. rem/services/rem/executor.py +293 -0
  148. rem/services/rem/parser.py +145 -0
  149. rem/services/rem/queries.py +196 -0
  150. rem/services/rem/query.py +371 -0
  151. rem/services/rem/service.py +527 -0
  152. rem/services/session/README.md +374 -0
  153. rem/services/session/__init__.py +6 -0
  154. rem/services/session/compression.py +360 -0
  155. rem/services/session/reload.py +77 -0
  156. rem/settings.py +1235 -0
  157. rem/sql/002_install_models.sql +1068 -0
  158. rem/sql/background_indexes.sql +42 -0
  159. rem/sql/install_models.sql +1038 -0
  160. rem/sql/migrations/001_install.sql +503 -0
  161. rem/sql/migrations/002_install_models.sql +1202 -0
  162. rem/utils/AGENTIC_CHUNKING.md +597 -0
  163. rem/utils/README.md +583 -0
  164. rem/utils/__init__.py +43 -0
  165. rem/utils/agentic_chunking.py +622 -0
  166. rem/utils/batch_ops.py +343 -0
  167. rem/utils/chunking.py +108 -0
  168. rem/utils/clip_embeddings.py +276 -0
  169. rem/utils/dict_utils.py +98 -0
  170. rem/utils/embeddings.py +423 -0
  171. rem/utils/examples/embeddings_example.py +305 -0
  172. rem/utils/examples/sql_types_example.py +202 -0
  173. rem/utils/markdown.py +16 -0
  174. rem/utils/model_helpers.py +236 -0
  175. rem/utils/schema_loader.py +336 -0
  176. rem/utils/sql_types.py +348 -0
  177. rem/utils/user_id.py +81 -0
  178. rem/utils/vision.py +330 -0
  179. rem/workers/README.md +506 -0
  180. rem/workers/__init__.py +5 -0
  181. rem/workers/dreaming.py +502 -0
  182. rem/workers/engram_processor.py +312 -0
  183. rem/workers/sqs_file_processor.py +193 -0
  184. remdb-0.3.0.dist-info/METADATA +1455 -0
  185. remdb-0.3.0.dist-info/RECORD +187 -0
  186. remdb-0.3.0.dist-info/WHEEL +4 -0
  187. remdb-0.3.0.dist-info/entry_points.txt +2 -0
rem/settings.py ADDED
@@ -0,0 +1,1235 @@
1
+ """
2
+ REM Settings and Configuration.
3
+
4
+ Pydantic settings with environment variable support:
5
+ - Nested settings with env_prefix for organization
6
+ - Environment variables use double underscore delimiter (ENV__NESTED__VAR)
7
+ - Sensitive defaults (auth disabled, OTEL disabled for local dev)
8
+ - Global settings singleton
9
+
10
+ Example .env file:
11
+ # API Server
12
+ API__HOST=0.0.0.0
13
+ API__PORT=8000
14
+ API__RELOAD=true
15
+ API__LOG_LEVEL=info
16
+
17
+ # LLM
18
+ LLM__DEFAULT_MODEL=anthropic:claude-sonnet-4-5-20250929
19
+ LLM__DEFAULT_TEMPERATURE=0.5
20
+ LLM__MAX_RETRIES=10
21
+ LLM__OPENAI_API_KEY=sk-...
22
+ LLM__ANTHROPIC_API_KEY=sk-ant-...
23
+
24
+ # Database (port 5050 for Docker Compose)
25
+ POSTGRES__CONNECTION_STRING=postgresql://rem:rem@localhost:5050/rem
26
+ POSTGRES__POOL_MIN_SIZE=5
27
+ POSTGRES__POOL_MAX_SIZE=20
28
+ POSTGRES__STATEMENT_TIMEOUT=30000
29
+
30
+ # Auth (disabled by default)
31
+ AUTH__ENABLED=false
32
+ AUTH__OIDC_ISSUER_URL=https://accounts.google.com
33
+ AUTH__OIDC_CLIENT_ID=your-client-id
34
+ AUTH__SESSION_SECRET=your-secret-key
35
+
36
+ # OpenTelemetry (disabled by default)
37
+ OTEL__ENABLED=false
38
+ OTEL__SERVICE_NAME=rem-api
39
+ OTEL__COLLECTOR_ENDPOINT=http://localhost:4318
40
+ OTEL__PROTOCOL=http
41
+
42
+ # Arize Phoenix (disabled by default)
43
+ PHOENIX__ENABLED=false
44
+ PHOENIX__COLLECTOR_ENDPOINT=http://localhost:6006/v1/traces
45
+ PHOENIX__PROJECT_NAME=rem
46
+
47
+ # S3 Storage
48
+ S3__BUCKET_NAME=rem-storage
49
+ S3__REGION=us-east-1
50
+ S3__ENDPOINT_URL=http://localhost:9000 # For MinIO
51
+ S3__ACCESS_KEY_ID=minioadmin
52
+ S3__SECRET_ACCESS_KEY=minioadmin
53
+
54
+ # Environment
55
+ ENVIRONMENT=development
56
+ TEAM=rem
57
+ """
58
+
59
+ import os
60
+ from pydantic import Field, field_validator
61
+ from pydantic_settings import BaseSettings, SettingsConfigDict
62
+
63
+
64
+ class LLMSettings(BaseSettings):
65
+ """
66
+ LLM provider settings for Pydantic AI agents.
67
+
68
+ Environment variables (accepts both prefixed and unprefixed):
69
+ LLM__DEFAULT_MODEL or DEFAULT_MODEL - Default model (format: provider:model-id)
70
+ LLM__DEFAULT_TEMPERATURE or DEFAULT_TEMPERATURE - Temperature for generation
71
+ LLM__MAX_RETRIES or MAX_RETRIES - Max agent request retries
72
+ LLM__EVALUATOR_MODEL or EVALUATOR_MODEL - Model for LLM-as-judge evaluation
73
+ LLM__OPENAI_API_KEY or OPENAI_API_KEY - OpenAI API key
74
+ LLM__ANTHROPIC_API_KEY or ANTHROPIC_API_KEY - Anthropic API key
75
+ LLM__EMBEDDING_PROVIDER or EMBEDDING_PROVIDER - Default embedding provider (openai, cohere, jina, etc.)
76
+ LLM__EMBEDDING_MODEL or EMBEDDING_MODEL - Default embedding model name
77
+ """
78
+
79
+ model_config = SettingsConfigDict(
80
+ env_prefix="LLM__",
81
+ env_file=".env",
82
+ env_file_encoding="utf-8",
83
+ extra="ignore",
84
+ )
85
+
86
+ default_model: str = Field(
87
+ default="anthropic:claude-sonnet-4-5-20250929",
88
+ description="Default LLM model (format: provider:model-id)",
89
+ )
90
+
91
+ default_temperature: float = Field(
92
+ default=0.5,
93
+ ge=0.0,
94
+ le=1.0,
95
+ description="Default temperature (0.0-0.3: analytical, 0.7-1.0: creative)",
96
+ )
97
+
98
+ max_retries: int = Field(
99
+ default=10,
100
+ description="Maximum agent request retries (prevents infinite loops from tool errors)",
101
+ )
102
+
103
+ default_max_iterations: int = Field(
104
+ default=20,
105
+ description="Default max iterations for agentic calls (limits total LLM requests per agent.run())",
106
+ )
107
+
108
+ evaluator_model: str = Field(
109
+ default="gpt-4.1",
110
+ description="Model for LLM-as-judge evaluators (separate from generation model)",
111
+ )
112
+
113
+ query_agent_model: str = Field(
114
+ default="cerebras:qwen-3-32b",
115
+ description="Model for REM Query Agent (natural language to REM query). Cerebras Qwen 3-32B provides ultra-fast inference (1.2s reasoning, 2400 tok/s). Alternative: cerebras:llama-3.3-70b, gpt-4o-mini, or claude-sonnet-4.5",
116
+ )
117
+
118
+ openai_api_key: str | None = Field(
119
+ default=None,
120
+ description="OpenAI API key for GPT models (reads from LLM__OPENAI_API_KEY or OPENAI_API_KEY)",
121
+ )
122
+
123
+ anthropic_api_key: str | None = Field(
124
+ default=None,
125
+ description="Anthropic API key for Claude models (reads from LLM__ANTHROPIC_API_KEY or ANTHROPIC_API_KEY)",
126
+ )
127
+
128
+ embedding_provider: str = Field(
129
+ default="openai",
130
+ description="Default embedding provider (openai, cohere, jina, etc.)",
131
+ )
132
+
133
+ embedding_model: str = Field(
134
+ default="text-embedding-3-small",
135
+ description="Default embedding model (provider-specific model name)",
136
+ )
137
+
138
+ @field_validator("openai_api_key", mode="before")
139
+ @classmethod
140
+ def validate_openai_api_key(cls, v):
141
+ """Fallback to OPENAI_API_KEY if LLM__OPENAI_API_KEY not set (LLM__ takes precedence)."""
142
+ if v is None:
143
+ return os.getenv("OPENAI_API_KEY")
144
+ return v
145
+
146
+ @field_validator("anthropic_api_key", mode="before")
147
+ @classmethod
148
+ def validate_anthropic_api_key(cls, v):
149
+ """Fallback to ANTHROPIC_API_KEY if LLM__ANTHROPIC_API_KEY not set (LLM__ takes precedence)."""
150
+ if v is None:
151
+ return os.getenv("ANTHROPIC_API_KEY")
152
+ return v
153
+
154
+
155
+ class MCPSettings(BaseSettings):
156
+ """
157
+ MCP server settings.
158
+
159
+ MCP server is mounted at /api/v1/mcp with FastMCP.
160
+ Can be accessed via:
161
+ - HTTP transport (production): /api/v1/mcp
162
+ - SSE transport (compatible with Claude Desktop)
163
+
164
+ Environment variables:
165
+ MCP_SERVER_{NAME} - Server URLs for MCP client connections
166
+ """
167
+
168
+ model_config = SettingsConfigDict(
169
+ env_prefix="MCP__",
170
+ env_file=".env",
171
+ env_file_encoding="utf-8",
172
+ extra="ignore",
173
+ )
174
+
175
+ @staticmethod
176
+ def get_server_url(server_name: str) -> str | None:
177
+ """
178
+ Get MCP server URL from environment variable.
179
+
180
+ Args:
181
+ server_name: Server name (e.g., "test", "prod")
182
+
183
+ Returns:
184
+ Server URL or None if not configured
185
+
186
+ Example:
187
+ MCP_SERVER_TEST=http://localhost:8000/api/v1/mcp
188
+ """
189
+ import os
190
+
191
+ env_key = f"MCP_SERVER_{server_name.upper()}"
192
+ return os.getenv(env_key)
193
+
194
+
195
+ class OTELSettings(BaseSettings):
196
+ """
197
+ OpenTelemetry observability settings.
198
+
199
+ Integrates with OpenTelemetry Collector for distributed tracing.
200
+ Uses OTLP protocol to export to Arize Phoenix or other OTLP backends.
201
+
202
+ Environment variables:
203
+ OTEL__ENABLED - Enable instrumentation (default: false for local dev)
204
+ OTEL__SERVICE_NAME - Service name for traces
205
+ OTEL__COLLECTOR_ENDPOINT - OTLP endpoint (gRPC: 4317, HTTP: 4318)
206
+ OTEL__PROTOCOL - Protocol to use (grpc or http)
207
+ OTEL__EXPORT_TIMEOUT - Export timeout in milliseconds
208
+ """
209
+
210
+ model_config = SettingsConfigDict(
211
+ env_prefix="OTEL__",
212
+ env_file=".env",
213
+ env_file_encoding="utf-8",
214
+ extra="ignore",
215
+ )
216
+
217
+ enabled: bool = Field(
218
+ default=False,
219
+ description="Enable OpenTelemetry instrumentation (disabled by default for local dev)",
220
+ )
221
+
222
+ service_name: str = Field(
223
+ default="rem-api",
224
+ description="Service name for traces",
225
+ )
226
+
227
+ collector_endpoint: str = Field(
228
+ default="http://localhost:4318",
229
+ description="OTLP collector endpoint (HTTP: 4318, gRPC: 4317)",
230
+ )
231
+
232
+ protocol: str = Field(
233
+ default="http",
234
+ description="OTLP protocol (http or grpc)",
235
+ )
236
+
237
+ export_timeout: int = Field(
238
+ default=10000,
239
+ description="Export timeout in milliseconds",
240
+ )
241
+
242
+
243
+ class PhoenixSettings(BaseSettings):
244
+ """
245
+ Arize Phoenix settings for LLM observability and evaluation.
246
+
247
+ Phoenix provides:
248
+ - OpenTelemetry-based LLM tracing (OpenInference conventions)
249
+ - Experiment tracking
250
+ - Evaluation feedback
251
+
252
+ Environment variables:
253
+ PHOENIX__ENABLED - Enable Phoenix integration
254
+ PHOENIX__BASE_URL - Phoenix base URL (for client connections)
255
+ PHOENIX__API_KEY - Phoenix API key (cloud instances)
256
+ PHOENIX__COLLECTOR_ENDPOINT - Phoenix OTLP endpoint
257
+ PHOENIX__PROJECT_NAME - Phoenix project name for trace organization
258
+ """
259
+
260
+ model_config = SettingsConfigDict(
261
+ env_prefix="PHOENIX__",
262
+ env_file=".env",
263
+ env_file_encoding="utf-8",
264
+ extra="ignore",
265
+ )
266
+
267
+ enabled: bool = Field(
268
+ default=False,
269
+ description="Enable Phoenix integration (disabled by default for local dev)",
270
+ )
271
+
272
+ base_url: str = Field(
273
+ default="http://localhost:6006",
274
+ description="Phoenix base URL for client connections (default local Phoenix port)",
275
+ )
276
+
277
+ api_key: str | None = Field(
278
+ default=None,
279
+ description="Arize Phoenix API key for cloud instances",
280
+ )
281
+
282
+ collector_endpoint: str = Field(
283
+ default="http://localhost:6006/v1/traces",
284
+ description="Phoenix OTLP endpoint for traces (default local Phoenix port)",
285
+ )
286
+
287
+ project_name: str = Field(
288
+ default="rem",
289
+ description="Phoenix project name for trace organization",
290
+ )
291
+
292
+
293
+ class GoogleOAuthSettings(BaseSettings):
294
+ """
295
+ Google OAuth settings.
296
+
297
+ Environment variables:
298
+ AUTH__GOOGLE__CLIENT_ID - Google OAuth client ID
299
+ AUTH__GOOGLE__CLIENT_SECRET - Google OAuth client secret
300
+ AUTH__GOOGLE__REDIRECT_URI - OAuth callback URL
301
+ AUTH__GOOGLE__HOSTED_DOMAIN - Restrict to Google Workspace domain
302
+ """
303
+
304
+ model_config = SettingsConfigDict(
305
+ env_prefix="AUTH__GOOGLE__",
306
+ env_file=".env",
307
+ env_file_encoding="utf-8",
308
+ extra="ignore",
309
+ )
310
+
311
+ client_id: str = Field(default="", description="Google OAuth client ID")
312
+ client_secret: str = Field(default="", description="Google OAuth client secret")
313
+ redirect_uri: str = Field(
314
+ default="http://localhost:8000/api/auth/google/callback",
315
+ description="OAuth redirect URI",
316
+ )
317
+ hosted_domain: str | None = Field(
318
+ default=None, description="Restrict to Google Workspace domain (e.g., example.com)"
319
+ )
320
+
321
+
322
+ class MicrosoftOAuthSettings(BaseSettings):
323
+ """
324
+ Microsoft Entra ID OAuth settings.
325
+
326
+ Environment variables:
327
+ AUTH__MICROSOFT__CLIENT_ID - Application (client) ID
328
+ AUTH__MICROSOFT__CLIENT_SECRET - Client secret
329
+ AUTH__MICROSOFT__REDIRECT_URI - OAuth callback URL
330
+ AUTH__MICROSOFT__TENANT - Tenant ID or common/organizations/consumers
331
+ """
332
+
333
+ model_config = SettingsConfigDict(
334
+ env_prefix="AUTH__MICROSOFT__",
335
+ env_file=".env",
336
+ env_file_encoding="utf-8",
337
+ extra="ignore",
338
+ )
339
+
340
+ client_id: str = Field(default="", description="Microsoft Application ID")
341
+ client_secret: str = Field(default="", description="Microsoft client secret")
342
+ redirect_uri: str = Field(
343
+ default="http://localhost:8000/api/auth/microsoft/callback",
344
+ description="OAuth redirect URI",
345
+ )
346
+ tenant: str = Field(
347
+ default="common",
348
+ description="Tenant ID or common/organizations/consumers",
349
+ )
350
+
351
+
352
+ class AuthSettings(BaseSettings):
353
+ """
354
+ Authentication settings for OAuth 2.1 / OIDC.
355
+
356
+ Supports multiple providers:
357
+ - Google OAuth
358
+ - Microsoft Entra ID
359
+ - Custom OIDC provider
360
+
361
+ Environment variables:
362
+ AUTH__ENABLED - Enable authentication (default: false)
363
+ AUTH__SESSION_SECRET - Secret for session cookie signing
364
+ AUTH__GOOGLE__* - Google OAuth settings
365
+ AUTH__MICROSOFT__* - Microsoft OAuth settings
366
+ """
367
+
368
+ model_config = SettingsConfigDict(
369
+ env_prefix="AUTH__",
370
+ env_file=".env",
371
+ env_file_encoding="utf-8",
372
+ extra="ignore",
373
+ )
374
+
375
+ enabled: bool = Field(
376
+ default=False,
377
+ description="Enable authentication (disabled by default for testing)",
378
+ )
379
+
380
+ session_secret: str = Field(
381
+ default="",
382
+ description="Secret key for session cookie signing (generate with secrets.token_hex(32))",
383
+ )
384
+
385
+ # OAuth provider settings
386
+ google: GoogleOAuthSettings = Field(default_factory=GoogleOAuthSettings)
387
+ microsoft: MicrosoftOAuthSettings = Field(default_factory=MicrosoftOAuthSettings)
388
+
389
+
390
+ class PostgresSettings(BaseSettings):
391
+ """
392
+ PostgreSQL settings for CloudNativePG.
393
+
394
+ Connects to PostgreSQL 18 with pgvector extension running on CloudNativePG.
395
+
396
+ Environment variables:
397
+ POSTGRES__ENABLED - Enable database connection (default: true)
398
+ POSTGRES__CONNECTION_STRING - PostgreSQL connection string
399
+ POSTGRES__POOL_SIZE - Connection pool size
400
+ POSTGRES__POOL_MIN_SIZE - Minimum pool size
401
+ POSTGRES__POOL_MAX_SIZE - Maximum pool size
402
+ POSTGRES__POOL_TIMEOUT - Connection timeout in seconds
403
+ POSTGRES__STATEMENT_TIMEOUT - Statement timeout in milliseconds
404
+ """
405
+
406
+ model_config = SettingsConfigDict(
407
+ env_prefix="POSTGRES__",
408
+ env_file=".env",
409
+ env_file_encoding="utf-8",
410
+ extra="ignore",
411
+ )
412
+
413
+ enabled: bool = Field(
414
+ default=True,
415
+ description="Enable database connection (set to false for testing without DB)",
416
+ )
417
+
418
+ connection_string: str = Field(
419
+ default="postgresql://rem:rem@localhost:5050/rem",
420
+ description="PostgreSQL connection string (default uses Docker Compose port 5050)",
421
+ )
422
+
423
+ pool_size: int = Field(
424
+ default=10,
425
+ description="Connection pool size (deprecated, use pool_min_size/pool_max_size)",
426
+ )
427
+
428
+ pool_min_size: int = Field(
429
+ default=5,
430
+ description="Minimum number of connections in pool",
431
+ )
432
+
433
+ pool_max_size: int = Field(
434
+ default=20,
435
+ description="Maximum number of connections in pool",
436
+ )
437
+
438
+ pool_timeout: int = Field(
439
+ default=30,
440
+ description="Connection timeout in seconds",
441
+ )
442
+
443
+ statement_timeout: int = Field(
444
+ default=30000,
445
+ description="Statement timeout in milliseconds (30 seconds default)",
446
+ )
447
+
448
+ @property
449
+ def user(self) -> str:
450
+ from urllib.parse import urlparse
451
+ return urlparse(self.connection_string).username or "postgres"
452
+
453
+ @property
454
+ def password(self) -> str | None:
455
+ from urllib.parse import urlparse
456
+ return urlparse(self.connection_string).password
457
+
458
+ @property
459
+ def database(self) -> str:
460
+ from urllib.parse import urlparse
461
+ return urlparse(self.connection_string).path.lstrip("/")
462
+
463
+ @property
464
+ def host(self) -> str:
465
+ from urllib.parse import urlparse
466
+ return urlparse(self.connection_string).hostname or "localhost"
467
+
468
+ @property
469
+ def port(self) -> int:
470
+ from urllib.parse import urlparse
471
+ return urlparse(self.connection_string).port or 5432
472
+
473
+
474
+ class MigrationSettings(BaseSettings):
475
+ """
476
+ Migration settings.
477
+
478
+ Environment variables:
479
+ MIGRATION__AUTO_UPGRADE - Automatically run migrations on startup
480
+ MIGRATION__MODE - Migration safety mode (permissive, additive, strict)
481
+ MIGRATION__ALLOW_DROP_COLUMNS - Allow DROP COLUMN operations
482
+ MIGRATION__ALLOW_DROP_TABLES - Allow DROP TABLE operations
483
+ MIGRATION__ALLOW_ALTER_COLUMNS - Allow ALTER COLUMN TYPE operations
484
+ MIGRATION__ALLOW_RENAME_COLUMNS - Allow RENAME COLUMN operations
485
+ MIGRATION__ALLOW_RENAME_TABLES - Allow RENAME TABLE operations
486
+ MIGRATION__UNSAFE_ALTER_WARNING - Warn on unsafe ALTER operations
487
+ """
488
+
489
+ model_config = SettingsConfigDict(
490
+ env_prefix="MIGRATION__",
491
+ env_file=".env",
492
+ env_file_encoding="utf-8",
493
+ extra="ignore",
494
+ )
495
+
496
+ auto_upgrade: bool = Field(
497
+ default=True,
498
+ description="Automatically run database migrations on startup",
499
+ )
500
+
501
+ mode: str = Field(
502
+ default="permissive",
503
+ description="Migration safety mode: permissive, additive, strict",
504
+ )
505
+
506
+ allow_drop_columns: bool = Field(
507
+ default=False,
508
+ description="Allow DROP COLUMN operations (unsafe)",
509
+ )
510
+
511
+ allow_drop_tables: bool = Field(
512
+ default=False,
513
+ description="Allow DROP TABLE operations (unsafe)",
514
+ )
515
+
516
+ allow_alter_columns: bool = Field(
517
+ default=True,
518
+ description="Allow ALTER COLUMN TYPE operations (can be unsafe)",
519
+ )
520
+
521
+ allow_rename_columns: bool = Field(
522
+ default=True,
523
+ description="Allow RENAME COLUMN operations (can be unsafe)",
524
+ )
525
+
526
+ allow_rename_tables: bool = Field(
527
+ default=True,
528
+ description="Allow RENAME TABLE operations (can be unsafe)",
529
+ )
530
+
531
+ unsafe_alter_warning: bool = Field(
532
+ default=True,
533
+ description="Emit warning on potentially unsafe ALTER operations",
534
+ )
535
+
536
+
537
+ class StorageSettings(BaseSettings):
538
+ """
539
+ Storage provider settings.
540
+
541
+ Controls which storage backend to use for file uploads and artifacts.
542
+
543
+ Environment variables:
544
+ STORAGE__PROVIDER - Storage provider (local or s3, default: local)
545
+ STORAGE__BASE_PATH - Base path for local filesystem storage (default: ~/.rem/fs)
546
+ """
547
+
548
+ model_config = SettingsConfigDict(
549
+ env_prefix="STORAGE__",
550
+ env_file=".env",
551
+ env_file_encoding="utf-8",
552
+ extra="ignore",
553
+ )
554
+
555
+ provider: str = Field(
556
+ default="local",
557
+ description="Storage provider: 'local' for filesystem, 's3' for AWS S3",
558
+ )
559
+
560
+ base_path: str = Field(
561
+ default="~/.rem/fs",
562
+ description="Base path for local filesystem storage (only used when provider='local')",
563
+ )
564
+
565
+
566
+ class S3Settings(BaseSettings):
567
+ """
568
+ S3 storage settings for file uploads and artifacts.
569
+
570
+ Uses IRSA (IAM Roles for Service Accounts) for AWS permissions in EKS.
571
+ For local development, can use MinIO or provide access keys.
572
+
573
+ Bucket Naming Convention:
574
+ - Default: rem-io-{environment} (e.g., rem-io-development, rem-io-staging, rem-io-production)
575
+ - Matches Kubernetes manifest convention for consistency
576
+ - Override with S3__BUCKET_NAME environment variable
577
+
578
+ Path Convention:
579
+ Uploads: s3://{bucket}/{version}/uploads/{user_id}/{yyyy}/{mm}/{dd}/{filename}
580
+ Parsed: s3://{bucket}/{version}/parsed/{user_id}/{yyyy}/{mm}/{dd}/{filename}/{resource}
581
+
582
+ Environment variables:
583
+ S3__BUCKET_NAME - S3 bucket name (default: rem-io-development)
584
+ S3__VERSION - API version for paths (default: v1)
585
+ S3__UPLOADS_PREFIX - Uploads directory prefix (default: uploads)
586
+ S3__PARSED_PREFIX - Parsed content directory prefix (default: parsed)
587
+ S3__REGION - AWS region
588
+ S3__ENDPOINT_URL - Custom endpoint (for MinIO, LocalStack)
589
+ S3__ACCESS_KEY_ID - AWS access key (not needed with IRSA)
590
+ S3__SECRET_ACCESS_KEY - AWS secret key (not needed with IRSA)
591
+ S3__USE_SSL - Use SSL for connections (default: true)
592
+ """
593
+
594
+ model_config = SettingsConfigDict(
595
+ env_prefix="S3__",
596
+ env_file=".env",
597
+ env_file_encoding="utf-8",
598
+ extra="ignore",
599
+ )
600
+
601
+ bucket_name: str = Field(
602
+ default="rem-io-development",
603
+ description="S3 bucket name (convention: rem-io-{environment})",
604
+ )
605
+
606
+ version: str = Field(
607
+ default="v1",
608
+ description="API version for S3 path structure",
609
+ )
610
+
611
+ uploads_prefix: str = Field(
612
+ default="uploads",
613
+ description="Prefix for uploaded files (e.g., 'uploads' -> bucket/v1/uploads/...)",
614
+ )
615
+
616
+ parsed_prefix: str = Field(
617
+ default="parsed",
618
+ description="Prefix for parsed content (e.g., 'parsed' -> bucket/v1/parsed/...)",
619
+ )
620
+
621
+ region: str = Field(
622
+ default="us-east-1",
623
+ description="AWS region",
624
+ )
625
+
626
+ endpoint_url: str | None = Field(
627
+ default=None,
628
+ description="Custom S3 endpoint (for MinIO, LocalStack)",
629
+ )
630
+
631
+ access_key_id: str | None = Field(
632
+ default=None,
633
+ description="AWS access key ID (not needed with IRSA in EKS)",
634
+ )
635
+
636
+ secret_access_key: str | None = Field(
637
+ default=None,
638
+ description="AWS secret access key (not needed with IRSA in EKS)",
639
+ )
640
+
641
+ use_ssl: bool = Field(
642
+ default=True,
643
+ description="Use SSL for S3 connections",
644
+ )
645
+
646
+
647
+ class ChunkingSettings(BaseSettings):
648
+ """
649
+ Document chunking settings for semantic text splitting.
650
+
651
+ Uses semchunk for semantic-aware text chunking that respects document structure.
652
+ Generous chunk sizes (couple paragraphs) with reasonable overlaps for context.
653
+
654
+ Environment variables:
655
+ CHUNKING__CHUNK_SIZE - Target chunk size in characters
656
+ CHUNKING__OVERLAP - Overlap between chunks in characters
657
+ CHUNKING__MIN_CHUNK_SIZE - Minimum chunk size (avoid tiny chunks)
658
+ CHUNKING__MAX_CHUNK_SIZE - Maximum chunk size (hard limit)
659
+ """
660
+
661
+ model_config = SettingsConfigDict(
662
+ env_prefix="CHUNKING__",
663
+ env_file=".env",
664
+ env_file_encoding="utf-8",
665
+ extra="ignore",
666
+ )
667
+
668
+ chunk_size: int = Field(
669
+ default=1500,
670
+ description="Target chunk size in characters (couple paragraphs, ~300-400 words)",
671
+ )
672
+
673
+ overlap: int = Field(
674
+ default=200,
675
+ description="Overlap between chunks in characters for context preservation",
676
+ )
677
+
678
+ min_chunk_size: int = Field(
679
+ default=100,
680
+ description="Minimum chunk size to avoid tiny fragments",
681
+ )
682
+
683
+ max_chunk_size: int = Field(
684
+ default=2500,
685
+ description="Maximum chunk size (hard limit, prevents oversized chunks)",
686
+ )
687
+
688
+
689
+ class ContentSettings(BaseSettings):
690
+ """
691
+ Content provider settings for file processing.
692
+
693
+ Defines supported file types for each provider type.
694
+ Allows override of specific extensions via register_provider().
695
+
696
+ Environment variables:
697
+ CONTENT__SUPPORTED_TEXT_TYPES - Comma-separated text extensions
698
+ CONTENT__SUPPORTED_DOC_TYPES - Comma-separated document extensions
699
+ CONTENT__SUPPORTED_AUDIO_TYPES - Comma-separated audio extensions
700
+ CONTENT__SUPPORTED_IMAGE_TYPES - Comma-separated image extensions
701
+ CONTENT__IMAGE_VLLM_SAMPLE_RATE - Sampling rate for vision LLM analysis (0.0-1.0)
702
+ CONTENT__IMAGE_VLLM_PROVIDER - Vision provider (anthropic, gemini, openai)
703
+ CONTENT__IMAGE_VLLM_MODEL - Vision model name (provider default if not set)
704
+ CONTENT__CLIP_PROVIDER - CLIP embedding provider (jina, self-hosted)
705
+ CONTENT__CLIP_MODEL - CLIP model name (jina-clip-v1, jina-clip-v2)
706
+ CONTENT__JINA_API_KEY - Jina AI API key for CLIP embeddings
707
+ """
708
+
709
+ model_config = SettingsConfigDict(
710
+ env_prefix="CONTENT__",
711
+ env_file=".env",
712
+ env_file_encoding="utf-8",
713
+ extra="ignore",
714
+ )
715
+
716
+ supported_text_types: list[str] = Field(
717
+ default_factory=lambda: [
718
+ # Plain text
719
+ ".txt",
720
+ ".md",
721
+ ".markdown",
722
+ # Data formats
723
+ ".json",
724
+ ".yaml",
725
+ ".yml",
726
+ ".csv",
727
+ ".tsv",
728
+ ".log",
729
+ # Code files
730
+ ".py",
731
+ ".js",
732
+ ".ts",
733
+ ".tsx",
734
+ ".jsx",
735
+ ".java",
736
+ ".c",
737
+ ".cpp",
738
+ ".h",
739
+ ".rs",
740
+ ".go",
741
+ ".rb",
742
+ ".php",
743
+ ".sh",
744
+ ".bash",
745
+ ".sql",
746
+ # Web files
747
+ ".html",
748
+ ".css",
749
+ ".xml",
750
+ ],
751
+ description="File extensions handled by TextProvider (plain text, code, data files)",
752
+ )
753
+
754
+ supported_doc_types: list[str] = Field(
755
+ default_factory=lambda: [
756
+ # Documents
757
+ ".pdf",
758
+ ".docx",
759
+ ".pptx",
760
+ ".xlsx",
761
+ # Images (OCR text extraction)
762
+ ".png",
763
+ ".jpg",
764
+ ".jpeg",
765
+ ],
766
+ description="File extensions handled by DocProvider (Kreuzberg: PDFs, Office docs, images with OCR)",
767
+ )
768
+
769
+ supported_audio_types: list[str] = Field(
770
+ default_factory=lambda: [
771
+ ".wav",
772
+ ".mp3",
773
+ ".m4a",
774
+ ".flac",
775
+ ".ogg",
776
+ ],
777
+ description="File extensions handled by AudioProvider (Whisper API transcription)",
778
+ )
779
+
780
+ supported_image_types: list[str] = Field(
781
+ default_factory=lambda: [
782
+ ".png",
783
+ ".jpg",
784
+ ".jpeg",
785
+ ".gif",
786
+ ".webp",
787
+ ],
788
+ description="File extensions handled by ImageProvider (vision LLM + CLIP embeddings)",
789
+ )
790
+
791
+ image_vllm_sample_rate: float = Field(
792
+ default=0.0,
793
+ ge=0.0,
794
+ le=1.0,
795
+ description="Sampling rate for vision LLM analysis (0.0 = never, 1.0 = always, 0.1 = 10% of images). Gold tier users always get vision analysis.",
796
+ )
797
+
798
+ image_vllm_provider: str = Field(
799
+ default="anthropic",
800
+ description="Vision LLM provider: anthropic, gemini, or openai",
801
+ )
802
+
803
+ image_vllm_model: str | None = Field(
804
+ default=None,
805
+ description="Vision model name (uses provider default if None)",
806
+ )
807
+
808
+ clip_provider: str = Field(
809
+ default="jina",
810
+ description="CLIP embedding provider (jina for API, self-hosted for future KEDA pods)",
811
+ )
812
+
813
+ clip_model: str = Field(
814
+ default="jina-clip-v2",
815
+ description="CLIP model for image embeddings (jina-clip-v1, jina-clip-v2, or custom)",
816
+ )
817
+
818
+ jina_api_key: str | None = Field(
819
+ default=None,
820
+ description="Jina AI API key for CLIP embeddings (https://jina.ai/embeddings/)",
821
+ )
822
+
823
+
824
+ class SQSSettings(BaseSettings):
825
+ """
826
+ SQS queue settings for file processing.
827
+
828
+ Uses IRSA (IAM Roles for Service Accounts) for AWS permissions in EKS.
829
+ For local development, can use access keys.
830
+
831
+ Environment variables:
832
+ SQS__QUEUE_URL - SQS queue URL (from Pulumi output)
833
+ SQS__REGION - AWS region
834
+ SQS__MAX_MESSAGES - Max messages per receive (1-10)
835
+ SQS__WAIT_TIME_SECONDS - Long polling wait time
836
+ SQS__VISIBILITY_TIMEOUT - Message visibility timeout
837
+ """
838
+
839
+ model_config = SettingsConfigDict(
840
+ env_prefix="SQS__",
841
+ env_file=".env",
842
+ env_file_encoding="utf-8",
843
+ extra="ignore",
844
+ )
845
+
846
+ queue_url: str = Field(
847
+ default="",
848
+ description="SQS queue URL for file processing events",
849
+ )
850
+
851
+ region: str = Field(
852
+ default="us-east-1",
853
+ description="AWS region",
854
+ )
855
+
856
+ max_messages: int = Field(
857
+ default=10,
858
+ ge=1,
859
+ le=10,
860
+ description="Maximum messages to receive per batch (1-10)",
861
+ )
862
+
863
+ wait_time_seconds: int = Field(
864
+ default=20,
865
+ ge=0,
866
+ le=20,
867
+ description="Long polling wait time in seconds (0-20, 20 recommended)",
868
+ )
869
+
870
+ visibility_timeout: int = Field(
871
+ default=300,
872
+ description="Visibility timeout in seconds (should match processing time)",
873
+ )
874
+
875
+
876
+ class ChatSettings(BaseSettings):
877
+ """
878
+ Chat and session context settings.
879
+
880
+ Environment variables:
881
+ CHAT__AUTO_INJECT_USER_CONTEXT - Automatically inject user profile into every request (default: false)
882
+
883
+ Design Philosophy:
884
+ - Session history is ALWAYS loaded (required for multi-turn conversations)
885
+ - Compression with REM LOOKUP hints keeps session history efficient
886
+ - User context is on-demand by default (agents receive REM LOOKUP hints)
887
+ - When auto_inject_user_context enabled, user profile is loaded and injected
888
+
889
+ Session History (always loaded with compression):
890
+ - Each chat request is a single message, so history MUST be recovered
891
+ - Long assistant responses stored as separate Message entities
892
+ - Compressed versions include REM LOOKUP hints: "... [REM LOOKUP session-{id}-msg-{index}] ..."
893
+ - Agent can retrieve full content on-demand using REM LOOKUP
894
+ - Prevents context window bloat while maintaining conversation continuity
895
+
896
+ User Context (on-demand by default):
897
+ - Agent system prompt includes: "User ID: {user_id}. To load user profile: Use REM LOOKUP users/{user_id}"
898
+ - Agent decides whether to load profile based on query
899
+ - More efficient for queries that don't need personalization
900
+
901
+ User Context (auto-inject when enabled):
902
+ - Set CHAT__AUTO_INJECT_USER_CONTEXT=true
903
+ - User profile automatically loaded and injected into system message
904
+ - Simpler for basic chatbots that always need context
905
+ """
906
+
907
+ model_config = SettingsConfigDict(
908
+ env_prefix="CHAT__",
909
+ env_file=".env",
910
+ env_file_encoding="utf-8",
911
+ extra="ignore",
912
+ )
913
+
914
+ auto_inject_user_context: bool = Field(
915
+ default=False,
916
+ description="Automatically inject user profile into every request (default: false, use REM LOOKUP hint instead)",
917
+ )
918
+
919
+
920
+ class APISettings(BaseSettings):
921
+ """
922
+ API server settings.
923
+
924
+ Environment variables:
925
+ API__HOST - Host to bind to (0.0.0.0 for Docker, 127.0.0.1 for local)
926
+ API__PORT - Port to listen on
927
+ API__RELOAD - Enable auto-reload for development
928
+ API__WORKERS - Number of worker processes (production)
929
+ API__LOG_LEVEL - Logging level (debug, info, warning, error)
930
+ """
931
+
932
+ model_config = SettingsConfigDict(
933
+ env_prefix="API__",
934
+ env_file=".env",
935
+ env_file_encoding="utf-8",
936
+ extra="ignore",
937
+ )
938
+
939
+ host: str = Field(
940
+ default="0.0.0.0",
941
+ description="Host to bind to (0.0.0.0 for Docker, 127.0.0.1 for local only)",
942
+ )
943
+
944
+ port: int = Field(
945
+ default=8000,
946
+ description="Port to listen on",
947
+ )
948
+
949
+ reload: bool = Field(
950
+ default=True,
951
+ description="Enable auto-reload for development (disable in production)",
952
+ )
953
+
954
+ workers: int = Field(
955
+ default=1,
956
+ description="Number of worker processes (use >1 in production)",
957
+ )
958
+
959
+ log_level: str = Field(
960
+ default="info",
961
+ description="Logging level (debug, info, warning, error, critical)",
962
+ )
963
+
964
+
965
+ class GitSettings(BaseSettings):
966
+ """
967
+ Git repository provider settings for versioned schema/experiment syncing.
968
+
969
+ Enables syncing of agent schemas, evaluators, and experiments from Git repositories
970
+ using either SSH or HTTPS authentication. Designed for cluster environments where
971
+ secrets are provided via Kubernetes Secrets or similar mechanisms.
972
+
973
+ **Use Cases**:
974
+ - Sync agent schemas from versioned repos (repo/schemas/)
975
+ - Sync experiments and evaluation datasets (repo/experiments/)
976
+ - Clone specific tags/releases for reproducible evaluations
977
+ - Support multi-tenancy with per-tenant repo configurations
978
+
979
+ **Authentication Methods**:
980
+ 1. **SSH** (recommended for production):
981
+ - Uses SSH keys from filesystem or Kubernetes Secrets
982
+ - Path specified via GIT__SSH_KEY_PATH or mounted at /etc/git-secret/ssh
983
+ - Known hosts file at /etc/git-secret/known_hosts
984
+
985
+ 2. **HTTPS with Personal Access Token** (PAT):
986
+ - GitHub: 5,000 API requests/hour per authenticated user
987
+ - GitLab: Similar rate limits
988
+ - Store PAT in GIT__PERSONAL_ACCESS_TOKEN environment variable
989
+
990
+ **Kubernetes Deployment Pattern** (git-sync sidecar):
991
+ ```yaml
992
+ # Secret creation (one-time setup)
993
+ kubectl create secret generic git-creds \\
994
+ --from-file=ssh=$HOME/.ssh/id_rsa \\
995
+ --from-file=known_hosts=$HOME/.ssh/known_hosts
996
+
997
+ # Pod spec with secret mounting
998
+ volumes:
999
+ - name: git-secret
1000
+ secret:
1001
+ secretName: git-creds
1002
+ defaultMode: 0400 # Read-only for owner
1003
+ containers:
1004
+ - name: rem-api
1005
+ volumeMounts:
1006
+ - name: git-secret
1007
+ mountPath: /etc/git-secret
1008
+ readOnly: true
1009
+ securityContext:
1010
+ fsGroup: 65533 # Make secrets readable by git user
1011
+ ```
1012
+
1013
+ **Path Conventions**:
1014
+ - Agent schemas: {repo_root}/schemas/
1015
+ - Experiments: {repo_root}/experiments/
1016
+ - Evaluators: {repo_root}/schemas/evaluators/
1017
+
1018
+ **Performance & Caching**:
1019
+ - Clones cached locally in {cache_dir}/{repo_hash}/
1020
+ - Supports shallow clones (--depth=1) for faster syncing
1021
+ - Periodic refresh via cron jobs or git-sync sidecar
1022
+
1023
+ Environment variables:
1024
+ GIT__ENABLED - Enable Git provider (default: False)
1025
+ GIT__DEFAULT_REPO_URL - Default Git repository URL (ssh:// or https://)
1026
+ GIT__DEFAULT_BRANCH - Default branch to clone (default: main)
1027
+ GIT__SSH_KEY_PATH - Path to SSH private key (default: /etc/git-secret/ssh)
1028
+ GIT__KNOWN_HOSTS_PATH - Path to known_hosts file (default: /etc/git-secret/known_hosts)
1029
+ GIT__PERSONAL_ACCESS_TOKEN - GitHub/GitLab PAT for HTTPS auth
1030
+ GIT__CACHE_DIR - Local cache directory for cloned repos
1031
+ GIT__SHALLOW_CLONE - Use shallow clone (--depth=1) for faster sync
1032
+ GIT__VERIFY_SSL - Verify SSL certificates for HTTPS (default: True)
1033
+
1034
+ **Security Best Practices**:
1035
+ - Store SSH keys in Kubernetes Secrets, never in environment variables
1036
+ - Use read-only SSH keys (deploy keys) with minimal permissions
1037
+ - Enable known_hosts verification to prevent MITM attacks
1038
+ - Rotate PATs regularly (90-day expiration recommended)
1039
+ - Use IRSA/Workload Identity for cloud-provider Git services
1040
+ """
1041
+
1042
+ model_config = SettingsConfigDict(
1043
+ env_prefix="GIT__",
1044
+ env_file=".env",
1045
+ env_file_encoding="utf-8",
1046
+ extra="ignore",
1047
+ )
1048
+
1049
+ enabled: bool = Field(
1050
+ default=False,
1051
+ description="Enable Git provider for syncing schemas/experiments from Git repos",
1052
+ )
1053
+
1054
+ default_repo_url: str | None = Field(
1055
+ default=None,
1056
+ description="Default Git repository URL (ssh://git@github.com/org/repo.git or https://github.com/org/repo.git)",
1057
+ )
1058
+
1059
+ default_branch: str = Field(
1060
+ default="main",
1061
+ description="Default branch to clone/checkout (main, master, develop, etc.)",
1062
+ )
1063
+
1064
+ ssh_key_path: str = Field(
1065
+ default="/etc/git-secret/ssh",
1066
+ description="Path to SSH private key (Kubernetes Secret mount point or local path)",
1067
+ )
1068
+
1069
+ known_hosts_path: str = Field(
1070
+ default="/etc/git-secret/known_hosts",
1071
+ description="Path to known_hosts file for SSH host verification",
1072
+ )
1073
+
1074
+ personal_access_token: str | None = Field(
1075
+ default=None,
1076
+ description="Personal Access Token (PAT) for HTTPS authentication (GitHub, GitLab, etc.)",
1077
+ )
1078
+
1079
+ cache_dir: str = Field(
1080
+ default="/tmp/rem-git-cache",
1081
+ description="Local cache directory for cloned repositories",
1082
+ )
1083
+
1084
+ shallow_clone: bool = Field(
1085
+ default=True,
1086
+ description="Use shallow clone (--depth=1) for faster syncing (recommended for large repos)",
1087
+ )
1088
+
1089
+ verify_ssl: bool = Field(
1090
+ default=True,
1091
+ description="Verify SSL certificates for HTTPS connections (disable for self-signed certs)",
1092
+ )
1093
+
1094
+ sync_interval: int = Field(
1095
+ default=300,
1096
+ description="Sync interval in seconds for git-sync sidecar pattern (default: 5 minutes)",
1097
+ )
1098
+
1099
+
1100
+ class TestSettings(BaseSettings):
1101
+ """
1102
+ Test environment settings.
1103
+
1104
+ Environment variables:
1105
+ TEST__USER_EMAIL - Test user email (default: test@rem.ai)
1106
+ TEST__USER_ID - Test user UUID (auto-generated from email if not provided)
1107
+
1108
+ The user_id is a deterministic UUID v5 generated from the email address.
1109
+ This ensures consistent IDs across test runs and allows tests to use both
1110
+ email and UUID interchangeably.
1111
+ """
1112
+
1113
+ model_config = SettingsConfigDict(
1114
+ env_prefix="TEST__",
1115
+ env_file=".env",
1116
+ env_file_encoding="utf-8",
1117
+ extra="ignore",
1118
+ )
1119
+
1120
+ user_email: str = Field(
1121
+ default="test@rem.ai",
1122
+ description="Test user email address",
1123
+ )
1124
+
1125
+ user_id: str | None = Field(
1126
+ default=None,
1127
+ description="Test user UUID (auto-generated from email if not provided)",
1128
+ )
1129
+
1130
+ @property
1131
+ def effective_user_id(self) -> str:
1132
+ """
1133
+ Get the effective user ID (either explicit or generated from email).
1134
+
1135
+ Returns a deterministic UUID v5 based on the email address if user_id
1136
+ is not explicitly set. This ensures consistent test data across runs.
1137
+ """
1138
+ if self.user_id:
1139
+ return self.user_id
1140
+
1141
+ # Generate deterministic UUID v5 from email
1142
+ # Using DNS namespace as the base (standard practice for email-based UUIDs)
1143
+ import uuid
1144
+ return str(uuid.uuid5(uuid.NAMESPACE_DNS, self.user_email))
1145
+
1146
+
1147
+ class Settings(BaseSettings):
1148
+ """
1149
+ Global application settings.
1150
+
1151
+ Aggregates all nested settings groups with environment variable support.
1152
+ Uses double underscore delimiter for nested variables (LLM__DEFAULT_MODEL).
1153
+
1154
+ Environment variables:
1155
+ TEAM - Team/project name for observability
1156
+ ENVIRONMENT - Environment (development, staging, production)
1157
+ DOMAIN - Public domain for OAuth discovery
1158
+ ROOT_PATH - Root path for reverse proxy (e.g., /rem for ALB routing)
1159
+ TEST__USER_ID - Default user ID for integration tests
1160
+ """
1161
+
1162
+ model_config = SettingsConfigDict(
1163
+ env_file=".env",
1164
+ env_file_encoding="utf-8",
1165
+ env_nested_delimiter="__",
1166
+ extra="ignore",
1167
+ )
1168
+
1169
+ team: str = Field(
1170
+ default="rem",
1171
+ description="Team or project name for observability",
1172
+ )
1173
+
1174
+ environment: str = Field(
1175
+ default="development",
1176
+ description="Environment (development, staging, production)",
1177
+ )
1178
+
1179
+ domain: str | None = Field(
1180
+ default=None,
1181
+ description="Public domain for OAuth discovery (e.g., https://api.example.com)",
1182
+ )
1183
+
1184
+ root_path: str = Field(
1185
+ default="",
1186
+ description="Root path for reverse proxy (e.g., /rem for ALB routing)",
1187
+ )
1188
+
1189
+ sql_dir: str = Field(
1190
+ default="src/rem/sql",
1191
+ description="Directory for SQL files and migrations",
1192
+ )
1193
+
1194
+ # Nested settings groups
1195
+ api: APISettings = Field(default_factory=APISettings)
1196
+ chat: ChatSettings = Field(default_factory=ChatSettings)
1197
+ llm: LLMSettings = Field(default_factory=LLMSettings)
1198
+ mcp: MCPSettings = Field(default_factory=MCPSettings)
1199
+ otel: OTELSettings = Field(default_factory=OTELSettings)
1200
+ phoenix: PhoenixSettings = Field(default_factory=PhoenixSettings)
1201
+ auth: AuthSettings = Field(default_factory=AuthSettings)
1202
+ postgres: PostgresSettings = Field(default_factory=PostgresSettings)
1203
+ migration: MigrationSettings = Field(default_factory=MigrationSettings)
1204
+ storage: StorageSettings = Field(default_factory=StorageSettings)
1205
+ s3: S3Settings = Field(default_factory=S3Settings)
1206
+ git: GitSettings = Field(default_factory=GitSettings)
1207
+ sqs: SQSSettings = Field(default_factory=SQSSettings)
1208
+ chunking: ChunkingSettings = Field(default_factory=ChunkingSettings)
1209
+ content: ContentSettings = Field(default_factory=ContentSettings)
1210
+ test: TestSettings = Field(default_factory=TestSettings)
1211
+
1212
+
1213
+ # Load configuration from ~/.rem/config.yaml before initializing settings
1214
+ # This allows user configuration to be merged with environment variables
1215
+ try:
1216
+ from rem.config import load_config, merge_config_to_env
1217
+
1218
+ _config = load_config()
1219
+ if _config:
1220
+ merge_config_to_env(_config)
1221
+ except ImportError:
1222
+ # config module not available (e.g., during initial setup)
1223
+ pass
1224
+
1225
+ # Global settings singleton
1226
+ settings = Settings()
1227
+
1228
+ # Sync API keys to environment for pydantic-ai
1229
+ # Pydantic AI providers check environment directly, so we need to ensure
1230
+ # API keys from settings (LLM__*_API_KEY) are also available without prefix
1231
+ if settings.llm.openai_api_key and not os.getenv("OPENAI_API_KEY"):
1232
+ os.environ["OPENAI_API_KEY"] = settings.llm.openai_api_key
1233
+
1234
+ if settings.llm.anthropic_api_key and not os.getenv("ANTHROPIC_API_KEY"):
1235
+ os.environ["ANTHROPIC_API_KEY"] = settings.llm.anthropic_api_key