remdb 0.3.7__py3-none-any.whl → 0.3.133__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.
- rem/__init__.py +129 -2
- rem/agentic/README.md +76 -0
- rem/agentic/__init__.py +15 -0
- rem/agentic/agents/__init__.py +16 -2
- rem/agentic/agents/sse_simulator.py +502 -0
- rem/agentic/context.py +51 -25
- rem/agentic/llm_provider_models.py +301 -0
- rem/agentic/mcp/tool_wrapper.py +112 -17
- rem/agentic/otel/setup.py +93 -4
- rem/agentic/providers/phoenix.py +314 -132
- rem/agentic/providers/pydantic_ai.py +215 -26
- rem/agentic/schema.py +361 -21
- rem/agentic/tools/rem_tools.py +3 -3
- rem/api/README.md +238 -1
- rem/api/deps.py +255 -0
- rem/api/main.py +154 -37
- rem/api/mcp_router/resources.py +1 -1
- rem/api/mcp_router/server.py +26 -5
- rem/api/mcp_router/tools.py +465 -7
- rem/api/middleware/tracking.py +172 -0
- rem/api/routers/admin.py +494 -0
- rem/api/routers/auth.py +124 -0
- rem/api/routers/chat/completions.py +402 -20
- rem/api/routers/chat/models.py +88 -10
- rem/api/routers/chat/otel_utils.py +33 -0
- rem/api/routers/chat/sse_events.py +542 -0
- rem/api/routers/chat/streaming.py +642 -45
- rem/api/routers/dev.py +81 -0
- rem/api/routers/feedback.py +268 -0
- rem/api/routers/messages.py +473 -0
- rem/api/routers/models.py +78 -0
- rem/api/routers/query.py +360 -0
- rem/api/routers/shared_sessions.py +406 -0
- rem/auth/middleware.py +126 -27
- rem/cli/commands/README.md +237 -64
- rem/cli/commands/ask.py +13 -10
- rem/cli/commands/cluster.py +1808 -0
- rem/cli/commands/configure.py +5 -6
- rem/cli/commands/db.py +396 -139
- rem/cli/commands/experiments.py +469 -74
- rem/cli/commands/process.py +22 -15
- rem/cli/commands/scaffold.py +47 -0
- rem/cli/commands/schema.py +97 -50
- rem/cli/main.py +29 -6
- rem/config.py +10 -3
- rem/models/core/core_model.py +7 -1
- rem/models/core/experiment.py +54 -0
- rem/models/core/rem_query.py +5 -2
- rem/models/entities/__init__.py +21 -0
- rem/models/entities/domain_resource.py +38 -0
- rem/models/entities/feedback.py +123 -0
- rem/models/entities/message.py +30 -1
- rem/models/entities/session.py +83 -0
- rem/models/entities/shared_session.py +180 -0
- rem/models/entities/user.py +10 -3
- rem/registry.py +373 -0
- rem/schemas/agents/rem.yaml +7 -3
- rem/services/content/providers.py +92 -133
- rem/services/content/service.py +92 -20
- rem/services/dreaming/affinity_service.py +2 -16
- rem/services/dreaming/moment_service.py +2 -15
- rem/services/embeddings/api.py +24 -17
- rem/services/embeddings/worker.py +16 -16
- rem/services/phoenix/EXPERIMENT_DESIGN.md +3 -3
- rem/services/phoenix/client.py +302 -28
- rem/services/postgres/README.md +159 -15
- rem/services/postgres/__init__.py +2 -1
- rem/services/postgres/diff_service.py +531 -0
- rem/services/postgres/pydantic_to_sqlalchemy.py +427 -129
- rem/services/postgres/repository.py +132 -0
- rem/services/postgres/schema_generator.py +291 -9
- rem/services/postgres/service.py +6 -6
- rem/services/rate_limit.py +113 -0
- rem/services/rem/README.md +14 -0
- rem/services/rem/parser.py +44 -9
- rem/services/rem/service.py +36 -2
- rem/services/session/compression.py +24 -1
- rem/services/session/reload.py +1 -1
- rem/services/user_service.py +98 -0
- rem/settings.py +399 -29
- rem/sql/background_indexes.sql +21 -16
- rem/sql/migrations/001_install.sql +387 -54
- rem/sql/migrations/002_install_models.sql +2320 -393
- rem/sql/migrations/003_optional_extensions.sql +326 -0
- rem/sql/migrations/004_cache_system.sql +548 -0
- rem/utils/__init__.py +18 -0
- rem/utils/constants.py +97 -0
- rem/utils/date_utils.py +228 -0
- rem/utils/embeddings.py +17 -4
- rem/utils/files.py +167 -0
- rem/utils/mime_types.py +158 -0
- rem/utils/model_helpers.py +156 -1
- rem/utils/schema_loader.py +282 -35
- rem/utils/sql_paths.py +146 -0
- rem/utils/sql_types.py +3 -1
- rem/utils/vision.py +9 -14
- rem/workers/README.md +14 -14
- rem/workers/__init__.py +3 -1
- rem/workers/db_listener.py +579 -0
- rem/workers/db_maintainer.py +74 -0
- rem/workers/unlogged_maintainer.py +463 -0
- {remdb-0.3.7.dist-info → remdb-0.3.133.dist-info}/METADATA +460 -303
- {remdb-0.3.7.dist-info → remdb-0.3.133.dist-info}/RECORD +105 -74
- {remdb-0.3.7.dist-info → remdb-0.3.133.dist-info}/WHEEL +1 -1
- rem/sql/002_install_models.sql +0 -1068
- rem/sql/install_models.sql +0 -1038
- {remdb-0.3.7.dist-info → remdb-0.3.133.dist-info}/entry_points.txt +0 -0
rem/cli/commands/process.py
CHANGED
|
@@ -12,12 +12,12 @@ from rem.services.content import ContentService
|
|
|
12
12
|
|
|
13
13
|
@click.command(name="ingest")
|
|
14
14
|
@click.argument("file_path", type=click.Path(exists=True))
|
|
15
|
-
@click.option("--user-id",
|
|
15
|
+
@click.option("--user-id", default=None, help="User ID to scope file privately (default: public/shared)")
|
|
16
16
|
@click.option("--category", help="Optional file category")
|
|
17
17
|
@click.option("--tags", help="Optional comma-separated tags")
|
|
18
18
|
def process_ingest(
|
|
19
19
|
file_path: str,
|
|
20
|
-
user_id: str,
|
|
20
|
+
user_id: str | None,
|
|
21
21
|
category: str | None,
|
|
22
22
|
tags: str | None,
|
|
23
23
|
):
|
|
@@ -32,8 +32,9 @@ def process_ingest(
|
|
|
32
32
|
5. Creates a File entity record.
|
|
33
33
|
|
|
34
34
|
Examples:
|
|
35
|
-
rem process ingest sample.pdf
|
|
36
|
-
rem process ingest contract.docx --
|
|
35
|
+
rem process ingest sample.pdf
|
|
36
|
+
rem process ingest contract.docx --category legal --tags contract,2023
|
|
37
|
+
rem process ingest agent.yaml # Auto-detects kind=agent, saves to schemas table
|
|
37
38
|
"""
|
|
38
39
|
import asyncio
|
|
39
40
|
from ...services.content import ContentService
|
|
@@ -56,7 +57,8 @@ def process_ingest(
|
|
|
56
57
|
|
|
57
58
|
tag_list = tags.split(",") if tags else None
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
scope_msg = f"user: {user_id}" if user_id else "public"
|
|
61
|
+
logger.info(f"Ingesting file: {file_path} ({scope_msg})")
|
|
60
62
|
result = await service.ingest_file(
|
|
61
63
|
file_uri=file_path,
|
|
62
64
|
user_id=user_id,
|
|
@@ -65,11 +67,15 @@ def process_ingest(
|
|
|
65
67
|
is_local_server=True, # CLI is local
|
|
66
68
|
)
|
|
67
69
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
# Handle schema ingestion (agents/evaluators)
|
|
71
|
+
if result.get("schema_name"):
|
|
72
|
+
logger.success(f"Schema ingested: {result['schema_name']} (kind={result.get('kind', 'agent')})")
|
|
73
|
+
logger.info(f"Version: {result.get('version', '1.0.0')}")
|
|
74
|
+
# Handle file ingestion
|
|
75
|
+
elif result.get("processing_status") == "completed":
|
|
76
|
+
logger.success(f"File ingested: {result['file_name']}")
|
|
70
77
|
logger.info(f"File ID: {result['file_id']}")
|
|
71
78
|
logger.info(f"Resources created: {result['resources_created']}")
|
|
72
|
-
logger.info(f"Status: {result['processing_status']}")
|
|
73
79
|
else:
|
|
74
80
|
logger.error(f"Ingestion failed: {result.get('message', 'Unknown error')}")
|
|
75
81
|
sys.exit(1)
|
|
@@ -192,15 +198,13 @@ def process_uri(uri: str, output: str, save: str | None):
|
|
|
192
198
|
|
|
193
199
|
|
|
194
200
|
@click.command(name="files")
|
|
195
|
-
@click.option("--
|
|
196
|
-
@click.option("--user-id", help="Filter by user ID")
|
|
201
|
+
@click.option("--user-id", default=None, help="User ID (default: from settings)")
|
|
197
202
|
@click.option("--status", type=click.Choice(["pending", "processing", "completed", "failed"]), help="Filter by status")
|
|
198
203
|
@click.option("--extractor", help="Run files through custom extractor (e.g., cv-parser-v1)")
|
|
199
204
|
@click.option("--limit", type=int, help="Max files to process")
|
|
200
205
|
@click.option("--provider", help="Optional LLM provider override")
|
|
201
206
|
@click.option("--model", help="Optional model override")
|
|
202
207
|
def process_files(
|
|
203
|
-
tenant_id: str,
|
|
204
208
|
user_id: Optional[str],
|
|
205
209
|
status: Optional[str],
|
|
206
210
|
extractor: Optional[str],
|
|
@@ -217,19 +221,22 @@ def process_files(
|
|
|
217
221
|
|
|
218
222
|
\b
|
|
219
223
|
# List completed files
|
|
220
|
-
rem process files --
|
|
224
|
+
rem process files --status completed
|
|
221
225
|
|
|
222
226
|
\b
|
|
223
227
|
# Extract from CV files
|
|
224
|
-
rem process files --
|
|
228
|
+
rem process files --extractor cv-parser-v1 --limit 10
|
|
225
229
|
|
|
226
230
|
\b
|
|
227
231
|
# Extract with provider override
|
|
228
|
-
rem process files --
|
|
232
|
+
rem process files --extractor contract-analyzer-v1 \\
|
|
229
233
|
--provider anthropic --model claude-sonnet-4-5
|
|
230
234
|
"""
|
|
235
|
+
from ...settings import settings
|
|
236
|
+
effective_user_id = user_id or settings.test.effective_user_id
|
|
237
|
+
|
|
231
238
|
logger.warning("Not implemented yet")
|
|
232
|
-
logger.info(f"Would process files for
|
|
239
|
+
logger.info(f"Would process files for user: {effective_user_id}")
|
|
233
240
|
|
|
234
241
|
if user_id:
|
|
235
242
|
logger.info(f"Filter: user_id={user_id}")
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Scaffold command - generate project structure for REM-based applications.
|
|
3
|
+
|
|
4
|
+
TODO: Implement this command to generate:
|
|
5
|
+
- my_app/main.py (entry point with create_app)
|
|
6
|
+
- my_app/models.py (example CoreModel subclass)
|
|
7
|
+
- my_app/routers/ (example FastAPI router)
|
|
8
|
+
- schemas/agents/ (example agent schema)
|
|
9
|
+
- schemas/evaluators/ (example evaluator)
|
|
10
|
+
- sql/migrations/ (empty migrations directory)
|
|
11
|
+
- pyproject.toml (with remdb dependency)
|
|
12
|
+
- README.md (basic usage instructions)
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
rem scaffold my-app
|
|
16
|
+
rem scaffold my-app --with-examples # Include example models/routers/tools
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import click
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.command()
|
|
23
|
+
@click.argument("name")
|
|
24
|
+
@click.option("--with-examples", is_flag=True, help="Include example code")
|
|
25
|
+
def scaffold(name: str, with_examples: bool) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Generate a new REM-based project structure.
|
|
28
|
+
|
|
29
|
+
NAME is the project directory name to create.
|
|
30
|
+
"""
|
|
31
|
+
click.echo(f"TODO: Scaffold command not yet implemented")
|
|
32
|
+
click.echo(f"Would create project: {name}")
|
|
33
|
+
click.echo(f"With examples: {with_examples}")
|
|
34
|
+
click.echo()
|
|
35
|
+
click.echo("For now, manually create this structure:")
|
|
36
|
+
click.echo(f"""
|
|
37
|
+
{name}/
|
|
38
|
+
├── {name.replace('-', '_')}/
|
|
39
|
+
│ ├── main.py # Entry point (create_app + extensions)
|
|
40
|
+
│ ├── models.py # Custom models (inherit CoreModel)
|
|
41
|
+
│ └── routers/ # Custom FastAPI routers
|
|
42
|
+
├── schemas/
|
|
43
|
+
│ ├── agents/ # Custom agent YAML schemas
|
|
44
|
+
│ └── evaluators/ # Custom evaluator schemas
|
|
45
|
+
├── sql/migrations/ # Custom SQL migrations
|
|
46
|
+
└── pyproject.toml
|
|
47
|
+
""")
|
rem/cli/commands/schema.py
CHANGED
|
@@ -8,6 +8,7 @@ Usage:
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
import asyncio
|
|
11
|
+
import importlib
|
|
11
12
|
from pathlib import Path
|
|
12
13
|
|
|
13
14
|
import click
|
|
@@ -15,68 +16,116 @@ from loguru import logger
|
|
|
15
16
|
|
|
16
17
|
from ...settings import settings
|
|
17
18
|
from ...services.postgres.schema_generator import SchemaGenerator
|
|
19
|
+
from ...utils.sql_paths import get_package_sql_dir, get_package_migrations_dir
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _import_model_modules() -> list[str]:
|
|
23
|
+
"""
|
|
24
|
+
Import modules specified in MODELS__IMPORT_MODULES setting.
|
|
25
|
+
|
|
26
|
+
This ensures downstream models decorated with @rem.register_model
|
|
27
|
+
are registered before schema generation.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
List of successfully imported module names
|
|
31
|
+
"""
|
|
32
|
+
imported = []
|
|
33
|
+
for module_name in settings.models.module_list:
|
|
34
|
+
try:
|
|
35
|
+
importlib.import_module(module_name)
|
|
36
|
+
imported.append(module_name)
|
|
37
|
+
logger.debug(f"Imported model module: {module_name}")
|
|
38
|
+
except ImportError as e:
|
|
39
|
+
logger.warning(f"Failed to import model module '{module_name}': {e}")
|
|
40
|
+
click.echo(
|
|
41
|
+
click.style(f" ⚠ Could not import '{module_name}': {e}", fg="yellow"),
|
|
42
|
+
err=True,
|
|
43
|
+
)
|
|
44
|
+
return imported
|
|
18
45
|
|
|
19
46
|
|
|
20
47
|
@click.command()
|
|
21
|
-
@click.option(
|
|
22
|
-
"--models",
|
|
23
|
-
"-m",
|
|
24
|
-
required=True,
|
|
25
|
-
type=click.Path(exists=True, path_type=Path),
|
|
26
|
-
help="Directory containing Pydantic models",
|
|
27
|
-
)
|
|
28
48
|
@click.option(
|
|
29
49
|
"--output",
|
|
30
50
|
"-o",
|
|
31
51
|
type=click.Path(path_type=Path),
|
|
32
|
-
default="
|
|
33
|
-
help="Output SQL file (default:
|
|
52
|
+
default="002_install_models.sql",
|
|
53
|
+
help="Output SQL file (default: 002_install_models.sql)",
|
|
34
54
|
)
|
|
35
55
|
@click.option(
|
|
36
56
|
"--output-dir",
|
|
37
57
|
type=click.Path(path_type=Path),
|
|
38
58
|
default=None,
|
|
39
|
-
help=
|
|
59
|
+
help="Base output directory (default: package sql/migrations)",
|
|
40
60
|
)
|
|
41
|
-
def generate(
|
|
61
|
+
def generate(output: Path, output_dir: Path | None):
|
|
42
62
|
"""
|
|
43
|
-
Generate database schema from Pydantic models.
|
|
63
|
+
Generate database schema from registered Pydantic models.
|
|
44
64
|
|
|
45
|
-
|
|
65
|
+
Uses the model registry (core models + user-registered models) to generate:
|
|
46
66
|
- CREATE TABLE statements
|
|
47
67
|
- Embeddings tables (embeddings_<table>)
|
|
48
68
|
- KV_STORE triggers for cache maintenance
|
|
49
69
|
- Indexes (foreground only)
|
|
50
70
|
|
|
51
|
-
Output is written to src/rem/sql/
|
|
71
|
+
Output is written to src/rem/sql/migrations/002_install_models.sql by default.
|
|
52
72
|
|
|
53
73
|
Example:
|
|
54
|
-
rem db schema generate
|
|
74
|
+
rem db schema generate
|
|
75
|
+
|
|
76
|
+
To register custom models in downstream apps:
|
|
77
|
+
|
|
78
|
+
1. Create models with @rem.register_model decorator:
|
|
79
|
+
|
|
80
|
+
# models/__init__.py
|
|
81
|
+
import rem
|
|
82
|
+
from rem.models.core import CoreModel
|
|
83
|
+
|
|
84
|
+
@rem.register_model
|
|
85
|
+
class MyEntity(CoreModel):
|
|
86
|
+
name: str
|
|
87
|
+
|
|
88
|
+
2. Set MODELS__IMPORT_MODULES in your .env:
|
|
89
|
+
|
|
90
|
+
MODELS__IMPORT_MODULES=models
|
|
91
|
+
|
|
92
|
+
3. Run schema generation:
|
|
93
|
+
|
|
94
|
+
rem db schema generate
|
|
55
95
|
|
|
56
96
|
This creates:
|
|
57
|
-
- src/rem/sql/
|
|
97
|
+
- src/rem/sql/migrations/002_install_models.sql - Entity tables and triggers
|
|
58
98
|
- src/rem/sql/background_indexes.sql - HNSW indexes (apply after data load)
|
|
59
99
|
|
|
60
|
-
After generation,
|
|
61
|
-
rem db
|
|
100
|
+
After generation, verify with:
|
|
101
|
+
rem db diff
|
|
62
102
|
"""
|
|
63
|
-
|
|
103
|
+
from ...registry import get_model_registry
|
|
104
|
+
|
|
105
|
+
# Import downstream model modules to trigger @rem.register_model decorators
|
|
106
|
+
imported_modules = _import_model_modules()
|
|
107
|
+
if imported_modules:
|
|
108
|
+
click.echo(f"Imported model modules: {', '.join(imported_modules)}")
|
|
109
|
+
|
|
110
|
+
registry = get_model_registry()
|
|
111
|
+
models = registry.get_models(include_core=True)
|
|
112
|
+
click.echo(f"Generating schema from {len(models)} registered models")
|
|
64
113
|
|
|
65
|
-
#
|
|
66
|
-
actual_output_dir = output_dir or
|
|
114
|
+
# Default to package migrations directory
|
|
115
|
+
actual_output_dir = output_dir or get_package_migrations_dir()
|
|
67
116
|
generator = SchemaGenerator(output_dir=actual_output_dir)
|
|
68
117
|
|
|
69
|
-
# Generate schema
|
|
118
|
+
# Generate schema from registry
|
|
70
119
|
try:
|
|
71
|
-
schema_sql = asyncio.run(generator.
|
|
120
|
+
schema_sql = asyncio.run(generator.generate_from_registry(output_file=output.name))
|
|
72
121
|
|
|
73
122
|
click.echo(f"✓ Schema generated: {len(generator.schemas)} tables")
|
|
74
123
|
click.echo(f"✓ Written to: {actual_output_dir / output.name}")
|
|
75
124
|
|
|
76
|
-
# Generate background indexes
|
|
125
|
+
# Generate background indexes in parent sql dir
|
|
77
126
|
background_indexes = generator.generate_background_indexes()
|
|
78
127
|
if background_indexes:
|
|
79
|
-
bg_file =
|
|
128
|
+
bg_file = get_package_sql_dir() / "background_indexes.sql"
|
|
80
129
|
bg_file.write_text(background_indexes)
|
|
81
130
|
click.echo(f"✓ Background indexes: {bg_file}")
|
|
82
131
|
|
|
@@ -94,48 +143,46 @@ def generate(models: Path, output: Path, output_dir: Path | None):
|
|
|
94
143
|
|
|
95
144
|
|
|
96
145
|
@click.command()
|
|
97
|
-
|
|
98
|
-
"--models",
|
|
99
|
-
"-m",
|
|
100
|
-
required=True,
|
|
101
|
-
type=click.Path(exists=True, path_type=Path),
|
|
102
|
-
help="Directory containing Pydantic models",
|
|
103
|
-
)
|
|
104
|
-
def validate(models: Path):
|
|
146
|
+
def validate():
|
|
105
147
|
"""
|
|
106
|
-
Validate Pydantic models for schema generation.
|
|
148
|
+
Validate registered Pydantic models for schema generation.
|
|
107
149
|
|
|
108
150
|
Checks:
|
|
109
|
-
- Models can be loaded
|
|
151
|
+
- Models can be loaded from registry
|
|
110
152
|
- Models have suitable entity_key fields
|
|
111
153
|
- Fields with embeddings are properly configured
|
|
154
|
+
|
|
155
|
+
Set MODELS__IMPORT_MODULES to include custom models from downstream apps.
|
|
112
156
|
"""
|
|
113
|
-
|
|
157
|
+
from ...registry import get_model_registry
|
|
114
158
|
|
|
115
|
-
|
|
116
|
-
|
|
159
|
+
# Import downstream model modules to trigger @rem.register_model decorators
|
|
160
|
+
imported_modules = _import_model_modules()
|
|
161
|
+
if imported_modules:
|
|
162
|
+
click.echo(f"Imported model modules: {', '.join(imported_modules)}")
|
|
117
163
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
164
|
+
registry = get_model_registry()
|
|
165
|
+
models = registry.get_models(include_core=True)
|
|
166
|
+
|
|
167
|
+
click.echo(f"Validating {len(models)} registered models")
|
|
121
168
|
|
|
122
|
-
|
|
169
|
+
if not models:
|
|
170
|
+
click.echo("✗ No models found in registry", err=True)
|
|
171
|
+
raise click.Abort()
|
|
123
172
|
|
|
173
|
+
generator = SchemaGenerator()
|
|
124
174
|
errors: list[str] = []
|
|
125
175
|
warnings: list[str] = []
|
|
126
176
|
|
|
127
|
-
for model_name,
|
|
128
|
-
|
|
129
|
-
|
|
177
|
+
for model_name, ext in models.items():
|
|
178
|
+
model = ext.model
|
|
179
|
+
table_name = ext.table_name or generator.infer_table_name(model)
|
|
180
|
+
entity_key = ext.entity_key_field or generator.infer_entity_key_field(model)
|
|
130
181
|
|
|
131
182
|
# Check for entity_key
|
|
132
183
|
if entity_key == "id":
|
|
133
184
|
warnings.append(f"{model_name}: No natural key field, using 'id'")
|
|
134
185
|
|
|
135
|
-
# Check for embeddable fields
|
|
136
|
-
# TODO: Implement should_embed_field check
|
|
137
|
-
embeddable: list[str] = [] # Placeholder - needs implementation
|
|
138
|
-
|
|
139
186
|
click.echo(f" {model_name} -> {table_name} (key: {entity_key})")
|
|
140
187
|
|
|
141
188
|
if warnings:
|
|
@@ -158,7 +205,7 @@ def validate(models: Path):
|
|
|
158
205
|
"-o",
|
|
159
206
|
type=click.Path(path_type=Path),
|
|
160
207
|
default=None,
|
|
161
|
-
help=
|
|
208
|
+
help="Output file for background indexes (default: package sql/background_indexes.sql)",
|
|
162
209
|
)
|
|
163
210
|
def indexes(output: Path):
|
|
164
211
|
"""
|
rem/cli/main.py
CHANGED
|
@@ -22,17 +22,30 @@ except Exception:
|
|
|
22
22
|
__version__ = "unknown"
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
def _configure_logger(level: str):
|
|
26
|
+
"""Configure loguru with custom level icons."""
|
|
27
|
+
logger.remove()
|
|
28
|
+
|
|
29
|
+
# Configure level icons - only warnings and errors get visual indicators
|
|
30
|
+
logger.level("DEBUG", icon=" ")
|
|
31
|
+
logger.level("INFO", icon=" ")
|
|
32
|
+
logger.level("WARNING", icon="🟠")
|
|
33
|
+
logger.level("ERROR", icon="🔴")
|
|
34
|
+
logger.level("CRITICAL", icon="🔴")
|
|
35
|
+
|
|
36
|
+
logger.add(
|
|
37
|
+
sys.stderr,
|
|
38
|
+
level=level,
|
|
39
|
+
format="<green>{time:HH:mm:ss}</green> | {level.icon} <level>{level: <8}</level> | <level>{message}</level>",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
25
43
|
@click.group()
|
|
26
44
|
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose logging")
|
|
27
45
|
@click.version_option(version=__version__, prog_name="rem")
|
|
28
46
|
def cli(verbose: bool):
|
|
29
47
|
"""REM - Resources Entities Moments system CLI."""
|
|
30
|
-
if verbose
|
|
31
|
-
logger.remove()
|
|
32
|
-
logger.add(sys.stderr, level="DEBUG")
|
|
33
|
-
else:
|
|
34
|
-
logger.remove()
|
|
35
|
-
logger.add(sys.stderr, level="INFO")
|
|
48
|
+
_configure_logger("DEBUG" if verbose else "INFO")
|
|
36
49
|
|
|
37
50
|
|
|
38
51
|
@cli.group()
|
|
@@ -65,6 +78,12 @@ def dreaming():
|
|
|
65
78
|
pass
|
|
66
79
|
|
|
67
80
|
|
|
81
|
+
@cli.group()
|
|
82
|
+
def cluster():
|
|
83
|
+
"""Kubernetes cluster deployment and management."""
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
|
|
68
87
|
# Register commands
|
|
69
88
|
from .commands.schema import register_commands as register_schema_commands
|
|
70
89
|
from .commands.db import register_commands as register_db_commands
|
|
@@ -75,16 +94,20 @@ from .commands.experiments import experiments as experiments_group
|
|
|
75
94
|
from .commands.configure import register_command as register_configure_command
|
|
76
95
|
from .commands.serve import register_command as register_serve_command
|
|
77
96
|
from .commands.mcp import register_command as register_mcp_command
|
|
97
|
+
from .commands.scaffold import scaffold as scaffold_command
|
|
98
|
+
from .commands.cluster import register_commands as register_cluster_commands
|
|
78
99
|
|
|
79
100
|
register_schema_commands(schema)
|
|
80
101
|
register_db_commands(db)
|
|
81
102
|
register_process_commands(process)
|
|
82
103
|
register_dreaming_commands(dreaming)
|
|
104
|
+
register_cluster_commands(cluster)
|
|
83
105
|
register_ask_command(cli)
|
|
84
106
|
register_configure_command(cli)
|
|
85
107
|
register_serve_command(cli)
|
|
86
108
|
register_mcp_command(cli)
|
|
87
109
|
cli.add_command(experiments_group)
|
|
110
|
+
cli.add_command(scaffold_command)
|
|
88
111
|
|
|
89
112
|
|
|
90
113
|
def main():
|
rem/config.py
CHANGED
|
@@ -15,7 +15,7 @@ File Format (~/.rem/config.yaml):
|
|
|
15
15
|
pool_max_size: 20
|
|
16
16
|
|
|
17
17
|
llm:
|
|
18
|
-
default_model:
|
|
18
|
+
default_model: openai:gpt-4.1
|
|
19
19
|
openai_api_key: sk-...
|
|
20
20
|
anthropic_api_key: sk-ant-...
|
|
21
21
|
|
|
@@ -95,9 +95,16 @@ def load_config() -> dict[str, Any]:
|
|
|
95
95
|
"""
|
|
96
96
|
Load configuration from ~/.rem/config.yaml.
|
|
97
97
|
|
|
98
|
+
Set REM_SKIP_CONFIG=1 to skip loading the config file (useful when using .env files).
|
|
99
|
+
|
|
98
100
|
Returns:
|
|
99
|
-
Configuration dictionary (empty if file doesn't exist)
|
|
101
|
+
Configuration dictionary (empty if file doesn't exist or skipped)
|
|
100
102
|
"""
|
|
103
|
+
# Allow skipping config file via environment variable
|
|
104
|
+
if os.environ.get("REM_SKIP_CONFIG", "").lower() in ("1", "true", "yes"):
|
|
105
|
+
logger.debug("Skipping config file (REM_SKIP_CONFIG is set)")
|
|
106
|
+
return {}
|
|
107
|
+
|
|
101
108
|
config_path = get_config_path()
|
|
102
109
|
|
|
103
110
|
if not config_path.exists():
|
|
@@ -216,7 +223,7 @@ def get_default_config() -> dict[str, Any]:
|
|
|
216
223
|
"pool_max_size": 20,
|
|
217
224
|
},
|
|
218
225
|
"llm": {
|
|
219
|
-
"default_model": "
|
|
226
|
+
"default_model": "openai:gpt-4.1",
|
|
220
227
|
"default_temperature": 0.5,
|
|
221
228
|
# API keys will be prompted for in wizard
|
|
222
229
|
# "openai_api_key": "",
|
rem/models/core/core_model.py
CHANGED
|
@@ -52,7 +52,13 @@ class CoreModel(BaseModel):
|
|
|
52
52
|
default=None, description="Tenant identifier for multi-tenancy isolation"
|
|
53
53
|
)
|
|
54
54
|
user_id: Optional[str] = Field(
|
|
55
|
-
default=None,
|
|
55
|
+
default=None,
|
|
56
|
+
description=(
|
|
57
|
+
"Owner user identifier (tenant-scoped). This is a VARCHAR(256), not a UUID, "
|
|
58
|
+
"to allow flexibility for external identity providers. Typically generated as "
|
|
59
|
+
"a hash of the user's email address. In future, other strong unique claims "
|
|
60
|
+
"(e.g., OAuth sub, verified phone) could also be used for generation."
|
|
61
|
+
),
|
|
56
62
|
)
|
|
57
63
|
graph_edges: list[dict] = Field(
|
|
58
64
|
default_factory=list,
|
rem/models/core/experiment.py
CHANGED
|
@@ -318,6 +318,15 @@ class ExperimentConfig(BaseModel):
|
|
|
318
318
|
)
|
|
319
319
|
)
|
|
320
320
|
|
|
321
|
+
task: str = Field(
|
|
322
|
+
default="general",
|
|
323
|
+
description=(
|
|
324
|
+
"Task name for organizing experiments by purpose.\n"
|
|
325
|
+
"Used with agent name to form directory: {agent}/{task}/\n"
|
|
326
|
+
"Examples: 'risk-assessment', 'classification', 'general'"
|
|
327
|
+
)
|
|
328
|
+
)
|
|
329
|
+
|
|
321
330
|
description: str = Field(
|
|
322
331
|
description="Human-readable description of experiment purpose and goals"
|
|
323
332
|
)
|
|
@@ -410,6 +419,24 @@ class ExperimentConfig(BaseModel):
|
|
|
410
419
|
|
|
411
420
|
return v
|
|
412
421
|
|
|
422
|
+
@field_validator("task")
|
|
423
|
+
@classmethod
|
|
424
|
+
def validate_task(cls, v: str) -> str:
|
|
425
|
+
"""Validate task name follows conventions."""
|
|
426
|
+
if not v:
|
|
427
|
+
return "general" # Default value
|
|
428
|
+
|
|
429
|
+
if not v.islower():
|
|
430
|
+
raise ValueError("Task name must be lowercase")
|
|
431
|
+
|
|
432
|
+
if " " in v:
|
|
433
|
+
raise ValueError("Task name cannot contain spaces (use hyphens)")
|
|
434
|
+
|
|
435
|
+
if not all(c.isalnum() or c == "-" for c in v):
|
|
436
|
+
raise ValueError("Task name can only contain lowercase letters, numbers, and hyphens")
|
|
437
|
+
|
|
438
|
+
return v
|
|
439
|
+
|
|
413
440
|
@field_validator("tags")
|
|
414
441
|
@classmethod
|
|
415
442
|
def validate_tags(cls, v: list[str]) -> list[str]:
|
|
@@ -420,6 +447,15 @@ class ExperimentConfig(BaseModel):
|
|
|
420
447
|
"""Get the experiment directory path."""
|
|
421
448
|
return Path(base_path) / self.name
|
|
422
449
|
|
|
450
|
+
def get_agent_task_dir(self, base_path: str = ".experiments") -> Path:
|
|
451
|
+
"""
|
|
452
|
+
Get the experiment directory path organized by agent/task.
|
|
453
|
+
|
|
454
|
+
Returns: Path like .experiments/{agent}/{task}/
|
|
455
|
+
This is the recommended structure for S3 export compatibility.
|
|
456
|
+
"""
|
|
457
|
+
return Path(base_path) / self.agent_schema_ref.name / self.task
|
|
458
|
+
|
|
423
459
|
def get_config_path(self, base_path: str = ".experiments") -> Path:
|
|
424
460
|
"""Get the path to experiment.yaml file."""
|
|
425
461
|
return self.get_experiment_dir(base_path) / "experiment.yaml"
|
|
@@ -428,6 +464,22 @@ class ExperimentConfig(BaseModel):
|
|
|
428
464
|
"""Get the path to README.md file."""
|
|
429
465
|
return self.get_experiment_dir(base_path) / "README.md"
|
|
430
466
|
|
|
467
|
+
def get_evaluator_filename(self) -> str:
|
|
468
|
+
"""
|
|
469
|
+
Get the evaluator filename with task prefix.
|
|
470
|
+
|
|
471
|
+
Returns: {agent_name}-{task}.yaml (e.g., siggy-risk-assessment.yaml)
|
|
472
|
+
"""
|
|
473
|
+
return f"{self.agent_schema_ref.name}-{self.task}.yaml"
|
|
474
|
+
|
|
475
|
+
def get_s3_export_path(self, bucket: str, version: str = "v0") -> str:
|
|
476
|
+
"""
|
|
477
|
+
Get the S3 path for exporting this experiment.
|
|
478
|
+
|
|
479
|
+
Returns: s3://{bucket}/{version}/datasets/calibration/experiments/{agent}/{task}/
|
|
480
|
+
"""
|
|
481
|
+
return f"s3://{bucket}/{version}/datasets/calibration/experiments/{self.agent_schema_ref.name}/{self.task}"
|
|
482
|
+
|
|
431
483
|
def to_yaml(self) -> str:
|
|
432
484
|
"""Export configuration as YAML string."""
|
|
433
485
|
import yaml
|
|
@@ -483,6 +535,7 @@ class ExperimentConfig(BaseModel):
|
|
|
483
535
|
## Configuration
|
|
484
536
|
|
|
485
537
|
**Status**: `{self.status.value}`
|
|
538
|
+
**Task**: `{self.task}`
|
|
486
539
|
**Tags**: {', '.join(f'`{tag}`' for tag in self.tags) if self.tags else 'None'}
|
|
487
540
|
|
|
488
541
|
## Agent Schema
|
|
@@ -494,6 +547,7 @@ class ExperimentConfig(BaseModel):
|
|
|
494
547
|
## Evaluator Schema
|
|
495
548
|
|
|
496
549
|
- **Name**: `{self.evaluator_schema_ref.name}`
|
|
550
|
+
- **File**: `{self.get_evaluator_filename()}`
|
|
497
551
|
- **Type**: `{self.evaluator_schema_ref.type}`
|
|
498
552
|
|
|
499
553
|
## Datasets
|
rem/models/core/rem_query.py
CHANGED
|
@@ -112,7 +112,7 @@ class SearchParameters(BaseModel):
|
|
|
112
112
|
table_name: str = Field(..., description="Table to search (resources, moments, etc.)")
|
|
113
113
|
limit: int = Field(default=10, gt=0, description="Maximum results")
|
|
114
114
|
min_similarity: float = Field(
|
|
115
|
-
default=0.
|
|
115
|
+
default=0.3, ge=0.0, le=1.0, description="Minimum similarity score (0.3 recommended for general queries)"
|
|
116
116
|
)
|
|
117
117
|
|
|
118
118
|
|
|
@@ -198,7 +198,10 @@ class RemQuery(BaseModel):
|
|
|
198
198
|
| SQLParameters
|
|
199
199
|
| TraverseParameters
|
|
200
200
|
) = Field(..., description="Query parameters")
|
|
201
|
-
user_id: str = Field(
|
|
201
|
+
user_id: Optional[str] = Field(
|
|
202
|
+
default=None,
|
|
203
|
+
description="User identifier (UUID5 hash of email). None = anonymous (shared/public data only)"
|
|
204
|
+
)
|
|
202
205
|
|
|
203
206
|
|
|
204
207
|
class TraverseStage(BaseModel):
|
rem/models/entities/__init__.py
CHANGED
|
@@ -5,6 +5,9 @@ Core entity types for the REM system:
|
|
|
5
5
|
- Resources: Base content units (documents, conversations, artifacts)
|
|
6
6
|
- ImageResources: Image-specific resources with CLIP embeddings
|
|
7
7
|
- Messages: Communication content
|
|
8
|
+
- Sessions: Conversation sessions (normal or evaluation mode)
|
|
9
|
+
- SharedSessions: Session sharing between users for collaboration
|
|
10
|
+
- Feedback: User feedback on messages/sessions with trace integration
|
|
8
11
|
- Users: User entities
|
|
9
12
|
- Files: File metadata and tracking
|
|
10
13
|
- Moments: Temporal narratives (meetings, coding sessions, conversations)
|
|
@@ -19,6 +22,8 @@ All entities inherit from CoreModel and support:
|
|
|
19
22
|
- Natural language labels for conversational queries
|
|
20
23
|
"""
|
|
21
24
|
|
|
25
|
+
from .domain_resource import DomainResource
|
|
26
|
+
from .feedback import Feedback, FeedbackCategory
|
|
22
27
|
from .file import File
|
|
23
28
|
from .image_resource import ImageResource
|
|
24
29
|
from .message import Message
|
|
@@ -27,12 +32,28 @@ from .ontology import Ontology
|
|
|
27
32
|
from .ontology_config import OntologyConfig
|
|
28
33
|
from .resource import Resource
|
|
29
34
|
from .schema import Schema
|
|
35
|
+
from .session import Session, SessionMode
|
|
36
|
+
from .shared_session import (
|
|
37
|
+
SharedSession,
|
|
38
|
+
SharedSessionCreate,
|
|
39
|
+
SharedWithMeResponse,
|
|
40
|
+
SharedWithMeSummary,
|
|
41
|
+
)
|
|
30
42
|
from .user import User, UserTier
|
|
31
43
|
|
|
32
44
|
__all__ = [
|
|
33
45
|
"Resource",
|
|
46
|
+
"DomainResource",
|
|
34
47
|
"ImageResource",
|
|
35
48
|
"Message",
|
|
49
|
+
"Session",
|
|
50
|
+
"SessionMode",
|
|
51
|
+
"SharedSession",
|
|
52
|
+
"SharedSessionCreate",
|
|
53
|
+
"SharedWithMeResponse",
|
|
54
|
+
"SharedWithMeSummary",
|
|
55
|
+
"Feedback",
|
|
56
|
+
"FeedbackCategory",
|
|
36
57
|
"User",
|
|
37
58
|
"UserTier",
|
|
38
59
|
"File",
|