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
@@ -0,0 +1,360 @@
1
+ """Session message compression and rehydration for efficient context loading.
2
+
3
+ This module implements message compression to keep conversation history within
4
+ context windows while preserving full content via REM LOOKUP.
5
+
6
+ Design Pattern:
7
+ - Long assistant messages (>400 chars) are stored as separate Message entities
8
+ - In-memory conversation uses truncated versions with REM lookup hints
9
+ - Full content retrieved on-demand via LOOKUP queries
10
+ - Compression disabled when Postgres is disabled
11
+ """
12
+
13
+ from typing import Any
14
+
15
+ from loguru import logger
16
+
17
+ from rem.models.entities import Message
18
+ from rem.services.postgres import PostgresService, Repository
19
+ from rem.settings import settings
20
+
21
+
22
+ class MessageCompressor:
23
+ """Compress and decompress session messages with REM lookup keys."""
24
+
25
+ def __init__(self, truncate_length: int = 200):
26
+ """
27
+ Initialize message compressor.
28
+
29
+ Args:
30
+ truncate_length: Number of characters to keep from start/end (default: 200)
31
+ """
32
+ self.truncate_length = truncate_length
33
+ self.min_length_for_compression = truncate_length * 2
34
+
35
+ def compress_message(
36
+ self, message: dict[str, Any], entity_key: str | None = None
37
+ ) -> dict[str, Any]:
38
+ """
39
+ Compress a message by truncating long content and adding REM lookup key.
40
+
41
+ Args:
42
+ message: Message dict with role and content
43
+ entity_key: Optional REM lookup key for full message recovery
44
+
45
+ Returns:
46
+ Compressed message dict
47
+ """
48
+ content = message.get("content", "")
49
+
50
+ # Don't compress short messages or system messages
51
+ if (
52
+ len(content) <= self.min_length_for_compression
53
+ or message.get("role") == "system"
54
+ ):
55
+ return message.copy()
56
+
57
+ # Compress long messages
58
+ n = self.truncate_length
59
+ start = content[:n]
60
+ end = content[-n:]
61
+
62
+ # Create compressed content with REM lookup hint
63
+ if entity_key:
64
+ compressed_content = f"{start}\n\n... [Message truncated - REM LOOKUP {entity_key} to recover full content] ...\n\n{end}"
65
+ else:
66
+ compressed_content = f"{start}\n\n... [Message truncated - {len(content) - 2*n} characters omitted] ...\n\n{end}"
67
+
68
+ compressed_message = message.copy()
69
+ compressed_message["content"] = compressed_content
70
+ compressed_message["_compressed"] = True
71
+ compressed_message["_original_length"] = len(content)
72
+ if entity_key:
73
+ compressed_message["_entity_key"] = entity_key
74
+
75
+ logger.debug(
76
+ f"Compressed message from {len(content)} to {len(compressed_content)} chars (key={entity_key})"
77
+ )
78
+
79
+ return compressed_message
80
+
81
+ def decompress_message(
82
+ self, message: dict[str, Any], full_content: str
83
+ ) -> dict[str, Any]:
84
+ """
85
+ Decompress a message by restoring full content.
86
+
87
+ Args:
88
+ message: Compressed message dict
89
+ full_content: Full content to restore
90
+
91
+ Returns:
92
+ Decompressed message dict
93
+ """
94
+ decompressed = message.copy()
95
+ decompressed["content"] = full_content
96
+ decompressed.pop("_compressed", None)
97
+ decompressed.pop("_original_length", None)
98
+ decompressed.pop("_entity_key", None)
99
+
100
+ return decompressed
101
+
102
+ def is_compressed(self, message: dict[str, Any]) -> bool:
103
+ """Check if a message is compressed."""
104
+ return message.get("_compressed", False)
105
+
106
+ def get_entity_key(self, message: dict[str, Any]) -> str | None:
107
+ """Get REM lookup key from compressed message."""
108
+ return message.get("_entity_key")
109
+
110
+
111
+ class SessionMessageStore:
112
+ """Store and retrieve session messages with compression."""
113
+
114
+ def __init__(
115
+ self,
116
+ user_id: str,
117
+ compressor: MessageCompressor | None = None,
118
+ ):
119
+ """
120
+ Initialize session message store.
121
+
122
+ Args:
123
+ user_id: User identifier for data isolation
124
+ compressor: Optional message compressor (creates default if None)
125
+ """
126
+ self.user_id = user_id
127
+ self.compressor = compressor or MessageCompressor()
128
+ self.repo = Repository(Message)
129
+
130
+ async def store_message(
131
+ self,
132
+ session_id: str,
133
+ message: dict[str, Any],
134
+ message_index: int,
135
+ user_id: str | None = None,
136
+ ) -> str:
137
+ """
138
+ Store a long assistant message as a Message entity for REM lookup.
139
+
140
+ Args:
141
+ session_id: Parent session identifier
142
+ message: Message dict to store
143
+ message_index: Index of message in conversation
144
+ user_id: Optional user identifier
145
+
146
+ Returns:
147
+ Entity key for REM lookup (message ID)
148
+ """
149
+ if not settings.postgres.enabled:
150
+ logger.debug("Postgres disabled, skipping message storage")
151
+ return f"msg-{message_index}"
152
+
153
+ # Create entity key for REM LOOKUP: session-{session_id}-msg-{index}
154
+ entity_key = f"session-{session_id}-msg-{message_index}"
155
+
156
+ # Create Message entity for assistant response
157
+ msg = Message(
158
+ content=message.get("content", ""),
159
+ message_type=message.get("role", "assistant"),
160
+ session_id=session_id,
161
+ tenant_id=self.user_id, # Set tenant_id to user_id (application scoped to user)
162
+ user_id=user_id or self.user_id,
163
+ metadata={
164
+ "message_index": message_index,
165
+ "entity_key": entity_key, # Store entity key for LOOKUP
166
+ "timestamp": message.get("timestamp"),
167
+ },
168
+ )
169
+
170
+ # Store in database
171
+ await self.repo.upsert(msg)
172
+
173
+ logger.debug(f"Stored assistant response: {entity_key} (id={msg.id})")
174
+ return entity_key
175
+
176
+ async def retrieve_message(self, entity_key: str) -> str | None:
177
+ """
178
+ Retrieve full message content by REM lookup key.
179
+
180
+ Uses LOOKUP query pattern: finds message by entity_key in metadata.
181
+
182
+ Args:
183
+ entity_key: REM lookup key (session-{id}-msg-{index})
184
+
185
+ Returns:
186
+ Full message content or None if not found
187
+ """
188
+ if not settings.postgres.enabled:
189
+ logger.debug("Postgres disabled, cannot retrieve message")
190
+ return None
191
+
192
+ try:
193
+ # LOOKUP pattern: find message by entity_key in metadata
194
+ query = """
195
+ SELECT * FROM messages
196
+ WHERE metadata->>'entity_key' = $1
197
+ AND user_id = $2
198
+ AND deleted_at IS NULL
199
+ LIMIT 1
200
+ """
201
+
202
+ if not self.repo.db:
203
+ logger.warning("Database not available for message lookup")
204
+ return None
205
+
206
+ row = await self.repo.db.fetchrow(query, entity_key, self.user_id)
207
+
208
+ if row:
209
+ msg = Message.model_validate(dict(row))
210
+ logger.debug(f"Retrieved message via LOOKUP: {entity_key}")
211
+ return msg.content
212
+
213
+ logger.warning(f"Message not found via LOOKUP: {entity_key}")
214
+ return None
215
+
216
+ except Exception as e:
217
+ logger.error(f"Failed to retrieve message {entity_key}: {e}")
218
+ return None
219
+
220
+ async def store_session_messages(
221
+ self,
222
+ session_id: str,
223
+ messages: list[dict[str, Any]],
224
+ user_id: str | None = None,
225
+ compress: bool = True,
226
+ ) -> list[dict[str, Any]]:
227
+ """
228
+ Store all session messages and return compressed versions.
229
+
230
+ Args:
231
+ session_id: Session identifier
232
+ messages: List of messages to store
233
+ user_id: Optional user identifier
234
+ compress: Whether to compress messages (default: True)
235
+
236
+ Returns:
237
+ List of compressed messages with REM lookup keys
238
+ """
239
+ if not settings.postgres.enabled:
240
+ logger.debug("Postgres disabled, returning messages uncompressed")
241
+ return messages
242
+
243
+ compressed_messages = []
244
+
245
+ for idx, message in enumerate(messages):
246
+ content = message.get("content", "")
247
+
248
+ # Only store and compress long assistant responses
249
+ if (
250
+ message.get("role") == "assistant"
251
+ and len(content) > self.compressor.min_length_for_compression
252
+ ):
253
+ # Store full message as separate Message entity
254
+ entity_key = await self.store_message(
255
+ session_id, message, idx, user_id
256
+ )
257
+
258
+ if compress:
259
+ compressed_msg = self.compressor.compress_message(
260
+ message, entity_key
261
+ )
262
+ compressed_messages.append(compressed_msg)
263
+ else:
264
+ msg_copy = message.copy()
265
+ msg_copy["_entity_key"] = entity_key
266
+ compressed_messages.append(msg_copy)
267
+ else:
268
+ # Short assistant messages, user messages, and system messages stored as-is
269
+ # Store ALL messages in database for full audit trail
270
+ msg = Message(
271
+ content=content,
272
+ message_type=message.get("role", "user"),
273
+ session_id=session_id,
274
+ tenant_id=self.user_id, # Set tenant_id to user_id (application scoped to user)
275
+ user_id=user_id or self.user_id,
276
+ metadata={
277
+ "message_index": idx,
278
+ "timestamp": message.get("timestamp"),
279
+ },
280
+ )
281
+ await self.repo.upsert(msg)
282
+ compressed_messages.append(message.copy())
283
+
284
+ return compressed_messages
285
+
286
+ async def load_session_messages(
287
+ self, session_id: str, user_id: str | None = None, decompress: bool = False
288
+ ) -> list[dict[str, Any]]:
289
+ """
290
+ Load session messages from database.
291
+
292
+ Args:
293
+ session_id: Session identifier
294
+ user_id: Optional user identifier for filtering
295
+ decompress: Whether to decompress messages (default: False)
296
+
297
+ Returns:
298
+ List of session messages in chronological order
299
+ """
300
+ if not settings.postgres.enabled:
301
+ logger.debug("Postgres disabled, returning empty message list")
302
+ return []
303
+
304
+ try:
305
+ # Load messages from repository
306
+ # Note: tenant_id column in messages table maps to user_id (user-scoped partitioning)
307
+ filters = {"session_id": session_id, "tenant_id": self.user_id}
308
+ if user_id:
309
+ filters["user_id"] = user_id
310
+
311
+ messages = await self.repo.find(filters, order_by="created_at ASC")
312
+
313
+ # Convert Message entities to dict format
314
+ message_dicts = []
315
+ for msg in messages:
316
+ msg_dict = {
317
+ "role": msg.message_type or "assistant",
318
+ "content": msg.content,
319
+ "timestamp": msg.created_at.isoformat() if msg.created_at else None,
320
+ }
321
+
322
+ # Check if message was compressed
323
+ entity_key: str | None = msg.metadata.get("entity_key") if msg.metadata else None
324
+ if entity_key and len(msg.content) <= self.compressor.min_length_for_compression:
325
+ # This is a compressed reference, mark it
326
+ msg_dict["_compressed"] = True
327
+ msg_dict["_entity_key"] = entity_key
328
+ msg_dict["_original_length"] = msg.metadata.get("original_length", 0)
329
+
330
+ message_dicts.append(msg_dict)
331
+
332
+ # Decompress if requested
333
+ if decompress:
334
+ decompressed_messages = []
335
+ for message in message_dicts:
336
+ if self.compressor.is_compressed(message):
337
+ entity_key = self.compressor.get_entity_key(message)
338
+ if entity_key:
339
+ full_content = await self.retrieve_message(entity_key)
340
+ if full_content:
341
+ decompressed_messages.append(
342
+ self.compressor.decompress_message(
343
+ message, full_content
344
+ )
345
+ )
346
+ else:
347
+ # Fallback to compressed version if retrieval fails
348
+ decompressed_messages.append(message)
349
+ else:
350
+ decompressed_messages.append(message)
351
+ else:
352
+ decompressed_messages.append(message)
353
+
354
+ return decompressed_messages
355
+
356
+ return message_dicts
357
+
358
+ except Exception as e:
359
+ logger.error(f"Failed to load session messages: {e}")
360
+ return []
@@ -0,0 +1,77 @@
1
+ """Session reloading logic for conversation history restoration.
2
+
3
+ This module implements session history loading from the database,
4
+ allowing conversations to be resumed across multiple API calls.
5
+
6
+ Design Pattern:
7
+ - Session identified by session_id from X-Session-Id header
8
+ - All messages for session loaded in chronological order
9
+ - Optional decompression of long assistant messages via REM LOOKUP
10
+ - Gracefully handles missing database (returns empty history)
11
+ """
12
+
13
+ from loguru import logger
14
+
15
+ from rem.services.session.compression import SessionMessageStore
16
+ from rem.settings import settings
17
+
18
+
19
+ async def reload_session(
20
+ session_id: str,
21
+ user_id: str,
22
+ decompress_messages: bool = False,
23
+ ) -> list[dict]:
24
+ """
25
+ Reload all messages for a session from the database.
26
+
27
+ Args:
28
+ session_id: Session/conversation identifier
29
+ user_id: User identifier for data isolation
30
+ decompress_messages: Whether to decompress long messages via REM LOOKUP
31
+
32
+ Returns:
33
+ List of message dicts in chronological order (oldest first)
34
+
35
+ Example:
36
+ ```python
37
+ # In completions endpoint
38
+ context = AgentContext.from_headers(dict(request.headers))
39
+
40
+ # Reload previous conversation history
41
+ history = await reload_session(
42
+ session_id=context.session_id,
43
+ user_id=context.user_id,
44
+ decompress_messages=False, # Use compressed versions for efficiency
45
+ )
46
+
47
+ # Combine with new user message
48
+ messages = history + [{"role": "user", "content": prompt}]
49
+ ```
50
+ """
51
+ if not settings.postgres.enabled:
52
+ logger.debug("Postgres disabled, returning empty session history")
53
+ return []
54
+
55
+ if not session_id:
56
+ logger.debug("No session_id provided, returning empty history")
57
+ return []
58
+
59
+ try:
60
+ # Create message store for this session
61
+ store = SessionMessageStore(user_id=user_id)
62
+
63
+ # Load messages (optionally decompressed)
64
+ messages = await store.load_session_messages(
65
+ session_id=session_id, user_id=user_id, decompress=decompress_messages
66
+ )
67
+
68
+ logger.info(
69
+ f"Reloaded {len(messages)} messages for session {session_id} "
70
+ f"(decompressed={decompress_messages})"
71
+ )
72
+
73
+ return messages
74
+
75
+ except Exception as e:
76
+ logger.error(f"Failed to reload session {session_id}: {e}")
77
+ return []