remdb 0.3.7__py3-none-any.whl → 0.3.14__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.
Files changed (43) hide show
  1. rem/__init__.py +129 -2
  2. rem/agentic/context.py +7 -5
  3. rem/agentic/providers/phoenix.py +32 -43
  4. rem/api/README.md +23 -0
  5. rem/api/main.py +27 -2
  6. rem/api/middleware/tracking.py +172 -0
  7. rem/api/routers/auth.py +54 -0
  8. rem/api/routers/chat/completions.py +1 -1
  9. rem/cli/commands/ask.py +13 -10
  10. rem/cli/commands/configure.py +4 -3
  11. rem/cli/commands/db.py +17 -3
  12. rem/cli/commands/experiments.py +76 -72
  13. rem/cli/commands/process.py +8 -7
  14. rem/cli/commands/scaffold.py +47 -0
  15. rem/cli/main.py +2 -0
  16. rem/models/entities/user.py +10 -3
  17. rem/registry.py +367 -0
  18. rem/services/content/providers.py +92 -133
  19. rem/services/dreaming/affinity_service.py +2 -16
  20. rem/services/dreaming/moment_service.py +2 -15
  21. rem/services/embeddings/api.py +20 -13
  22. rem/services/phoenix/EXPERIMENT_DESIGN.md +3 -3
  23. rem/services/phoenix/client.py +148 -14
  24. rem/services/postgres/schema_generator.py +86 -5
  25. rem/services/rate_limit.py +113 -0
  26. rem/services/rem/README.md +14 -0
  27. rem/services/user_service.py +98 -0
  28. rem/settings.py +79 -10
  29. rem/sql/install_models.sql +13 -0
  30. rem/sql/migrations/003_seed_default_user.sql +48 -0
  31. rem/utils/constants.py +97 -0
  32. rem/utils/date_utils.py +228 -0
  33. rem/utils/embeddings.py +17 -4
  34. rem/utils/files.py +167 -0
  35. rem/utils/mime_types.py +158 -0
  36. rem/utils/schema_loader.py +63 -14
  37. rem/utils/vision.py +9 -14
  38. rem/workers/README.md +14 -14
  39. rem/workers/db_maintainer.py +74 -0
  40. {remdb-0.3.7.dist-info → remdb-0.3.14.dist-info}/METADATA +169 -121
  41. {remdb-0.3.7.dist-info → remdb-0.3.14.dist-info}/RECORD +43 -32
  42. {remdb-0.3.7.dist-info → remdb-0.3.14.dist-info}/WHEEL +0 -0
  43. {remdb-0.3.7.dist-info → remdb-0.3.14.dist-info}/entry_points.txt +0 -0
rem/utils/files.py ADDED
@@ -0,0 +1,167 @@
1
+ """
2
+ File utilities for consistent file handling throughout REM.
3
+
4
+ Provides context managers and helpers for temporary file operations,
5
+ ensuring proper cleanup and consistent patterns.
6
+ """
7
+
8
+ import tempfile
9
+ from contextlib import contextmanager
10
+ from pathlib import Path
11
+ from typing import Generator, Optional
12
+
13
+ from loguru import logger
14
+
15
+
16
+ @contextmanager
17
+ def temp_file_from_bytes(
18
+ content: bytes,
19
+ suffix: str = "",
20
+ prefix: str = "rem_",
21
+ dir: Optional[str] = None,
22
+ ) -> Generator[Path, None, None]:
23
+ """
24
+ Create a temporary file from bytes, yield path, cleanup automatically.
25
+
26
+ This context manager ensures proper cleanup of temporary files even
27
+ if an exception occurs during processing.
28
+
29
+ Args:
30
+ content: Bytes to write to the temporary file
31
+ suffix: File extension (e.g., ".pdf", ".wav")
32
+ prefix: Prefix for the temp file name
33
+ dir: Directory for temp file (uses system temp if None)
34
+
35
+ Yields:
36
+ Path to the temporary file
37
+
38
+ Example:
39
+ >>> with temp_file_from_bytes(pdf_bytes, suffix=".pdf") as tmp_path:
40
+ ... result = process_pdf(tmp_path)
41
+ # File is automatically cleaned up after the block
42
+
43
+ Note:
44
+ The file is created with delete=False so we control cleanup.
45
+ This allows the file to be read by external processes.
46
+ """
47
+ tmp_path: Optional[Path] = None
48
+ try:
49
+ with tempfile.NamedTemporaryFile(
50
+ suffix=suffix,
51
+ prefix=prefix,
52
+ dir=dir,
53
+ delete=False,
54
+ ) as tmp:
55
+ tmp.write(content)
56
+ tmp_path = Path(tmp.name)
57
+
58
+ yield tmp_path
59
+
60
+ finally:
61
+ if tmp_path is not None:
62
+ try:
63
+ tmp_path.unlink(missing_ok=True)
64
+ except Exception as e:
65
+ logger.warning(f"Failed to cleanup temp file {tmp_path}: {e}")
66
+
67
+
68
+ @contextmanager
69
+ def temp_file_empty(
70
+ suffix: str = "",
71
+ prefix: str = "rem_",
72
+ dir: Optional[str] = None,
73
+ ) -> Generator[Path, None, None]:
74
+ """
75
+ Create an empty temporary file, yield path, cleanup automatically.
76
+
77
+ Useful when you need to write to a file after creation or when
78
+ an external process will write to the file.
79
+
80
+ Args:
81
+ suffix: File extension
82
+ prefix: Prefix for the temp file name
83
+ dir: Directory for temp file
84
+
85
+ Yields:
86
+ Path to the empty temporary file
87
+ """
88
+ tmp_path: Optional[Path] = None
89
+ try:
90
+ with tempfile.NamedTemporaryFile(
91
+ suffix=suffix,
92
+ prefix=prefix,
93
+ dir=dir,
94
+ delete=False,
95
+ ) as tmp:
96
+ tmp_path = Path(tmp.name)
97
+
98
+ yield tmp_path
99
+
100
+ finally:
101
+ if tmp_path is not None:
102
+ try:
103
+ tmp_path.unlink(missing_ok=True)
104
+ except Exception as e:
105
+ logger.warning(f"Failed to cleanup temp file {tmp_path}: {e}")
106
+
107
+
108
+ @contextmanager
109
+ def temp_directory(
110
+ prefix: str = "rem_",
111
+ dir: Optional[str] = None,
112
+ ) -> Generator[Path, None, None]:
113
+ """
114
+ Create a temporary directory, yield path, cleanup automatically.
115
+
116
+ Args:
117
+ prefix: Prefix for the temp directory name
118
+ dir: Parent directory for temp directory
119
+
120
+ Yields:
121
+ Path to the temporary directory
122
+ """
123
+ import shutil
124
+
125
+ tmp_dir: Optional[Path] = None
126
+ try:
127
+ tmp_dir = Path(tempfile.mkdtemp(prefix=prefix, dir=dir))
128
+ yield tmp_dir
129
+
130
+ finally:
131
+ if tmp_dir is not None:
132
+ try:
133
+ shutil.rmtree(tmp_dir, ignore_errors=True)
134
+ except Exception as e:
135
+ logger.warning(f"Failed to cleanup temp directory {tmp_dir}: {e}")
136
+
137
+
138
+ def ensure_parent_exists(path: Path) -> Path:
139
+ """
140
+ Ensure parent directory exists, creating if necessary.
141
+
142
+ Args:
143
+ path: File path whose parent should exist
144
+
145
+ Returns:
146
+ The original path (for chaining)
147
+ """
148
+ path.parent.mkdir(parents=True, exist_ok=True)
149
+ return path
150
+
151
+
152
+ def safe_delete(path: Path) -> bool:
153
+ """
154
+ Safely delete a file, returning success status.
155
+
156
+ Args:
157
+ path: Path to delete
158
+
159
+ Returns:
160
+ True if deleted or didn't exist, False on error
161
+ """
162
+ try:
163
+ path.unlink(missing_ok=True)
164
+ return True
165
+ except Exception as e:
166
+ logger.warning(f"Failed to delete {path}: {e}")
167
+ return False
@@ -0,0 +1,158 @@
1
+ """
2
+ Centralized MIME type mappings for file format detection.
3
+
4
+ Provides bidirectional mappings between file extensions and MIME types.
5
+ Use these constants throughout the codebase instead of inline dictionaries.
6
+ """
7
+
8
+ # Extension to MIME type mapping (extension includes leading dot)
9
+ EXTENSION_TO_MIME: dict[str, str] = {
10
+ # Images
11
+ ".png": "image/png",
12
+ ".jpg": "image/jpeg",
13
+ ".jpeg": "image/jpeg",
14
+ ".gif": "image/gif",
15
+ ".webp": "image/webp",
16
+ ".bmp": "image/bmp",
17
+ ".tiff": "image/tiff",
18
+ ".svg": "image/svg+xml",
19
+ # Documents
20
+ ".pdf": "application/pdf",
21
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
22
+ ".doc": "application/msword",
23
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
24
+ ".ppt": "application/vnd.ms-powerpoint",
25
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
26
+ ".xls": "application/vnd.ms-excel",
27
+ # Audio
28
+ ".wav": "audio/wav",
29
+ ".mp3": "audio/mpeg",
30
+ ".m4a": "audio/x-m4a",
31
+ ".flac": "audio/flac",
32
+ ".ogg": "audio/ogg",
33
+ ".aac": "audio/aac",
34
+ # Video
35
+ ".mp4": "video/mp4",
36
+ ".webm": "video/webm",
37
+ ".avi": "video/x-msvideo",
38
+ ".mov": "video/quicktime",
39
+ # Text/Code
40
+ ".txt": "text/plain",
41
+ ".md": "text/markdown",
42
+ ".markdown": "text/markdown",
43
+ ".json": "application/json",
44
+ ".yaml": "application/x-yaml",
45
+ ".yml": "application/x-yaml",
46
+ ".xml": "application/xml",
47
+ ".html": "text/html",
48
+ ".css": "text/css",
49
+ ".js": "application/javascript",
50
+ ".py": "text/x-python",
51
+ ".ts": "application/typescript",
52
+ ".csv": "text/csv",
53
+ }
54
+
55
+ # MIME type to extension mapping (reverse of above, preferring shorter extensions)
56
+ MIME_TO_EXTENSION: dict[str, str] = {
57
+ # Images
58
+ "image/png": ".png",
59
+ "image/jpeg": ".jpg",
60
+ "image/gif": ".gif",
61
+ "image/webp": ".webp",
62
+ "image/bmp": ".bmp",
63
+ "image/tiff": ".tiff",
64
+ "image/svg+xml": ".svg",
65
+ # Documents
66
+ "application/pdf": ".pdf",
67
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": ".docx",
68
+ "application/msword": ".doc",
69
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation": ".pptx",
70
+ "application/vnd.ms-powerpoint": ".ppt",
71
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx",
72
+ "application/vnd.ms-excel": ".xls",
73
+ # Audio
74
+ "audio/wav": ".wav",
75
+ "audio/mpeg": ".mp3",
76
+ "audio/x-m4a": ".m4a",
77
+ "audio/mp4": ".m4a",
78
+ "audio/flac": ".flac",
79
+ "audio/ogg": ".ogg",
80
+ "audio/aac": ".aac",
81
+ # Video
82
+ "video/mp4": ".mp4",
83
+ "video/webm": ".webm",
84
+ "video/x-msvideo": ".avi",
85
+ "video/quicktime": ".mov",
86
+ # Text/Code
87
+ "text/plain": ".txt",
88
+ "text/markdown": ".md",
89
+ "application/json": ".json",
90
+ "application/x-yaml": ".yaml",
91
+ "application/xml": ".xml",
92
+ "text/html": ".html",
93
+ "text/css": ".css",
94
+ "application/javascript": ".js",
95
+ "text/x-python": ".py",
96
+ "application/typescript": ".ts",
97
+ "text/csv": ".csv",
98
+ }
99
+
100
+ # Grouped by category for convenience
101
+ IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".tiff", ".svg"}
102
+ DOCUMENT_EXTENSIONS = {".pdf", ".docx", ".doc", ".pptx", ".ppt", ".xlsx", ".xls"}
103
+ AUDIO_EXTENSIONS = {".wav", ".mp3", ".m4a", ".flac", ".ogg", ".aac"}
104
+ VIDEO_EXTENSIONS = {".mp4", ".webm", ".avi", ".mov"}
105
+ TEXT_EXTENSIONS = {".txt", ".md", ".markdown", ".json", ".yaml", ".yml", ".xml", ".html", ".css", ".js", ".py", ".ts", ".csv"}
106
+
107
+
108
+ def get_extension(mime_type: str, default: str = ".bin") -> str:
109
+ """
110
+ Get file extension for a MIME type.
111
+
112
+ Args:
113
+ mime_type: MIME type string (e.g., "image/png")
114
+ default: Default extension if MIME type not found
115
+
116
+ Returns:
117
+ File extension with leading dot (e.g., ".png")
118
+ """
119
+ return MIME_TO_EXTENSION.get(mime_type, default)
120
+
121
+
122
+ def get_mime_type(extension: str, default: str = "application/octet-stream") -> str:
123
+ """
124
+ Get MIME type for a file extension.
125
+
126
+ Args:
127
+ extension: File extension with or without leading dot
128
+ default: Default MIME type if extension not found
129
+
130
+ Returns:
131
+ MIME type string (e.g., "image/png")
132
+ """
133
+ # Normalize extension to have leading dot
134
+ ext = extension if extension.startswith(".") else f".{extension}"
135
+ return EXTENSION_TO_MIME.get(ext.lower(), default)
136
+
137
+
138
+ def is_image(extension_or_mime: str) -> bool:
139
+ """Check if extension or MIME type represents an image."""
140
+ if extension_or_mime.startswith("."):
141
+ return extension_or_mime.lower() in IMAGE_EXTENSIONS
142
+ return extension_or_mime.startswith("image/")
143
+
144
+
145
+ def is_audio(extension_or_mime: str) -> bool:
146
+ """Check if extension or MIME type represents audio."""
147
+ if extension_or_mime.startswith("."):
148
+ return extension_or_mime.lower() in AUDIO_EXTENSIONS
149
+ return extension_or_mime.startswith("audio/")
150
+
151
+
152
+ def is_document(extension_or_mime: str) -> bool:
153
+ """Check if extension or MIME type represents a document."""
154
+ if extension_or_mime.startswith("."):
155
+ return extension_or_mime.lower() in DOCUMENT_EXTENSIONS
156
+ # Check common document MIME types
157
+ doc_mimes = {"application/pdf", "application/msword"}
158
+ return extension_or_mime in doc_mimes or "officedocument" in extension_or_mime
@@ -9,7 +9,7 @@ Design Pattern:
9
9
  - Support short names: "contract-analyzer" → "schemas/agents/contract-analyzer.yaml"
10
10
  - Support relative/absolute paths
11
11
  - Consistent error messages and logging
12
- i
12
+
13
13
  Usage:
14
14
  # From API
15
15
  schema = load_agent_schema("rem")
@@ -20,6 +20,26 @@ Usage:
20
20
  # From agent factory
21
21
  schema = load_agent_schema("contract-analyzer")
22
22
 
23
+ TODO: Git FS Integration
24
+ The schema loader currently uses importlib.resources for package schemas
25
+ and direct filesystem access for custom paths. The FS abstraction layer
26
+ (rem.services.fs.FS) could be used to abstract storage backends:
27
+
28
+ - Local filesystem (current)
29
+ - Git repositories (GitService)
30
+ - S3 (via FS provider)
31
+
32
+ This would enable loading schemas from versioned Git repos or S3 buckets
33
+ without changing the API. The FS provider pattern already exists and just
34
+ needs integration testing with the schema loader.
35
+
36
+ Example future usage:
37
+ # Load from Git at specific version
38
+ schema = load_agent_schema("git://rem/schemas/agents/rem.yaml?ref=v1.0.0")
39
+
40
+ # Load from S3
41
+ schema = load_agent_schema("s3://rem-schemas/agents/cv-parser.yaml")
42
+
23
43
  Schema Caching Status:
24
44
 
25
45
  ✅ IMPLEMENTED: Filesystem Schema Caching (2025-11-22)
@@ -71,13 +91,14 @@ import yaml
71
91
  from loguru import logger
72
92
 
73
93
 
74
- # Standard search paths for agent schemas (in priority order)
94
+ # Standard search paths for agent/evaluator schemas (in priority order)
75
95
  SCHEMA_SEARCH_PATHS = [
76
96
  "schemas/agents/{name}.yaml", # Top-level agents (e.g., rem.yaml)
77
97
  "schemas/agents/core/{name}.yaml", # Core system agents
78
98
  "schemas/agents/examples/{name}.yaml", # Example agents
79
- "schemas/evaluators/{name}.yaml",
80
- "schemas/{name}.yaml",
99
+ "schemas/evaluators/{name}.yaml", # Nested evaluators (e.g., hello-world/default)
100
+ "schemas/evaluators/rem/{name}.yaml", # REM evaluators (e.g., lookup-correctness)
101
+ "schemas/{name}.yaml", # Generic schemas
81
102
  ]
82
103
 
83
104
  # In-memory cache for filesystem schemas (no TTL - immutable)
@@ -188,12 +209,13 @@ def load_agent_schema(
188
209
  Search Order:
189
210
  1. Check cache (if use_cache=True and schema found in FS cache)
190
211
  2. Exact path if it exists (absolute or relative)
191
- 3. Package resources: schemas/agents/{name}.yaml (top-level)
192
- 4. Package resources: schemas/agents/core/{name}.yaml
193
- 5. Package resources: schemas/agents/examples/{name}.yaml
194
- 6. Package resources: schemas/evaluators/{name}.yaml
195
- 7. Package resources: schemas/{name}.yaml
196
- 8. Database LOOKUP: schemas table (if enable_db_fallback=True and user_id provided)
212
+ 3. Custom paths from rem.register_schema_path() and SCHEMA__PATHS env var
213
+ 4. Package resources: schemas/agents/{name}.yaml (top-level)
214
+ 5. Package resources: schemas/agents/core/{name}.yaml
215
+ 6. Package resources: schemas/agents/examples/{name}.yaml
216
+ 7. Package resources: schemas/evaluators/{name}.yaml
217
+ 8. Package resources: schemas/{name}.yaml
218
+ 9. Database LOOKUP: schemas table (if enable_db_fallback=True and user_id provided)
197
219
 
198
220
  Args:
199
221
  schema_name_or_path: Schema name or file path
@@ -247,7 +269,28 @@ def load_agent_schema(
247
269
  # 2. Normalize name for package resource search
248
270
  base_name = cache_key
249
271
 
250
- # 3. Try package resources with standard search paths
272
+ # 3. Try custom schema paths (from registry + SCHEMA__PATHS env var)
273
+ from ..registry import get_schema_paths
274
+
275
+ custom_paths = get_schema_paths()
276
+ for custom_dir in custom_paths:
277
+ # Try various patterns within each custom directory
278
+ for pattern in [
279
+ f"{base_name}.yaml",
280
+ f"{base_name}.yml",
281
+ f"agents/{base_name}.yaml",
282
+ f"evaluators/{base_name}.yaml",
283
+ ]:
284
+ custom_path = Path(custom_dir) / pattern
285
+ if custom_path.exists():
286
+ logger.debug(f"Loading schema from custom path: {custom_path}")
287
+ with open(custom_path, "r") as f:
288
+ schema = yaml.safe_load(f)
289
+ logger.debug(f"Loaded schema with keys: {list(schema.keys())}")
290
+ # Don't cache custom paths (they may change during development)
291
+ return cast(dict[str, Any], schema)
292
+
293
+ # 4. Try package resources with standard search paths
251
294
  for search_pattern in SCHEMA_SEARCH_PATHS:
252
295
  search_path = search_pattern.format(name=base_name)
253
296
 
@@ -272,7 +315,7 @@ def load_agent_schema(
272
315
  logger.debug(f"Could not load from {search_path}: {e}")
273
316
  continue
274
317
 
275
- # 4. Try database LOOKUP fallback (if enabled and user_id provided)
318
+ # 5. Try database LOOKUP fallback (if enabled and user_id provided)
276
319
  if enable_db_fallback and user_id:
277
320
  try:
278
321
  logger.debug(f"Attempting database LOOKUP for schema: {base_name} (user_id={user_id})")
@@ -284,8 +327,13 @@ def load_agent_schema(
284
327
  logger.debug(f"Database schema lookup failed: {e}")
285
328
  # Fall through to error below
286
329
 
287
- # 5. Schema not found in any location
330
+ # 6. Schema not found in any location
288
331
  searched_paths = [pattern.format(name=base_name) for pattern in SCHEMA_SEARCH_PATHS]
332
+
333
+ custom_paths_note = ""
334
+ if custom_paths:
335
+ custom_paths_note = f"\n - Custom paths: {', '.join(custom_paths)}"
336
+
289
337
  db_search_note = ""
290
338
  if enable_db_fallback:
291
339
  if user_id:
@@ -296,7 +344,8 @@ def load_agent_schema(
296
344
  raise FileNotFoundError(
297
345
  f"Schema not found: {schema_name_or_path}\n"
298
346
  f"Searched locations:\n"
299
- f" - Exact path: {path}\n"
347
+ f" - Exact path: {path}"
348
+ f"{custom_paths_note}\n"
300
349
  f" - Package resources: {', '.join(searched_paths)}"
301
350
  f"{db_search_note}"
302
351
  )
rem/utils/vision.py CHANGED
@@ -11,7 +11,6 @@ markdown descriptions of images.
11
11
  """
12
12
 
13
13
  import base64
14
- import os
15
14
  from enum import Enum
16
15
  from pathlib import Path
17
16
  from typing import Optional
@@ -19,6 +18,9 @@ from typing import Optional
19
18
  import requests
20
19
  from loguru import logger
21
20
 
21
+ from rem.utils.constants import HTTP_TIMEOUT_LONG, VISION_MAX_TOKENS
22
+ from rem.utils.mime_types import EXTENSION_TO_MIME
23
+
22
24
 
23
25
  class VisionProvider(str, Enum):
24
26
  """Supported vision providers."""
@@ -141,14 +143,7 @@ class ImageAnalyzer:
141
143
 
142
144
  # Detect media type
143
145
  suffix = image_path.suffix.lower()
144
- media_type_map = {
145
- ".png": "image/png",
146
- ".jpg": "image/jpeg",
147
- ".jpeg": "image/jpeg",
148
- ".gif": "image/gif",
149
- ".webp": "image/webp",
150
- }
151
- media_type = media_type_map.get(suffix, "image/png")
146
+ media_type = EXTENSION_TO_MIME.get(suffix, "image/png")
152
147
 
153
148
  logger.info(f"Analyzing {image_path.name} with {self.provider.value} ({self.model})")
154
149
 
@@ -190,7 +185,7 @@ class ImageAnalyzer:
190
185
 
191
186
  body = {
192
187
  "model": self.model,
193
- "max_tokens": 2048,
188
+ "max_tokens": VISION_MAX_TOKENS,
194
189
  "messages": [
195
190
  {
196
191
  "role": "user",
@@ -216,7 +211,7 @@ class ImageAnalyzer:
216
211
  "https://api.anthropic.com/v1/messages",
217
212
  headers=headers,
218
213
  json=body,
219
- timeout=60.0,
214
+ timeout=HTTP_TIMEOUT_LONG,
220
215
  )
221
216
 
222
217
  if response.status_code != 200:
@@ -261,7 +256,7 @@ class ImageAnalyzer:
261
256
  url,
262
257
  params=params,
263
258
  json=body,
264
- timeout=60.0,
259
+ timeout=HTTP_TIMEOUT_LONG,
265
260
  )
266
261
 
267
262
  if response.status_code != 200:
@@ -311,14 +306,14 @@ class ImageAnalyzer:
311
306
  ],
312
307
  }
313
308
  ],
314
- "max_tokens": 2048,
309
+ "max_tokens": VISION_MAX_TOKENS,
315
310
  }
316
311
 
317
312
  response = requests.post(
318
313
  url,
319
314
  headers=headers,
320
315
  json=body,
321
- timeout=60.0,
316
+ timeout=HTTP_TIMEOUT_LONG,
322
317
  )
323
318
 
324
319
  if response.status_code != 200:
rem/workers/README.md CHANGED
@@ -207,7 +207,7 @@ Reads recent activity to generate comprehensive user profiles.
207
207
 
208
208
  **CLI:**
209
209
  ```bash
210
- rem-dreaming user-model --tenant-id=tenant-123
210
+ rem-dreaming user-model
211
211
  ```
212
212
 
213
213
  **Frequency:** Daily (runs as part of full workflow)
@@ -235,13 +235,13 @@ Extracts temporal narratives from resources.
235
235
  **CLI:**
236
236
  ```bash
237
237
  # Process last 24 hours
238
- rem-dreaming moments --tenant-id=tenant-123
238
+ rem-dreaming moments
239
239
 
240
240
  # Custom lookback
241
- rem-dreaming moments --tenant-id=tenant-123 --lookback-hours=48
241
+ rem-dreaming moments --lookback-hours=48
242
242
 
243
243
  # Limit resources processed
244
- rem-dreaming moments --tenant-id=tenant-123 --limit=100
244
+ rem-dreaming moments --limit=100
245
245
  ```
246
246
 
247
247
  **Frequency:** Daily or on-demand
@@ -283,13 +283,13 @@ Builds semantic relationships between resources.
283
283
  **CLI:**
284
284
  ```bash
285
285
  # Semantic mode (fast, cheap)
286
- rem-dreaming affinity --tenant-id=tenant-123
286
+ rem-dreaming affinity
287
287
 
288
288
  # LLM mode (intelligent, expensive)
289
- rem-dreaming affinity --tenant-id=tenant-123 --use-llm --limit=100
289
+ rem-dreaming affinity --use-llm --limit=100
290
290
 
291
291
  # Custom lookback
292
- rem-dreaming affinity --tenant-id=tenant-123 --lookback-hours=168
292
+ rem-dreaming affinity --lookback-hours=168
293
293
  ```
294
294
 
295
295
  **Frequency:**
@@ -308,13 +308,13 @@ Runs all operations in sequence.
308
308
  **CLI:**
309
309
  ```bash
310
310
  # Single tenant
311
- rem-dreaming full --tenant-id=tenant-123
311
+ rem-dreaming full
312
312
 
313
313
  # All active tenants (daily cron)
314
314
  rem-dreaming full --all-tenants
315
315
 
316
316
  # Use LLM affinity mode
317
- rem-dreaming full --tenant-id=tenant-123 --use-llm-affinity
317
+ rem-dreaming full --use-llm-affinity
318
318
  ```
319
319
 
320
320
  **Frequency:** Daily at 3 AM UTC
@@ -455,16 +455,16 @@ export REM_API_URL=http://localhost:8000
455
455
  export OPENAI_API_KEY=sk-...
456
456
 
457
457
  # Run user model update
458
- python -m rem.cli.dreaming user-model --tenant-id=tenant-test
458
+ python -m rem.cli.dreaming user-model
459
459
 
460
460
  # Run moment construction
461
- python -m rem.cli.dreaming moments --tenant-id=tenant-test --lookback-hours=24
461
+ python -m rem.cli.dreaming moments --lookback-hours=24
462
462
 
463
463
  # Run affinity (semantic mode)
464
- python -m rem.cli.dreaming affinity --tenant-id=tenant-test
464
+ python -m rem.cli.dreaming affinity
465
465
 
466
466
  # Run full workflow
467
- python -m rem.cli.dreaming full --tenant-id=tenant-test
467
+ python -m rem.cli.dreaming full
468
468
  ```
469
469
 
470
470
  ### Testing with Docker
@@ -478,7 +478,7 @@ docker run --rm \
478
478
  -e REM_API_URL=http://host.docker.internal:8000 \
479
479
  -e OPENAI_API_KEY=$OPENAI_API_KEY \
480
480
  rem-stack:latest \
481
- python -m rem.cli.dreaming full --tenant-id=tenant-test
481
+ python -m rem.cli.dreaming full
482
482
  ```
483
483
 
484
484
  ## Architecture Decisions