remdb 0.2.6__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.
- rem/__init__.py +2 -0
- rem/agentic/README.md +650 -0
- rem/agentic/__init__.py +39 -0
- rem/agentic/agents/README.md +155 -0
- rem/agentic/agents/__init__.py +8 -0
- rem/agentic/context.py +148 -0
- rem/agentic/context_builder.py +329 -0
- rem/agentic/mcp/__init__.py +0 -0
- rem/agentic/mcp/tool_wrapper.py +107 -0
- rem/agentic/otel/__init__.py +5 -0
- rem/agentic/otel/setup.py +151 -0
- rem/agentic/providers/phoenix.py +674 -0
- rem/agentic/providers/pydantic_ai.py +572 -0
- rem/agentic/query.py +117 -0
- rem/agentic/query_helper.py +89 -0
- rem/agentic/schema.py +396 -0
- rem/agentic/serialization.py +245 -0
- rem/agentic/tools/__init__.py +5 -0
- rem/agentic/tools/rem_tools.py +231 -0
- rem/api/README.md +420 -0
- rem/api/main.py +324 -0
- rem/api/mcp_router/prompts.py +182 -0
- rem/api/mcp_router/resources.py +536 -0
- rem/api/mcp_router/server.py +213 -0
- rem/api/mcp_router/tools.py +584 -0
- rem/api/routers/auth.py +229 -0
- rem/api/routers/chat/__init__.py +5 -0
- rem/api/routers/chat/completions.py +281 -0
- rem/api/routers/chat/json_utils.py +76 -0
- rem/api/routers/chat/models.py +124 -0
- rem/api/routers/chat/streaming.py +185 -0
- rem/auth/README.md +258 -0
- rem/auth/__init__.py +26 -0
- rem/auth/middleware.py +100 -0
- rem/auth/providers/__init__.py +13 -0
- rem/auth/providers/base.py +376 -0
- rem/auth/providers/google.py +163 -0
- rem/auth/providers/microsoft.py +237 -0
- rem/cli/README.md +455 -0
- rem/cli/__init__.py +8 -0
- rem/cli/commands/README.md +126 -0
- rem/cli/commands/__init__.py +3 -0
- rem/cli/commands/ask.py +565 -0
- rem/cli/commands/configure.py +423 -0
- rem/cli/commands/db.py +493 -0
- rem/cli/commands/dreaming.py +324 -0
- rem/cli/commands/experiments.py +1124 -0
- rem/cli/commands/mcp.py +66 -0
- rem/cli/commands/process.py +245 -0
- rem/cli/commands/schema.py +183 -0
- rem/cli/commands/serve.py +106 -0
- rem/cli/dreaming.py +363 -0
- rem/cli/main.py +88 -0
- rem/config.py +237 -0
- rem/mcp_server.py +41 -0
- rem/models/core/__init__.py +49 -0
- rem/models/core/core_model.py +64 -0
- rem/models/core/engram.py +333 -0
- rem/models/core/experiment.py +628 -0
- rem/models/core/inline_edge.py +132 -0
- rem/models/core/rem_query.py +243 -0
- rem/models/entities/__init__.py +43 -0
- rem/models/entities/file.py +57 -0
- rem/models/entities/image_resource.py +88 -0
- rem/models/entities/message.py +35 -0
- rem/models/entities/moment.py +123 -0
- rem/models/entities/ontology.py +191 -0
- rem/models/entities/ontology_config.py +131 -0
- rem/models/entities/resource.py +95 -0
- rem/models/entities/schema.py +87 -0
- rem/models/entities/user.py +85 -0
- rem/py.typed +0 -0
- rem/schemas/README.md +507 -0
- rem/schemas/__init__.py +6 -0
- rem/schemas/agents/README.md +92 -0
- rem/schemas/agents/core/moment-builder.yaml +178 -0
- rem/schemas/agents/core/rem-query-agent.yaml +226 -0
- rem/schemas/agents/core/resource-affinity-assessor.yaml +99 -0
- rem/schemas/agents/core/simple-assistant.yaml +19 -0
- rem/schemas/agents/core/user-profile-builder.yaml +163 -0
- rem/schemas/agents/examples/contract-analyzer.yaml +317 -0
- rem/schemas/agents/examples/contract-extractor.yaml +134 -0
- rem/schemas/agents/examples/cv-parser.yaml +263 -0
- rem/schemas/agents/examples/hello-world.yaml +37 -0
- rem/schemas/agents/examples/query.yaml +54 -0
- rem/schemas/agents/examples/simple.yaml +21 -0
- rem/schemas/agents/examples/test.yaml +29 -0
- rem/schemas/agents/rem.yaml +128 -0
- rem/schemas/evaluators/hello-world/default.yaml +77 -0
- rem/schemas/evaluators/rem/faithfulness.yaml +219 -0
- rem/schemas/evaluators/rem/lookup-correctness.yaml +182 -0
- rem/schemas/evaluators/rem/retrieval-precision.yaml +199 -0
- rem/schemas/evaluators/rem/retrieval-recall.yaml +211 -0
- rem/schemas/evaluators/rem/search-correctness.yaml +192 -0
- rem/services/__init__.py +16 -0
- rem/services/audio/INTEGRATION.md +308 -0
- rem/services/audio/README.md +376 -0
- rem/services/audio/__init__.py +15 -0
- rem/services/audio/chunker.py +354 -0
- rem/services/audio/transcriber.py +259 -0
- rem/services/content/README.md +1269 -0
- rem/services/content/__init__.py +5 -0
- rem/services/content/providers.py +806 -0
- rem/services/content/service.py +657 -0
- rem/services/dreaming/README.md +230 -0
- rem/services/dreaming/__init__.py +53 -0
- rem/services/dreaming/affinity_service.py +336 -0
- rem/services/dreaming/moment_service.py +264 -0
- rem/services/dreaming/ontology_service.py +54 -0
- rem/services/dreaming/user_model_service.py +297 -0
- rem/services/dreaming/utils.py +39 -0
- rem/services/embeddings/__init__.py +11 -0
- rem/services/embeddings/api.py +120 -0
- rem/services/embeddings/worker.py +421 -0
- rem/services/fs/README.md +662 -0
- rem/services/fs/__init__.py +62 -0
- rem/services/fs/examples.py +206 -0
- rem/services/fs/examples_paths.py +204 -0
- rem/services/fs/git_provider.py +935 -0
- rem/services/fs/local_provider.py +760 -0
- rem/services/fs/parsing-hooks-examples.md +172 -0
- rem/services/fs/paths.py +276 -0
- rem/services/fs/provider.py +460 -0
- rem/services/fs/s3_provider.py +1042 -0
- rem/services/fs/service.py +186 -0
- rem/services/git/README.md +1075 -0
- rem/services/git/__init__.py +17 -0
- rem/services/git/service.py +469 -0
- rem/services/phoenix/EXPERIMENT_DESIGN.md +1146 -0
- rem/services/phoenix/README.md +453 -0
- rem/services/phoenix/__init__.py +46 -0
- rem/services/phoenix/client.py +686 -0
- rem/services/phoenix/config.py +88 -0
- rem/services/phoenix/prompt_labels.py +477 -0
- rem/services/postgres/README.md +575 -0
- rem/services/postgres/__init__.py +23 -0
- rem/services/postgres/migration_service.py +427 -0
- rem/services/postgres/pydantic_to_sqlalchemy.py +232 -0
- rem/services/postgres/register_type.py +352 -0
- rem/services/postgres/repository.py +337 -0
- rem/services/postgres/schema_generator.py +379 -0
- rem/services/postgres/service.py +802 -0
- rem/services/postgres/sql_builder.py +354 -0
- rem/services/rem/README.md +304 -0
- rem/services/rem/__init__.py +23 -0
- rem/services/rem/exceptions.py +71 -0
- rem/services/rem/executor.py +293 -0
- rem/services/rem/parser.py +145 -0
- rem/services/rem/queries.py +196 -0
- rem/services/rem/query.py +371 -0
- rem/services/rem/service.py +527 -0
- rem/services/session/README.md +374 -0
- rem/services/session/__init__.py +6 -0
- rem/services/session/compression.py +360 -0
- rem/services/session/reload.py +77 -0
- rem/settings.py +1235 -0
- rem/sql/002_install_models.sql +1068 -0
- rem/sql/background_indexes.sql +42 -0
- rem/sql/install_models.sql +1038 -0
- rem/sql/migrations/001_install.sql +503 -0
- rem/sql/migrations/002_install_models.sql +1202 -0
- rem/utils/AGENTIC_CHUNKING.md +597 -0
- rem/utils/README.md +583 -0
- rem/utils/__init__.py +43 -0
- rem/utils/agentic_chunking.py +622 -0
- rem/utils/batch_ops.py +343 -0
- rem/utils/chunking.py +108 -0
- rem/utils/clip_embeddings.py +276 -0
- rem/utils/dict_utils.py +98 -0
- rem/utils/embeddings.py +423 -0
- rem/utils/examples/embeddings_example.py +305 -0
- rem/utils/examples/sql_types_example.py +202 -0
- rem/utils/markdown.py +16 -0
- rem/utils/model_helpers.py +236 -0
- rem/utils/schema_loader.py +229 -0
- rem/utils/sql_types.py +348 -0
- rem/utils/user_id.py +81 -0
- rem/utils/vision.py +330 -0
- rem/workers/README.md +506 -0
- rem/workers/__init__.py +5 -0
- rem/workers/dreaming.py +502 -0
- rem/workers/engram_processor.py +312 -0
- rem/workers/sqs_file_processor.py +193 -0
- remdb-0.2.6.dist-info/METADATA +1191 -0
- remdb-0.2.6.dist-info/RECORD +187 -0
- remdb-0.2.6.dist-info/WHEEL +4 -0
- remdb-0.2.6.dist-info/entry_points.txt +2 -0
rem/cli/dreaming.py
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"""
|
|
2
|
+
REM Dreaming CLI - Memory indexing and insight extraction.
|
|
3
|
+
|
|
4
|
+
Command-line interface for running dreaming workers to build the
|
|
5
|
+
REM knowledge graph through user model updates, moment construction,
|
|
6
|
+
and resource affinity operations.
|
|
7
|
+
|
|
8
|
+
Commands:
|
|
9
|
+
- user-model: Update user profiles from activity
|
|
10
|
+
- moments: Extract temporal narratives from resources
|
|
11
|
+
- affinity: Build semantic relationships between resources
|
|
12
|
+
- custom: Run custom extractors on user's resources/sessions
|
|
13
|
+
- full: Run complete dreaming workflow (all operations)
|
|
14
|
+
|
|
15
|
+
Usage Examples:
|
|
16
|
+
```bash
|
|
17
|
+
# Update user model for specific user
|
|
18
|
+
rem-dreaming user-model --user-id=user-123
|
|
19
|
+
|
|
20
|
+
# Extract moments with custom lookback
|
|
21
|
+
rem-dreaming moments --user-id=user-123 --lookback-hours=48
|
|
22
|
+
|
|
23
|
+
# Build resource affinity (semantic mode, fast)
|
|
24
|
+
rem-dreaming affinity --user-id=user-123
|
|
25
|
+
|
|
26
|
+
# Build resource affinity (LLM mode, intelligent but expensive)
|
|
27
|
+
rem-dreaming affinity --user-id=user-123 --use-llm --limit=100
|
|
28
|
+
|
|
29
|
+
# Run custom extractor on user's data
|
|
30
|
+
rem-dreaming custom --user-id=user-123 --extractor cv-parser-v1
|
|
31
|
+
rem-dreaming custom --user-id=user-123 --extractor contract-analyzer-v1 --lookback-hours=168
|
|
32
|
+
|
|
33
|
+
# Run full workflow for user
|
|
34
|
+
rem-dreaming full --user-id=user-123
|
|
35
|
+
|
|
36
|
+
# Process all active users (daily cron)
|
|
37
|
+
rem-dreaming full --all-users
|
|
38
|
+
|
|
39
|
+
# Process with custom REM API endpoint
|
|
40
|
+
rem-dreaming full --user-id=user-123 --rem-api-url=http://localhost:8000
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Environment Variables:
|
|
44
|
+
- REM_API_URL: REM API endpoint (default: http://rem-api:8000)
|
|
45
|
+
- REM_EMBEDDING_PROVIDER: Embedding provider (default: text-embedding-3-small)
|
|
46
|
+
- REM_DEFAULT_MODEL: LLM model (default: gpt-4o)
|
|
47
|
+
- REM_LOOKBACK_HOURS: Default lookback window (default: 24)
|
|
48
|
+
- OPENAI_API_KEY: OpenAI API key
|
|
49
|
+
|
|
50
|
+
Exit Codes:
|
|
51
|
+
- 0: Success
|
|
52
|
+
- 1: Validation error (missing required args)
|
|
53
|
+
- 2: Execution error (worker failed)
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
import asyncio
|
|
57
|
+
import os
|
|
58
|
+
import sys
|
|
59
|
+
from typing import Optional
|
|
60
|
+
|
|
61
|
+
import typer
|
|
62
|
+
from rich.console import Console
|
|
63
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
64
|
+
|
|
65
|
+
from rem.workers.dreaming import (
|
|
66
|
+
AffinityMode,
|
|
67
|
+
DreamingWorker,
|
|
68
|
+
TaskType,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
app = typer.Typer(
|
|
72
|
+
name="rem-dreaming",
|
|
73
|
+
help="REM dreaming worker for memory indexing",
|
|
74
|
+
add_completion=False,
|
|
75
|
+
)
|
|
76
|
+
console = Console()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def get_worker() -> DreamingWorker:
|
|
80
|
+
"""Create dreaming worker from environment."""
|
|
81
|
+
return DreamingWorker(
|
|
82
|
+
rem_api_url=os.getenv("REM_API_URL", "http://rem-api:8000"),
|
|
83
|
+
embedding_provider=os.getenv(
|
|
84
|
+
"REM_EMBEDDING_PROVIDER", "text-embedding-3-small"
|
|
85
|
+
),
|
|
86
|
+
default_model=os.getenv("REM_DEFAULT_MODEL", "gpt-4o"),
|
|
87
|
+
lookback_hours=int(os.getenv("REM_LOOKBACK_HOURS", "24")),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@app.command()
|
|
92
|
+
def user_model(
|
|
93
|
+
user_id: str = typer.Option(..., help="User ID to process"),
|
|
94
|
+
max_sessions: int = typer.Option(100, help="Max sessions to analyze"),
|
|
95
|
+
max_moments: int = typer.Option(20, help="Max moments to include"),
|
|
96
|
+
max_resources: int = typer.Option(20, help="Max resources to include"),
|
|
97
|
+
):
|
|
98
|
+
"""
|
|
99
|
+
Update user model from recent activity.
|
|
100
|
+
|
|
101
|
+
Reads recent sessions, moments, and resources to generate
|
|
102
|
+
a comprehensive user profile summary using LLM analysis.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
async def run():
|
|
106
|
+
worker = get_worker()
|
|
107
|
+
try:
|
|
108
|
+
with Progress(
|
|
109
|
+
SpinnerColumn(),
|
|
110
|
+
TextColumn("[progress.description]{task.description}"),
|
|
111
|
+
console=console,
|
|
112
|
+
) as progress:
|
|
113
|
+
task = progress.add_task(
|
|
114
|
+
f"Updating user model for {user_id}...", total=None
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
result = await worker.update_user_model(
|
|
118
|
+
user_id=user_id,
|
|
119
|
+
max_sessions=max_sessions,
|
|
120
|
+
max_moments=max_moments,
|
|
121
|
+
max_resources=max_resources,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
progress.update(task, completed=True)
|
|
125
|
+
console.print(f"[green]✓[/green] User model updated")
|
|
126
|
+
console.print(result)
|
|
127
|
+
except Exception as e:
|
|
128
|
+
console.print(f"[red]✗[/red] Failed: {e}", style="red")
|
|
129
|
+
sys.exit(2)
|
|
130
|
+
finally:
|
|
131
|
+
await worker.close()
|
|
132
|
+
|
|
133
|
+
asyncio.run(run())
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@app.command()
|
|
137
|
+
def moments(
|
|
138
|
+
user_id: str = typer.Option(..., help="User ID to process"),
|
|
139
|
+
lookback_hours: Optional[int] = typer.Option(
|
|
140
|
+
None, help="Hours to look back (default: from env)"
|
|
141
|
+
),
|
|
142
|
+
limit: Optional[int] = typer.Option(None, help="Max resources to process"),
|
|
143
|
+
):
|
|
144
|
+
"""
|
|
145
|
+
Extract moments from resources.
|
|
146
|
+
|
|
147
|
+
Analyzes recent resources to identify temporal narratives
|
|
148
|
+
(meetings, coding sessions, conversations) and creates
|
|
149
|
+
Moment entities with temporal boundaries and metadata.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
async def run():
|
|
153
|
+
worker = get_worker()
|
|
154
|
+
try:
|
|
155
|
+
with Progress(
|
|
156
|
+
SpinnerColumn(),
|
|
157
|
+
TextColumn("[progress.description]{task.description}"),
|
|
158
|
+
console=console,
|
|
159
|
+
) as progress:
|
|
160
|
+
task = progress.add_task(
|
|
161
|
+
f"Constructing moments for {user_id}...", total=None
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
result = await worker.construct_moments(
|
|
165
|
+
user_id=user_id,
|
|
166
|
+
lookback_hours=lookback_hours,
|
|
167
|
+
limit=limit,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
progress.update(task, completed=True)
|
|
171
|
+
console.print(f"[green]✓[/green] Moments constructed")
|
|
172
|
+
console.print(result)
|
|
173
|
+
except Exception as e:
|
|
174
|
+
console.print(f"[red]✗[/red] Failed: {e}", style="red")
|
|
175
|
+
sys.exit(2)
|
|
176
|
+
finally:
|
|
177
|
+
await worker.close()
|
|
178
|
+
|
|
179
|
+
asyncio.run(run())
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@app.command()
|
|
183
|
+
def affinity(
|
|
184
|
+
user_id: str = typer.Option(..., help="User ID to process"),
|
|
185
|
+
use_llm: bool = typer.Option(
|
|
186
|
+
False, "--use-llm", help="Use LLM mode (expensive, use --limit)"
|
|
187
|
+
),
|
|
188
|
+
lookback_hours: Optional[int] = typer.Option(
|
|
189
|
+
None, help="Hours to look back (default: from env)"
|
|
190
|
+
),
|
|
191
|
+
limit: Optional[int] = typer.Option(
|
|
192
|
+
None, help="Max resources to process (REQUIRED for LLM mode)"
|
|
193
|
+
),
|
|
194
|
+
):
|
|
195
|
+
"""
|
|
196
|
+
Build resource affinity graph.
|
|
197
|
+
|
|
198
|
+
Creates semantic relationships between resources using either
|
|
199
|
+
vector similarity (fast, default) or LLM analysis (intelligent but expensive).
|
|
200
|
+
|
|
201
|
+
Semantic Mode (default):
|
|
202
|
+
- Fast vector similarity search
|
|
203
|
+
- No LLM calls, just embedding cosine similarity
|
|
204
|
+
- Good for frequent updates
|
|
205
|
+
|
|
206
|
+
LLM Mode (--use-llm):
|
|
207
|
+
- Intelligent relationship assessment
|
|
208
|
+
- Expensive: ALWAYS use --limit to control costs
|
|
209
|
+
- Good for deep analysis (weekly or monthly)
|
|
210
|
+
|
|
211
|
+
Example:
|
|
212
|
+
# Semantic mode (fast, cheap)
|
|
213
|
+
rem-dreaming affinity --user-id=user-123
|
|
214
|
+
|
|
215
|
+
# LLM mode (intelligent, expensive)
|
|
216
|
+
rem-dreaming affinity --user-id=user-123 --use-llm --limit=100
|
|
217
|
+
"""
|
|
218
|
+
if use_llm and not limit:
|
|
219
|
+
console.print(
|
|
220
|
+
"[red]Error:[/red] --limit is REQUIRED when using --use-llm to control costs",
|
|
221
|
+
style="red",
|
|
222
|
+
)
|
|
223
|
+
sys.exit(1)
|
|
224
|
+
|
|
225
|
+
async def run():
|
|
226
|
+
worker = get_worker()
|
|
227
|
+
try:
|
|
228
|
+
mode = AffinityMode.LLM if use_llm else AffinityMode.SEMANTIC
|
|
229
|
+
mode_str = "LLM" if use_llm else "semantic"
|
|
230
|
+
|
|
231
|
+
with Progress(
|
|
232
|
+
SpinnerColumn(),
|
|
233
|
+
TextColumn("[progress.description]{task.description}"),
|
|
234
|
+
console=console,
|
|
235
|
+
) as progress:
|
|
236
|
+
task = progress.add_task(
|
|
237
|
+
f"Building {mode_str} affinity for {user_id}...", total=None
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
result = await worker.build_affinity(
|
|
241
|
+
user_id=user_id,
|
|
242
|
+
mode=mode,
|
|
243
|
+
lookback_hours=lookback_hours,
|
|
244
|
+
limit=limit,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
progress.update(task, completed=True)
|
|
248
|
+
console.print(f"[green]✓[/green] Resource affinity built ({mode_str} mode)")
|
|
249
|
+
console.print(result)
|
|
250
|
+
except Exception as e:
|
|
251
|
+
console.print(f"[red]✗[/red] Failed: {e}", style="red")
|
|
252
|
+
sys.exit(2)
|
|
253
|
+
finally:
|
|
254
|
+
await worker.close()
|
|
255
|
+
|
|
256
|
+
asyncio.run(run())
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@app.command()
|
|
260
|
+
def full(
|
|
261
|
+
user_id: Optional[str] = typer.Option(None, help="User ID (or --all-users)"),
|
|
262
|
+
all_users: bool = typer.Option(
|
|
263
|
+
False, "--all-users", help="Process all active users"
|
|
264
|
+
),
|
|
265
|
+
use_llm_affinity: bool = typer.Option(
|
|
266
|
+
False, "--use-llm-affinity", help="Use LLM mode for affinity (expensive)"
|
|
267
|
+
),
|
|
268
|
+
lookback_hours: Optional[int] = typer.Option(
|
|
269
|
+
None, help="Hours to look back (default: from env)"
|
|
270
|
+
),
|
|
271
|
+
):
|
|
272
|
+
"""
|
|
273
|
+
Run complete dreaming workflow.
|
|
274
|
+
|
|
275
|
+
Executes all dreaming operations in sequence:
|
|
276
|
+
1. Update user model
|
|
277
|
+
2. Construct moments
|
|
278
|
+
3. Build resource affinity
|
|
279
|
+
|
|
280
|
+
Recommended for daily cron execution.
|
|
281
|
+
|
|
282
|
+
Examples:
|
|
283
|
+
# Process single user
|
|
284
|
+
rem-dreaming full --user-id=user-123
|
|
285
|
+
|
|
286
|
+
# Process all active users (daily cron)
|
|
287
|
+
rem-dreaming full --all-users
|
|
288
|
+
|
|
289
|
+
# Use LLM affinity mode (expensive)
|
|
290
|
+
rem-dreaming full --user-id=user-123 --use-llm-affinity
|
|
291
|
+
"""
|
|
292
|
+
if not user_id and not all_users:
|
|
293
|
+
console.print(
|
|
294
|
+
"[red]Error:[/red] Either --user-id or --all-users is required",
|
|
295
|
+
style="red",
|
|
296
|
+
)
|
|
297
|
+
sys.exit(1)
|
|
298
|
+
|
|
299
|
+
if user_id and all_users:
|
|
300
|
+
console.print(
|
|
301
|
+
"[red]Error:[/red] Cannot use both --user-id and --all-users",
|
|
302
|
+
style="red",
|
|
303
|
+
)
|
|
304
|
+
sys.exit(1)
|
|
305
|
+
|
|
306
|
+
async def run():
|
|
307
|
+
worker = get_worker()
|
|
308
|
+
try:
|
|
309
|
+
if all_users:
|
|
310
|
+
with Progress(
|
|
311
|
+
SpinnerColumn(),
|
|
312
|
+
TextColumn("[progress.description]{task.description}"),
|
|
313
|
+
console=console,
|
|
314
|
+
) as progress:
|
|
315
|
+
task = progress.add_task("Processing all users...", total=None)
|
|
316
|
+
|
|
317
|
+
results = await worker.process_all_users(
|
|
318
|
+
task_type=TaskType.FULL,
|
|
319
|
+
use_llm_affinity=use_llm_affinity,
|
|
320
|
+
lookback_hours=lookback_hours,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
progress.update(task, completed=True)
|
|
324
|
+
console.print(
|
|
325
|
+
f"[green]✓[/green] Processed {len(results)} users"
|
|
326
|
+
)
|
|
327
|
+
for result in results:
|
|
328
|
+
console.print(result)
|
|
329
|
+
else:
|
|
330
|
+
with Progress(
|
|
331
|
+
SpinnerColumn(),
|
|
332
|
+
TextColumn("[progress.description]{task.description}"),
|
|
333
|
+
console=console,
|
|
334
|
+
) as progress:
|
|
335
|
+
task = progress.add_task(
|
|
336
|
+
f"Running full workflow for {user_id}...", total=None
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
result = await worker.process_full(
|
|
340
|
+
user_id=user_id,
|
|
341
|
+
use_llm_affinity=use_llm_affinity,
|
|
342
|
+
lookback_hours=lookback_hours,
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
progress.update(task, completed=True)
|
|
346
|
+
console.print(f"[green]✓[/green] Full workflow completed")
|
|
347
|
+
console.print(result)
|
|
348
|
+
except Exception as e:
|
|
349
|
+
console.print(f"[red]✗[/red] Failed: {e}", style="red")
|
|
350
|
+
sys.exit(2)
|
|
351
|
+
finally:
|
|
352
|
+
await worker.close()
|
|
353
|
+
|
|
354
|
+
asyncio.run(run())
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def main():
|
|
358
|
+
"""Entry point."""
|
|
359
|
+
app()
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
if __name__ == "__main__":
|
|
363
|
+
main()
|
rem/cli/main.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""
|
|
2
|
+
REM CLI entry point.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
rem db schema generate --models src/rem/models/entities
|
|
6
|
+
rem db schema validate
|
|
7
|
+
rem db migrate up
|
|
8
|
+
rem dev run-server
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import click
|
|
15
|
+
from loguru import logger
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.group()
|
|
19
|
+
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose logging")
|
|
20
|
+
def cli(verbose: bool):
|
|
21
|
+
"""REM - Resources Entities Moments system CLI."""
|
|
22
|
+
if verbose:
|
|
23
|
+
logger.remove()
|
|
24
|
+
logger.add(sys.stderr, level="DEBUG")
|
|
25
|
+
else:
|
|
26
|
+
logger.remove()
|
|
27
|
+
logger.add(sys.stderr, level="INFO")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@cli.group()
|
|
31
|
+
def db():
|
|
32
|
+
"""Database operations (schema, migrate, status, etc.)."""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@db.group()
|
|
37
|
+
def schema():
|
|
38
|
+
"""Database schema management commands."""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@cli.group()
|
|
43
|
+
def dev():
|
|
44
|
+
"""Development utilities."""
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@cli.group()
|
|
49
|
+
def process():
|
|
50
|
+
"""File processing commands."""
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@cli.group()
|
|
55
|
+
def dreaming():
|
|
56
|
+
"""Memory indexing and knowledge extraction."""
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# Register commands
|
|
61
|
+
from .commands.schema import register_commands as register_schema_commands
|
|
62
|
+
from .commands.db import register_commands as register_db_commands
|
|
63
|
+
from .commands.process import register_commands as register_process_commands
|
|
64
|
+
from .commands.ask import register_command as register_ask_command
|
|
65
|
+
from .commands.dreaming import register_commands as register_dreaming_commands
|
|
66
|
+
from .commands.experiments import experiments as experiments_group
|
|
67
|
+
from .commands.configure import register_command as register_configure_command
|
|
68
|
+
from .commands.serve import register_command as register_serve_command
|
|
69
|
+
from .commands.mcp import register_command as register_mcp_command
|
|
70
|
+
|
|
71
|
+
register_schema_commands(schema)
|
|
72
|
+
register_db_commands(db)
|
|
73
|
+
register_process_commands(process)
|
|
74
|
+
register_dreaming_commands(dreaming)
|
|
75
|
+
register_ask_command(cli)
|
|
76
|
+
register_configure_command(cli)
|
|
77
|
+
register_serve_command(cli)
|
|
78
|
+
register_mcp_command(cli)
|
|
79
|
+
cli.add_command(experiments_group)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def main():
|
|
83
|
+
"""Main entry point for CLI."""
|
|
84
|
+
cli()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
main()
|
rem/config.py
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""
|
|
2
|
+
REM Configuration Management.
|
|
3
|
+
|
|
4
|
+
Provides persistent configuration in ~/.rem/config.yaml with environment variable overrides.
|
|
5
|
+
|
|
6
|
+
Configuration Precedence (highest to lowest):
|
|
7
|
+
1. Environment variables (POSTGRES__CONNECTION_STRING, etc.)
|
|
8
|
+
2. ~/.rem/config.yaml (user configuration)
|
|
9
|
+
3. Default values (from settings.py)
|
|
10
|
+
|
|
11
|
+
File Format (~/.rem/config.yaml):
|
|
12
|
+
postgres:
|
|
13
|
+
connection_string: postgresql://user:pass@localhost:5432/rem
|
|
14
|
+
pool_min_size: 5
|
|
15
|
+
pool_max_size: 20
|
|
16
|
+
|
|
17
|
+
llm:
|
|
18
|
+
default_model: anthropic:claude-sonnet-4-5-20250929
|
|
19
|
+
openai_api_key: sk-...
|
|
20
|
+
anthropic_api_key: sk-ant-...
|
|
21
|
+
|
|
22
|
+
s3:
|
|
23
|
+
bucket_name: rem-storage
|
|
24
|
+
region: us-east-1
|
|
25
|
+
endpoint_url: http://localhost:9000
|
|
26
|
+
|
|
27
|
+
# Additional custom environment variables
|
|
28
|
+
env:
|
|
29
|
+
MY_CUSTOM_VAR: value
|
|
30
|
+
|
|
31
|
+
Usage:
|
|
32
|
+
from rem.config import load_config, get_config_path, ensure_config_dir
|
|
33
|
+
|
|
34
|
+
# Load configuration and merge with environment
|
|
35
|
+
config = load_config()
|
|
36
|
+
|
|
37
|
+
# Get configuration file path
|
|
38
|
+
config_path = get_config_path()
|
|
39
|
+
|
|
40
|
+
# Ensure ~/.rem directory exists
|
|
41
|
+
ensure_config_dir()
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
import os
|
|
45
|
+
from pathlib import Path
|
|
46
|
+
from typing import Any
|
|
47
|
+
|
|
48
|
+
import yaml
|
|
49
|
+
from loguru import logger
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_rem_home() -> Path:
|
|
53
|
+
"""
|
|
54
|
+
Get REM home directory (~/.rem).
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Path to ~/.rem directory
|
|
58
|
+
"""
|
|
59
|
+
return Path.home() / ".rem"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def ensure_config_dir() -> Path:
|
|
63
|
+
"""
|
|
64
|
+
Ensure ~/.rem directory exists.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Path to ~/.rem directory
|
|
68
|
+
"""
|
|
69
|
+
rem_home = get_rem_home()
|
|
70
|
+
rem_home.mkdir(exist_ok=True, mode=0o700) # User-only permissions
|
|
71
|
+
return rem_home
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_config_path() -> Path:
|
|
75
|
+
"""
|
|
76
|
+
Get path to configuration file (~/.rem/config.yaml).
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Path to configuration file
|
|
80
|
+
"""
|
|
81
|
+
return get_rem_home() / "config.yaml"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def config_exists() -> bool:
|
|
85
|
+
"""
|
|
86
|
+
Check if configuration file exists.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
True if ~/.rem/config.yaml exists
|
|
90
|
+
"""
|
|
91
|
+
return get_config_path().exists()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def load_config() -> dict[str, Any]:
|
|
95
|
+
"""
|
|
96
|
+
Load configuration from ~/.rem/config.yaml.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Configuration dictionary (empty if file doesn't exist)
|
|
100
|
+
"""
|
|
101
|
+
config_path = get_config_path()
|
|
102
|
+
|
|
103
|
+
if not config_path.exists():
|
|
104
|
+
logger.debug(f"Configuration file not found: {config_path}")
|
|
105
|
+
return {}
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
with open(config_path, "r") as f:
|
|
109
|
+
config = yaml.safe_load(f) or {}
|
|
110
|
+
logger.debug(f"Loaded configuration from {config_path}")
|
|
111
|
+
return config
|
|
112
|
+
except Exception as e:
|
|
113
|
+
logger.warning(f"Failed to load configuration from {config_path}: {e}")
|
|
114
|
+
return {}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def save_config(config: dict[str, Any]) -> None:
|
|
118
|
+
"""
|
|
119
|
+
Save configuration to ~/.rem/config.yaml.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
config: Configuration dictionary to save
|
|
123
|
+
"""
|
|
124
|
+
ensure_config_dir()
|
|
125
|
+
config_path = get_config_path()
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
with open(config_path, "w") as f:
|
|
129
|
+
yaml.dump(config, f, default_flow_style=False, sort_keys=False)
|
|
130
|
+
logger.info(f"Configuration saved to {config_path}")
|
|
131
|
+
except Exception as e:
|
|
132
|
+
logger.error(f"Failed to save configuration to {config_path}: {e}")
|
|
133
|
+
raise
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def merge_config_to_env(config: dict[str, Any]) -> None:
|
|
137
|
+
"""
|
|
138
|
+
Merge configuration file into environment variables.
|
|
139
|
+
|
|
140
|
+
This allows Pydantic Settings to pick up values from the config file
|
|
141
|
+
by setting environment variables before settings initialization.
|
|
142
|
+
|
|
143
|
+
Precedence:
|
|
144
|
+
- Existing environment variables are NOT overwritten
|
|
145
|
+
- Only sets env vars if they don't already exist
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
config: Configuration dictionary from ~/.rem/config.yaml
|
|
149
|
+
|
|
150
|
+
Example:
|
|
151
|
+
config = {"postgres": {"connection_string": "postgresql://..."}}
|
|
152
|
+
merge_config_to_env(config)
|
|
153
|
+
# Sets POSTGRES__CONNECTION_STRING if not already set
|
|
154
|
+
"""
|
|
155
|
+
# Handle custom env vars first
|
|
156
|
+
if "env" in config:
|
|
157
|
+
for key, value in config["env"].items():
|
|
158
|
+
if key not in os.environ:
|
|
159
|
+
os.environ[key] = str(value)
|
|
160
|
+
logger.debug(f"Set env var from config: {key}")
|
|
161
|
+
|
|
162
|
+
# Convert nested config to environment variables
|
|
163
|
+
for section, values in config.items():
|
|
164
|
+
if section == "env":
|
|
165
|
+
continue # Already handled
|
|
166
|
+
|
|
167
|
+
if not isinstance(values, dict):
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
for key, value in values.items():
|
|
171
|
+
# Convert to environment variable format (SECTION__KEY)
|
|
172
|
+
env_key = f"{section.upper()}__{key.upper()}"
|
|
173
|
+
|
|
174
|
+
# Only set if not already in environment
|
|
175
|
+
if env_key not in os.environ:
|
|
176
|
+
os.environ[env_key] = str(value)
|
|
177
|
+
logger.debug(f"Set env var from config: {env_key}")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def validate_config(config: dict[str, Any]) -> list[str]:
|
|
181
|
+
"""
|
|
182
|
+
Validate configuration for required fields.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
config: Configuration dictionary
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
List of validation error messages (empty if valid)
|
|
189
|
+
"""
|
|
190
|
+
errors = []
|
|
191
|
+
|
|
192
|
+
# Postgres connection is required
|
|
193
|
+
postgres = config.get("postgres", {})
|
|
194
|
+
if not postgres.get("connection_string"):
|
|
195
|
+
errors.append("PostgreSQL connection string is required (postgres.connection_string)")
|
|
196
|
+
|
|
197
|
+
# Validate connection string format
|
|
198
|
+
conn_str = postgres.get("connection_string", "")
|
|
199
|
+
if conn_str and not conn_str.startswith("postgresql://"):
|
|
200
|
+
errors.append("PostgreSQL connection string must start with 'postgresql://'")
|
|
201
|
+
|
|
202
|
+
return errors
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def get_default_config() -> dict[str, Any]:
|
|
206
|
+
"""
|
|
207
|
+
Get default configuration template for new installations.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Default configuration dictionary
|
|
211
|
+
"""
|
|
212
|
+
return {
|
|
213
|
+
"postgres": {
|
|
214
|
+
"connection_string": "postgresql://rem:rem@localhost:5432/rem",
|
|
215
|
+
"pool_min_size": 5,
|
|
216
|
+
"pool_max_size": 20,
|
|
217
|
+
},
|
|
218
|
+
"llm": {
|
|
219
|
+
"default_model": "anthropic:claude-sonnet-4-5-20250929",
|
|
220
|
+
"default_temperature": 0.5,
|
|
221
|
+
# API keys will be prompted for in wizard
|
|
222
|
+
# "openai_api_key": "",
|
|
223
|
+
# "anthropic_api_key": "",
|
|
224
|
+
},
|
|
225
|
+
"s3": {
|
|
226
|
+
"bucket_name": "rem-storage",
|
|
227
|
+
"region": "us-east-1",
|
|
228
|
+
# Optional fields
|
|
229
|
+
# "endpoint_url": "http://localhost:9000", # For MinIO
|
|
230
|
+
# "access_key_id": "",
|
|
231
|
+
# "secret_access_key": "",
|
|
232
|
+
},
|
|
233
|
+
"env": {
|
|
234
|
+
# Custom environment variables
|
|
235
|
+
# "MY_VAR": "value",
|
|
236
|
+
},
|
|
237
|
+
}
|