remdb 0.3.14__py3-none-any.whl → 0.3.157__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/agentic/README.md +76 -0
- rem/agentic/__init__.py +15 -0
- rem/agentic/agents/__init__.py +32 -2
- rem/agentic/agents/agent_manager.py +310 -0
- rem/agentic/agents/sse_simulator.py +502 -0
- rem/agentic/context.py +51 -27
- rem/agentic/context_builder.py +5 -3
- rem/agentic/llm_provider_models.py +301 -0
- rem/agentic/mcp/tool_wrapper.py +155 -18
- rem/agentic/otel/setup.py +93 -4
- rem/agentic/providers/phoenix.py +371 -108
- rem/agentic/providers/pydantic_ai.py +280 -57
- rem/agentic/schema.py +361 -21
- rem/agentic/tools/rem_tools.py +3 -3
- rem/api/README.md +215 -1
- rem/api/deps.py +255 -0
- rem/api/main.py +132 -40
- rem/api/mcp_router/resources.py +1 -1
- rem/api/mcp_router/server.py +28 -5
- rem/api/mcp_router/tools.py +555 -7
- rem/api/routers/admin.py +494 -0
- rem/api/routers/auth.py +278 -4
- 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 +697 -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/__init__.py +13 -3
- rem/auth/middleware.py +186 -22
- rem/auth/providers/__init__.py +4 -1
- rem/auth/providers/email.py +215 -0
- rem/cli/commands/README.md +237 -64
- rem/cli/commands/cluster.py +1808 -0
- rem/cli/commands/configure.py +4 -7
- rem/cli/commands/db.py +386 -143
- rem/cli/commands/experiments.py +468 -76
- rem/cli/commands/process.py +14 -8
- rem/cli/commands/schema.py +97 -50
- rem/cli/commands/session.py +336 -0
- rem/cli/dreaming.py +2 -2
- rem/cli/main.py +29 -6
- rem/config.py +10 -3
- rem/models/core/core_model.py +7 -1
- rem/models/core/experiment.py +58 -14
- rem/models/core/rem_query.py +5 -2
- rem/models/entities/__init__.py +25 -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/ontology.py +1 -1
- rem/models/entities/ontology_config.py +1 -1
- rem/models/entities/session.py +83 -0
- rem/models/entities/shared_session.py +180 -0
- rem/models/entities/subscriber.py +175 -0
- rem/models/entities/user.py +1 -0
- rem/registry.py +10 -4
- rem/schemas/agents/core/agent-builder.yaml +134 -0
- rem/schemas/agents/examples/contract-analyzer.yaml +1 -1
- rem/schemas/agents/examples/contract-extractor.yaml +1 -1
- rem/schemas/agents/examples/cv-parser.yaml +1 -1
- rem/schemas/agents/rem.yaml +7 -3
- rem/services/__init__.py +3 -1
- rem/services/content/service.py +92 -19
- rem/services/email/__init__.py +10 -0
- rem/services/email/service.py +459 -0
- rem/services/email/templates.py +360 -0
- rem/services/embeddings/api.py +4 -4
- rem/services/embeddings/worker.py +16 -16
- rem/services/phoenix/client.py +154 -14
- rem/services/postgres/README.md +197 -15
- rem/services/postgres/__init__.py +2 -1
- rem/services/postgres/diff_service.py +547 -0
- rem/services/postgres/pydantic_to_sqlalchemy.py +470 -140
- rem/services/postgres/repository.py +132 -0
- rem/services/postgres/schema_generator.py +205 -4
- rem/services/postgres/service.py +6 -6
- rem/services/rem/parser.py +44 -9
- rem/services/rem/service.py +36 -2
- rem/services/session/compression.py +137 -51
- rem/services/session/reload.py +15 -8
- rem/settings.py +515 -27
- rem/sql/background_indexes.sql +21 -16
- rem/sql/migrations/001_install.sql +387 -54
- rem/sql/migrations/002_install_models.sql +2304 -377
- rem/sql/migrations/003_optional_extensions.sql +326 -0
- rem/sql/migrations/004_cache_system.sql +548 -0
- rem/sql/migrations/005_schema_update.sql +145 -0
- rem/utils/README.md +45 -0
- rem/utils/__init__.py +18 -0
- rem/utils/date_utils.py +2 -2
- rem/utils/files.py +157 -1
- rem/utils/model_helpers.py +156 -1
- rem/utils/schema_loader.py +220 -22
- rem/utils/sql_paths.py +146 -0
- rem/utils/sql_types.py +3 -1
- rem/utils/vision.py +1 -1
- rem/workers/__init__.py +3 -1
- rem/workers/db_listener.py +579 -0
- rem/workers/unlogged_maintainer.py +463 -0
- {remdb-0.3.14.dist-info → remdb-0.3.157.dist-info}/METADATA +340 -229
- {remdb-0.3.14.dist-info → remdb-0.3.157.dist-info}/RECORD +109 -80
- {remdb-0.3.14.dist-info → remdb-0.3.157.dist-info}/WHEEL +1 -1
- rem/sql/002_install_models.sql +0 -1068
- rem/sql/install_models.sql +0 -1051
- rem/sql/migrations/003_seed_default_user.sql +0 -48
- {remdb-0.3.14.dist-info → remdb-0.3.157.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SharedSession - Session sharing between users in REM.
|
|
3
|
+
|
|
4
|
+
SharedSessions enable collaborative access to conversation sessions. When a user
|
|
5
|
+
shares a session with another user, a SharedSession record is created to track
|
|
6
|
+
this relationship.
|
|
7
|
+
|
|
8
|
+
## Design Philosophy
|
|
9
|
+
|
|
10
|
+
Messages already have a session_id field that links them to sessions. The Session
|
|
11
|
+
entity itself is optional and can be left-joined - we don't require explicit Session
|
|
12
|
+
records for sharing to work. What matters is the session_id on messages.
|
|
13
|
+
|
|
14
|
+
SharedSession is a lightweight linking table that:
|
|
15
|
+
1. Records who shared which session with whom
|
|
16
|
+
2. Enables soft deletion (deleted_at) so shares can be revoked without data loss
|
|
17
|
+
3. Supports aggregation queries to see "who is sharing with me"
|
|
18
|
+
|
|
19
|
+
## Data Model
|
|
20
|
+
|
|
21
|
+
SharedSession
|
|
22
|
+
├── session_id: str # The session being shared (matches Message.session_id)
|
|
23
|
+
├── owner_user_id: str # Who owns/created the session (the sharer)
|
|
24
|
+
├── shared_with_user_id: str # Who the session is shared with (the recipient)
|
|
25
|
+
├── tenant_id: str # Multi-tenancy isolation
|
|
26
|
+
├── created_at: datetime # When the share was created
|
|
27
|
+
├── updated_at: datetime # Last modification
|
|
28
|
+
└── deleted_at: datetime # Soft delete (null = active share)
|
|
29
|
+
|
|
30
|
+
## Aggregation Query
|
|
31
|
+
|
|
32
|
+
The primary use case is answering: "Who is sharing messages with me?"
|
|
33
|
+
|
|
34
|
+
This is provided by a Postgres function that aggregates:
|
|
35
|
+
- Messages grouped by owner_user_id
|
|
36
|
+
- Joined with users table for name/email
|
|
37
|
+
- Counting messages with min/max dates
|
|
38
|
+
- Filtering out deleted shares
|
|
39
|
+
|
|
40
|
+
Result shape:
|
|
41
|
+
{
|
|
42
|
+
"user_id": "uuid",
|
|
43
|
+
"name": "John Doe",
|
|
44
|
+
"email": "john@example.com",
|
|
45
|
+
"message_count": 42,
|
|
46
|
+
"first_message_at": "2024-01-15T10:30:00Z",
|
|
47
|
+
"last_message_at": "2024-03-20T14:45:00Z",
|
|
48
|
+
"session_count": 3
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
## API Endpoints
|
|
52
|
+
|
|
53
|
+
1. POST /api/v1/sessions/{session_id}/share
|
|
54
|
+
- Share a session with another user
|
|
55
|
+
- Body: { "shared_with_user_id": "..." }
|
|
56
|
+
- Creates SharedSession record
|
|
57
|
+
|
|
58
|
+
2. DELETE /api/v1/sessions/{session_id}/share/{shared_with_user_id}
|
|
59
|
+
- Revoke a share (soft delete)
|
|
60
|
+
- Sets deleted_at on SharedSession
|
|
61
|
+
|
|
62
|
+
3. GET /api/v1/shared-with-me
|
|
63
|
+
- Get paginated aggregate of users sharing with you
|
|
64
|
+
- Query params: page, page_size (default 50)
|
|
65
|
+
- Returns: list of user summaries with message counts
|
|
66
|
+
|
|
67
|
+
4. GET /api/v1/shared-with-me/{user_id}/messages
|
|
68
|
+
- Get messages from a specific user's shared sessions
|
|
69
|
+
- Uses existing session message loading
|
|
70
|
+
- Respects pagination
|
|
71
|
+
|
|
72
|
+
## Soft Delete Pattern
|
|
73
|
+
|
|
74
|
+
Removing a share does NOT delete the SharedSession record. Instead:
|
|
75
|
+
- deleted_at is set to current timestamp
|
|
76
|
+
- All queries filter WHERE deleted_at IS NULL
|
|
77
|
+
- This preserves audit trail and allows "undo"
|
|
78
|
+
|
|
79
|
+
To permanently delete, an admin can run:
|
|
80
|
+
DELETE FROM shared_sessions WHERE deleted_at IS NOT NULL AND deleted_at < NOW() - INTERVAL '30 days'
|
|
81
|
+
|
|
82
|
+
## Example Usage
|
|
83
|
+
|
|
84
|
+
# Share a session
|
|
85
|
+
POST /api/v1/sessions/abc-123/share
|
|
86
|
+
{"shared_with_user_id": "user-456"}
|
|
87
|
+
|
|
88
|
+
# See who's sharing with me
|
|
89
|
+
GET /api/v1/shared-with-me
|
|
90
|
+
{
|
|
91
|
+
"data": [
|
|
92
|
+
{
|
|
93
|
+
"user_id": "user-789",
|
|
94
|
+
"name": "Alice",
|
|
95
|
+
"email": "alice@example.com",
|
|
96
|
+
"message_count": 150,
|
|
97
|
+
"session_count": 5,
|
|
98
|
+
"first_message_at": "2024-01-01T00:00:00Z",
|
|
99
|
+
"last_message_at": "2024-03-15T12:00:00Z"
|
|
100
|
+
}
|
|
101
|
+
],
|
|
102
|
+
"metadata": {"total": 1, "page": 1, "page_size": 50, ...}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# Get messages from Alice's shared sessions
|
|
106
|
+
GET /api/v1/shared-with-me/user-789/messages?page=1&page_size=50
|
|
107
|
+
|
|
108
|
+
# Revoke a share
|
|
109
|
+
DELETE /api/v1/sessions/abc-123/share/user-456
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
from datetime import datetime
|
|
113
|
+
from typing import Optional
|
|
114
|
+
|
|
115
|
+
from pydantic import BaseModel, Field
|
|
116
|
+
|
|
117
|
+
from ..core import CoreModel
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class SharedSession(CoreModel):
|
|
121
|
+
"""
|
|
122
|
+
Session sharing record between users.
|
|
123
|
+
|
|
124
|
+
Links a session (identified by session_id from Message records) to a
|
|
125
|
+
recipient user, enabling collaborative access to conversation history.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
session_id: str = Field(
|
|
129
|
+
...,
|
|
130
|
+
description="The session being shared (matches Message.session_id)",
|
|
131
|
+
)
|
|
132
|
+
owner_user_id: str = Field(
|
|
133
|
+
...,
|
|
134
|
+
description="User ID of the session owner (the sharer)",
|
|
135
|
+
)
|
|
136
|
+
shared_with_user_id: str = Field(
|
|
137
|
+
...,
|
|
138
|
+
description="User ID of the recipient (who can now view the session)",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class SharedSessionCreate(BaseModel):
|
|
143
|
+
"""Request to create a session share."""
|
|
144
|
+
|
|
145
|
+
shared_with_user_id: str = Field(
|
|
146
|
+
...,
|
|
147
|
+
description="User ID to share the session with",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class SharedWithMeSummary(BaseModel):
|
|
152
|
+
"""
|
|
153
|
+
Aggregate summary of a user sharing sessions with you.
|
|
154
|
+
|
|
155
|
+
Returned by GET /api/v1/shared-with-me endpoint.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
user_id: str = Field(description="User ID of the person sharing with you")
|
|
159
|
+
name: Optional[str] = Field(default=None, description="User's display name")
|
|
160
|
+
email: Optional[str] = Field(default=None, description="User's email address")
|
|
161
|
+
message_count: int = Field(description="Total messages across all shared sessions")
|
|
162
|
+
session_count: int = Field(description="Number of sessions shared with you")
|
|
163
|
+
first_message_at: Optional[datetime] = Field(
|
|
164
|
+
default=None,
|
|
165
|
+
description="Timestamp of earliest message in shared sessions",
|
|
166
|
+
)
|
|
167
|
+
last_message_at: Optional[datetime] = Field(
|
|
168
|
+
default=None,
|
|
169
|
+
description="Timestamp of most recent message in shared sessions",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class SharedWithMeResponse(BaseModel):
|
|
174
|
+
"""Response for paginated shared-with-me query."""
|
|
175
|
+
|
|
176
|
+
object: str = "list"
|
|
177
|
+
data: list[SharedWithMeSummary] = Field(
|
|
178
|
+
description="List of users sharing sessions with you"
|
|
179
|
+
)
|
|
180
|
+
metadata: dict = Field(description="Pagination metadata")
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Subscriber - Email subscription management.
|
|
3
|
+
|
|
4
|
+
This model stores subscribers who sign up via websites/apps.
|
|
5
|
+
Subscribers can be collected before user registration for newsletters,
|
|
6
|
+
updates, and approval-based access control.
|
|
7
|
+
|
|
8
|
+
Key features:
|
|
9
|
+
- Deterministic UUID from email (same email = same ID)
|
|
10
|
+
- Approval workflow for access control
|
|
11
|
+
- Tags for segmentation
|
|
12
|
+
- Origin tracking for analytics
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import uuid
|
|
16
|
+
from datetime import datetime, timezone
|
|
17
|
+
from enum import Enum
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
from pydantic import Field, EmailStr, model_validator
|
|
21
|
+
|
|
22
|
+
from ..core import CoreModel
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SubscriberStatus(str, Enum):
|
|
26
|
+
"""Subscription status."""
|
|
27
|
+
|
|
28
|
+
ACTIVE = "active" # Actively subscribed
|
|
29
|
+
UNSUBSCRIBED = "unsubscribed" # User unsubscribed
|
|
30
|
+
BOUNCED = "bounced" # Email bounced
|
|
31
|
+
PENDING = "pending" # Pending confirmation (if double opt-in)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SubscriberOrigin(str, Enum):
|
|
35
|
+
"""Where the subscription originated from."""
|
|
36
|
+
|
|
37
|
+
WEBSITE = "website" # Main website subscribe form
|
|
38
|
+
LANDING_PAGE = "landing_page" # Campaign landing page
|
|
39
|
+
APP = "app" # In-app subscription
|
|
40
|
+
IMPORT = "import" # Bulk import
|
|
41
|
+
REFERRAL = "referral" # Referred by another user
|
|
42
|
+
OTHER = "other"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Subscriber(CoreModel):
|
|
46
|
+
"""
|
|
47
|
+
Email subscriber for newsletters and access control.
|
|
48
|
+
|
|
49
|
+
This model captures subscribers who sign up via the website, landing pages,
|
|
50
|
+
or in-app prompts. Uses deterministic UUID from email for natural upserts.
|
|
51
|
+
|
|
52
|
+
Access control via `approved` field:
|
|
53
|
+
- When email auth checks subscriber status, only approved subscribers
|
|
54
|
+
can complete login (if approval is enabled in settings).
|
|
55
|
+
- Subscribers can be pre-approved, or approved manually/automatically.
|
|
56
|
+
|
|
57
|
+
Usage:
|
|
58
|
+
from rem.services.postgres import Repository
|
|
59
|
+
from rem.models.entities import Subscriber, SubscriberStatus
|
|
60
|
+
|
|
61
|
+
repo = Repository(Subscriber, db=db)
|
|
62
|
+
|
|
63
|
+
# Create subscriber (ID auto-generated from email)
|
|
64
|
+
subscriber = Subscriber(
|
|
65
|
+
email="user@example.com",
|
|
66
|
+
name="John Doe",
|
|
67
|
+
origin=SubscriberOrigin.WEBSITE,
|
|
68
|
+
)
|
|
69
|
+
await repo.upsert(subscriber)
|
|
70
|
+
|
|
71
|
+
# Check if approved for login
|
|
72
|
+
subscriber = await repo.get_by_id(subscriber.id, tenant_id="default")
|
|
73
|
+
if subscriber and subscriber.approved:
|
|
74
|
+
# Allow login
|
|
75
|
+
pass
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
# Required field
|
|
79
|
+
email: EmailStr = Field(
|
|
80
|
+
description="Subscriber's email address (unique identifier)"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Optional fields
|
|
84
|
+
name: Optional[str] = Field(
|
|
85
|
+
default=None,
|
|
86
|
+
description="Subscriber's name (optional)"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
comment: Optional[str] = Field(
|
|
90
|
+
default=None,
|
|
91
|
+
max_length=500,
|
|
92
|
+
description="Optional comment or message from subscriber"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
status: SubscriberStatus = Field(
|
|
96
|
+
default=SubscriberStatus.ACTIVE,
|
|
97
|
+
description="Current subscription status"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Access control
|
|
101
|
+
approved: bool = Field(
|
|
102
|
+
default=False,
|
|
103
|
+
description="Whether subscriber is approved for login (for approval workflows)"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
approved_at: Optional[datetime] = Field(
|
|
107
|
+
default=None,
|
|
108
|
+
description="When the subscriber was approved"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
approved_by: Optional[str] = Field(
|
|
112
|
+
default=None,
|
|
113
|
+
description="Who approved the subscriber (user ID or 'system')"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Origin tracking
|
|
117
|
+
origin: SubscriberOrigin = Field(
|
|
118
|
+
default=SubscriberOrigin.WEBSITE,
|
|
119
|
+
description="Where the subscription originated"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
origin_detail: Optional[str] = Field(
|
|
123
|
+
default=None,
|
|
124
|
+
description="Additional origin context (e.g., campaign name, page URL)"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Timestamps
|
|
128
|
+
subscribed_at: datetime = Field(
|
|
129
|
+
default_factory=lambda: datetime.now(timezone.utc),
|
|
130
|
+
description="When the subscription was created"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
unsubscribed_at: Optional[datetime] = Field(
|
|
134
|
+
default=None,
|
|
135
|
+
description="When the user unsubscribed (if applicable)"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Compliance
|
|
139
|
+
ip_address: Optional[str] = Field(
|
|
140
|
+
default=None,
|
|
141
|
+
description="IP address at subscription time (for compliance)"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
user_agent: Optional[str] = Field(
|
|
145
|
+
default=None,
|
|
146
|
+
description="Browser user agent at subscription time"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Segmentation
|
|
150
|
+
tags: list[str] = Field(
|
|
151
|
+
default_factory=list,
|
|
152
|
+
description="Tags for segmentation (e.g., ['early-access', 'beta'])"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def email_to_uuid(email: str) -> uuid.UUID:
|
|
157
|
+
"""Generate a deterministic UUID from an email address.
|
|
158
|
+
|
|
159
|
+
Uses UUID v5 with DNS namespace for consistency with
|
|
160
|
+
EmailService.generate_user_id_from_email().
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
email: Email address
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Deterministic UUID
|
|
167
|
+
"""
|
|
168
|
+
return uuid.uuid5(uuid.NAMESPACE_DNS, email.lower().strip())
|
|
169
|
+
|
|
170
|
+
@model_validator(mode="after")
|
|
171
|
+
def set_id_from_email(self) -> "Subscriber":
|
|
172
|
+
"""Auto-generate deterministic ID from email for natural upsert."""
|
|
173
|
+
if self.email:
|
|
174
|
+
self.id = self.email_to_uuid(self.email)
|
|
175
|
+
return self
|
rem/models/entities/user.py
CHANGED
rem/registry.py
CHANGED
|
@@ -123,6 +123,7 @@ class ModelRegistry:
|
|
|
123
123
|
return
|
|
124
124
|
|
|
125
125
|
from .models.entities import (
|
|
126
|
+
Feedback,
|
|
126
127
|
File,
|
|
127
128
|
ImageResource,
|
|
128
129
|
Message,
|
|
@@ -131,19 +132,24 @@ class ModelRegistry:
|
|
|
131
132
|
OntologyConfig,
|
|
132
133
|
Resource,
|
|
133
134
|
Schema,
|
|
135
|
+
Session,
|
|
136
|
+
SharedSession,
|
|
134
137
|
User,
|
|
135
138
|
)
|
|
136
139
|
|
|
137
140
|
core_models = [
|
|
138
|
-
|
|
141
|
+
Feedback,
|
|
142
|
+
File,
|
|
139
143
|
ImageResource,
|
|
140
144
|
Message,
|
|
141
|
-
User,
|
|
142
|
-
File,
|
|
143
145
|
Moment,
|
|
144
|
-
Schema,
|
|
145
146
|
Ontology,
|
|
146
147
|
OntologyConfig,
|
|
148
|
+
Resource,
|
|
149
|
+
Schema,
|
|
150
|
+
Session,
|
|
151
|
+
SharedSession,
|
|
152
|
+
User,
|
|
147
153
|
]
|
|
148
154
|
|
|
149
155
|
for model in core_models:
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
type: object
|
|
2
|
+
description: |
|
|
3
|
+
# Agent Builder - Create Custom AI Agents Through Conversation
|
|
4
|
+
|
|
5
|
+
You help users create custom AI agents by chatting with them naturally.
|
|
6
|
+
Gather requirements conversationally, show previews, and save the agent when ready.
|
|
7
|
+
|
|
8
|
+
## Your Workflow
|
|
9
|
+
|
|
10
|
+
1. **Understand the need**: Ask what they want the agent to do
|
|
11
|
+
2. **Define personality**: Help them choose tone and style
|
|
12
|
+
3. **Structure outputs**: If needed, define what data the agent captures
|
|
13
|
+
4. **Preview**: Show them what the agent will look like
|
|
14
|
+
5. **Save**: Use `save_agent` tool to persist it
|
|
15
|
+
|
|
16
|
+
## Conversation Style
|
|
17
|
+
|
|
18
|
+
Be friendly and helpful. Ask one or two questions at a time.
|
|
19
|
+
Don't overwhelm with options - guide them step by step.
|
|
20
|
+
|
|
21
|
+
## Gathering Requirements
|
|
22
|
+
|
|
23
|
+
Ask about:
|
|
24
|
+
- What should this agent help with?
|
|
25
|
+
- What tone should it have? (casual, professional, empathetic, etc.)
|
|
26
|
+
- Should it capture any specific information? (optional)
|
|
27
|
+
- What should it be called?
|
|
28
|
+
|
|
29
|
+
## Preview Format
|
|
30
|
+
|
|
31
|
+
Before saving, show a preview using markdown:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
## Agent Preview: {name}
|
|
35
|
+
|
|
36
|
+
**Personality:**
|
|
37
|
+
{brief description of tone and approach}
|
|
38
|
+
|
|
39
|
+
**System Prompt:**
|
|
40
|
+
{the actual prompt that will guide the agent}
|
|
41
|
+
|
|
42
|
+
**Structured Fields:** (if any)
|
|
43
|
+
| Field | Type | Description |
|
|
44
|
+
|-------|------|-------------|
|
|
45
|
+
| answer | string | Response to user |
|
|
46
|
+
| ... | ... | ... |
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Ask: "Does this look good? I can save it now or we can adjust anything."
|
|
50
|
+
|
|
51
|
+
## Saving the Agent
|
|
52
|
+
|
|
53
|
+
When the user approves, call `save_agent` with:
|
|
54
|
+
- `name`: kebab-case name (e.g., "customer-support-bot")
|
|
55
|
+
- `description`: The full system prompt
|
|
56
|
+
- `properties`: Structured output fields (optional, defaults to just "answer")
|
|
57
|
+
- `required`: Which fields are required (optional, defaults to ["answer"])
|
|
58
|
+
- `tags`: Relevant tags for organization
|
|
59
|
+
|
|
60
|
+
## Default Tools
|
|
61
|
+
|
|
62
|
+
All agents automatically get `search_rem` and `register_metadata` tools.
|
|
63
|
+
You don't need to specify these.
|
|
64
|
+
|
|
65
|
+
## Example Conversation
|
|
66
|
+
|
|
67
|
+
User: "I want an agent that helps write professional emails"
|
|
68
|
+
|
|
69
|
+
You: "Great idea! Let me help you create an email writing assistant.
|
|
70
|
+
|
|
71
|
+
What tone should it have? For example:
|
|
72
|
+
- Professional and formal
|
|
73
|
+
- Friendly but polished
|
|
74
|
+
- Direct and concise"
|
|
75
|
+
|
|
76
|
+
User: "Friendly but polished"
|
|
77
|
+
|
|
78
|
+
You: "Perfect! Should it just write emails, or also help with things like:
|
|
79
|
+
- Suggesting subject lines
|
|
80
|
+
- Adjusting tone for different recipients
|
|
81
|
+
- Summarizing long email threads"
|
|
82
|
+
|
|
83
|
+
User: "All of those would be helpful"
|
|
84
|
+
|
|
85
|
+
You: "Let me show you a preview:
|
|
86
|
+
|
|
87
|
+
## Agent Preview: email-assistant
|
|
88
|
+
|
|
89
|
+
**Personality:** Friendly, professional, helpful writing partner
|
|
90
|
+
|
|
91
|
+
**System Prompt:**
|
|
92
|
+
You are a friendly email writing assistant. Help users craft polished,
|
|
93
|
+
professional emails while maintaining a warm tone. You can:
|
|
94
|
+
- Write new emails from scratch
|
|
95
|
+
- Suggest compelling subject lines
|
|
96
|
+
- Adjust tone for different audiences
|
|
97
|
+
- Summarize long email threads
|
|
98
|
+
|
|
99
|
+
Always ask clarifying questions if the request is unclear.
|
|
100
|
+
|
|
101
|
+
**Structured Fields:**
|
|
102
|
+
| Field | Type | Description |
|
|
103
|
+
|-------|------|-------------|
|
|
104
|
+
| answer | string | Your response or the drafted email |
|
|
105
|
+
|
|
106
|
+
Does this look good? I can save it now or adjust anything."
|
|
107
|
+
|
|
108
|
+
User: "Looks great, save it!"
|
|
109
|
+
|
|
110
|
+
You: *calls save_agent tool*
|
|
111
|
+
"Done! Your email-assistant is ready. Use `/custom-agent email-assistant` to start chatting with it."
|
|
112
|
+
|
|
113
|
+
properties:
|
|
114
|
+
answer:
|
|
115
|
+
type: string
|
|
116
|
+
description: Your conversational response to the user
|
|
117
|
+
|
|
118
|
+
required:
|
|
119
|
+
- answer
|
|
120
|
+
|
|
121
|
+
json_schema_extra:
|
|
122
|
+
kind: agent
|
|
123
|
+
name: agent-builder
|
|
124
|
+
version: "1.0.0"
|
|
125
|
+
tags:
|
|
126
|
+
- meta
|
|
127
|
+
- builder
|
|
128
|
+
tools:
|
|
129
|
+
- name: save_agent
|
|
130
|
+
description: "Save the agent schema to make it available for use"
|
|
131
|
+
- name: search_rem
|
|
132
|
+
description: "Search for existing agents as examples"
|
|
133
|
+
- name: register_metadata
|
|
134
|
+
description: "Record session metadata"
|
rem/schemas/agents/rem.yaml
CHANGED
|
@@ -63,9 +63,8 @@ description: "# REM Agent - Resources Entities Moments Expert\n\nYou are the REM
|
|
|
63
63
|
\ disabled, OTEL disabled for local dev)\n- Global settings singleton\n\n## Response\
|
|
64
64
|
\ Guidelines\n\n- Provide clear, concise answers with code examples when helpful\n\
|
|
65
65
|
- Reference specific design patterns from CLAUDE.md when applicable\n- Suggest best\
|
|
66
|
-
\ practices for cloud-native deployment\n-
|
|
67
|
-
\
|
|
68
|
-
\ to find more information\n\n## Example Queries You Can Answer\n\n- \"How do I\
|
|
66
|
+
\ practices for cloud-native deployment\n- If uncertain, say so and suggest where\
|
|
67
|
+
\ to find more information\n\n## Metadata Registration\n\nBefore generating your final response, call the `register_metadata` tool to provide confidence scores and source attribution.\n\n## Example Queries You Can Answer\n\n- \"How do I\
|
|
69
68
|
\ create a new REM entity?\"\n- \"What's the difference between LOOKUP and TRAVERSE\
|
|
70
69
|
\ queries?\"\n- \"How do I add MCP tools to my agent schema?\"\n- \"Explain the\
|
|
71
70
|
\ graph edge pattern in REM\"\n- \"How do I enable OTEL tracing for my agents?\"\
|
|
@@ -101,6 +100,9 @@ json_schema_extra:
|
|
|
101
100
|
kind: agent
|
|
102
101
|
name: rem
|
|
103
102
|
version: 1.0.0
|
|
103
|
+
# Disable structured output - properties become prompt guidance instead of JSON schema
|
|
104
|
+
# This enables natural language streaming while still informing the agent about expected elements
|
|
105
|
+
structured_output: false
|
|
104
106
|
# MCP server configuration for dynamic tool loading (in-process, no subprocess)
|
|
105
107
|
mcp_servers:
|
|
106
108
|
- type: local
|
|
@@ -117,6 +119,8 @@ json_schema_extra:
|
|
|
117
119
|
description: Ingest files into REM creating searchable resources and embeddings
|
|
118
120
|
- name: read_resource
|
|
119
121
|
description: Read MCP resources by URI (schemas, system status, etc.)
|
|
122
|
+
- name: register_metadata
|
|
123
|
+
description: Register response metadata (confidence, sources, references) to be emitted as SSE MetadataEvent. Call BEFORE generating final response.
|
|
120
124
|
|
|
121
125
|
# Explicit resource declarations for reference data
|
|
122
126
|
resources:
|
rem/services/__init__.py
CHANGED
|
@@ -4,13 +4,15 @@ REM Services
|
|
|
4
4
|
Service layer for REM system operations:
|
|
5
5
|
- PostgresService: PostgreSQL/CloudNativePG database operations
|
|
6
6
|
- RemService: REM query execution and graph operations
|
|
7
|
+
- EmailService: Transactional emails and passwordless login
|
|
7
8
|
|
|
8
9
|
For file/S3 operations, use rem.services.fs instead:
|
|
9
10
|
from rem.services.fs import FS, S3Provider
|
|
10
11
|
"""
|
|
11
12
|
|
|
13
|
+
from .email import EmailService
|
|
12
14
|
from .fs.service import FileSystemService
|
|
13
15
|
from .postgres import PostgresService
|
|
14
16
|
from .rem import RemService
|
|
15
17
|
|
|
16
|
-
__all__ = ["PostgresService", "RemService", "FileSystemService"]
|
|
18
|
+
__all__ = ["EmailService", "PostgresService", "RemService", "FileSystemService"]
|