remdb 0.3.127__py3-none-any.whl → 0.3.172__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of remdb might be problematic. Click here for more details.

Files changed (62) hide show
  1. rem/agentic/agents/__init__.py +16 -0
  2. rem/agentic/agents/agent_manager.py +311 -0
  3. rem/agentic/context.py +81 -3
  4. rem/agentic/context_builder.py +36 -9
  5. rem/agentic/mcp/tool_wrapper.py +132 -15
  6. rem/agentic/providers/phoenix.py +371 -108
  7. rem/agentic/providers/pydantic_ai.py +163 -45
  8. rem/agentic/schema.py +8 -4
  9. rem/api/deps.py +3 -5
  10. rem/api/main.py +22 -3
  11. rem/api/mcp_router/resources.py +15 -10
  12. rem/api/mcp_router/server.py +2 -0
  13. rem/api/mcp_router/tools.py +94 -2
  14. rem/api/middleware/tracking.py +5 -5
  15. rem/api/routers/auth.py +349 -6
  16. rem/api/routers/chat/completions.py +5 -3
  17. rem/api/routers/chat/streaming.py +95 -22
  18. rem/api/routers/messages.py +24 -15
  19. rem/auth/__init__.py +13 -3
  20. rem/auth/jwt.py +352 -0
  21. rem/auth/middleware.py +115 -10
  22. rem/auth/providers/__init__.py +4 -1
  23. rem/auth/providers/email.py +215 -0
  24. rem/cli/commands/configure.py +3 -4
  25. rem/cli/commands/experiments.py +226 -50
  26. rem/cli/commands/session.py +336 -0
  27. rem/cli/dreaming.py +2 -2
  28. rem/cli/main.py +2 -0
  29. rem/models/core/experiment.py +58 -14
  30. rem/models/entities/__init__.py +4 -0
  31. rem/models/entities/ontology.py +1 -1
  32. rem/models/entities/ontology_config.py +1 -1
  33. rem/models/entities/subscriber.py +175 -0
  34. rem/models/entities/user.py +1 -0
  35. rem/schemas/agents/core/agent-builder.yaml +235 -0
  36. rem/schemas/agents/examples/contract-analyzer.yaml +1 -1
  37. rem/schemas/agents/examples/contract-extractor.yaml +1 -1
  38. rem/schemas/agents/examples/cv-parser.yaml +1 -1
  39. rem/services/__init__.py +3 -1
  40. rem/services/content/service.py +4 -3
  41. rem/services/email/__init__.py +10 -0
  42. rem/services/email/service.py +513 -0
  43. rem/services/email/templates.py +360 -0
  44. rem/services/postgres/README.md +38 -0
  45. rem/services/postgres/diff_service.py +19 -3
  46. rem/services/postgres/pydantic_to_sqlalchemy.py +45 -13
  47. rem/services/postgres/repository.py +5 -4
  48. rem/services/session/compression.py +113 -50
  49. rem/services/session/reload.py +14 -7
  50. rem/services/user_service.py +41 -9
  51. rem/settings.py +292 -5
  52. rem/sql/migrations/001_install.sql +1 -1
  53. rem/sql/migrations/002_install_models.sql +91 -91
  54. rem/sql/migrations/005_schema_update.sql +145 -0
  55. rem/utils/README.md +45 -0
  56. rem/utils/files.py +157 -1
  57. rem/utils/schema_loader.py +45 -7
  58. rem/utils/vision.py +1 -1
  59. {remdb-0.3.127.dist-info → remdb-0.3.172.dist-info}/METADATA +7 -5
  60. {remdb-0.3.127.dist-info → remdb-0.3.172.dist-info}/RECORD +62 -52
  61. {remdb-0.3.127.dist-info → remdb-0.3.172.dist-info}/WHEEL +0 -0
  62. {remdb-0.3.127.dist-info → remdb-0.3.172.dist-info}/entry_points.txt +0 -0
rem/cli/dreaming.py CHANGED
@@ -43,7 +43,7 @@ rem-dreaming full --user-id=user-123 --rem-api-url=http://localhost:8000
43
43
  Environment Variables:
44
44
  - REM_API_URL: REM API endpoint (default: http://rem-api:8000)
45
45
  - REM_EMBEDDING_PROVIDER: Embedding provider (default: text-embedding-3-small)
46
- - REM_DEFAULT_MODEL: LLM model (default: gpt-4o)
46
+ - REM_DEFAULT_MODEL: LLM model (default: gpt-4.1)
47
47
  - REM_LOOKBACK_HOURS: Default lookback window (default: 24)
48
48
  - OPENAI_API_KEY: OpenAI API key
49
49
 
@@ -83,7 +83,7 @@ def get_worker() -> DreamingWorker:
83
83
  embedding_provider=os.getenv(
84
84
  "REM_EMBEDDING_PROVIDER", "text-embedding-3-small"
85
85
  ),
86
- default_model=os.getenv("REM_DEFAULT_MODEL", "gpt-4o"),
86
+ default_model=os.getenv("REM_DEFAULT_MODEL", "gpt-4.1"),
87
87
  lookback_hours=int(os.getenv("REM_LOOKBACK_HOURS", "24")),
88
88
  )
89
89
 
rem/cli/main.py CHANGED
@@ -96,6 +96,7 @@ from .commands.serve import register_command as register_serve_command
96
96
  from .commands.mcp import register_command as register_mcp_command
97
97
  from .commands.scaffold import scaffold as scaffold_command
98
98
  from .commands.cluster import register_commands as register_cluster_commands
99
+ from .commands.session import register_command as register_session_command
99
100
 
100
101
  register_schema_commands(schema)
101
102
  register_db_commands(db)
@@ -108,6 +109,7 @@ register_serve_command(cli)
108
109
  register_mcp_command(cli)
109
110
  cli.add_command(experiments_group)
110
111
  cli.add_command(scaffold_command)
112
+ register_session_command(cli)
111
113
 
112
114
 
113
115
  def main():
@@ -138,18 +138,14 @@ class DatasetReference(BaseModel):
138
138
 
139
139
  path: str = Field(
140
140
  description=(
141
- "Path to dataset:\n"
141
+ "Path to dataset. Format is inferred from file extension.\n"
142
+ "Supported: .csv, .tsv, .parquet, .json, .jsonl, .xlsx, .ods, .avro, .ipc\n"
142
143
  "- Git: Relative path from experiment root (e.g., 'datasets/ground_truth.csv')\n"
143
- "- S3: Full S3 URI (e.g., 's3://bucket/experiments/my-exp/datasets/ground_truth.csv')\n"
144
+ "- S3: Full S3 URI (e.g., 's3://bucket/experiments/my-exp/datasets/data.parquet')\n"
144
145
  "- Hybrid: S3 URI for data, Git path for schema"
145
146
  )
146
147
  )
147
148
 
148
- format: Literal["csv", "jsonl", "parquet", "json"] = Field(
149
- default="csv",
150
- description="Dataset file format"
151
- )
152
-
153
149
  schema_path: str | None = Field(
154
150
  default=None,
155
151
  description=(
@@ -262,8 +258,7 @@ class ExperimentConfig(BaseModel):
262
258
  datasets:
263
259
  ground_truth:
264
260
  location: git
265
- path: datasets/ground_truth.csv
266
- format: csv
261
+ path: datasets/ground_truth.csv # format inferred from extension
267
262
  results:
268
263
  location: git
269
264
  base_path: results/
@@ -288,12 +283,10 @@ class ExperimentConfig(BaseModel):
288
283
  ground_truth:
289
284
  location: s3
290
285
  path: s3://rem-prod/experiments/cv-parser-production/datasets/ground_truth.parquet
291
- format: parquet
292
286
  schema_path: datasets/schema.yaml # Schema in Git for documentation
293
287
  test_cases:
294
288
  location: s3
295
289
  path: s3://rem-prod/experiments/cv-parser-production/datasets/test_cases.jsonl
296
- format: jsonl
297
290
  results:
298
291
  location: hybrid
299
292
  base_path: s3://rem-prod/experiments/cv-parser-production/results/
@@ -318,6 +311,15 @@ class ExperimentConfig(BaseModel):
318
311
  )
319
312
  )
320
313
 
314
+ task: str = Field(
315
+ default="general",
316
+ description=(
317
+ "Task name for organizing experiments by purpose.\n"
318
+ "Used with agent name to form directory: {agent}/{task}/\n"
319
+ "Examples: 'risk-assessment', 'classification', 'general'"
320
+ )
321
+ )
322
+
321
323
  description: str = Field(
322
324
  description="Human-readable description of experiment purpose and goals"
323
325
  )
@@ -410,6 +412,24 @@ class ExperimentConfig(BaseModel):
410
412
 
411
413
  return v
412
414
 
415
+ @field_validator("task")
416
+ @classmethod
417
+ def validate_task(cls, v: str) -> str:
418
+ """Validate task name follows conventions."""
419
+ if not v:
420
+ return "general" # Default value
421
+
422
+ if not v.islower():
423
+ raise ValueError("Task name must be lowercase")
424
+
425
+ if " " in v:
426
+ raise ValueError("Task name cannot contain spaces (use hyphens)")
427
+
428
+ if not all(c.isalnum() or c == "-" for c in v):
429
+ raise ValueError("Task name can only contain lowercase letters, numbers, and hyphens")
430
+
431
+ return v
432
+
413
433
  @field_validator("tags")
414
434
  @classmethod
415
435
  def validate_tags(cls, v: list[str]) -> list[str]:
@@ -420,6 +440,15 @@ class ExperimentConfig(BaseModel):
420
440
  """Get the experiment directory path."""
421
441
  return Path(base_path) / self.name
422
442
 
443
+ def get_agent_task_dir(self, base_path: str = ".experiments") -> Path:
444
+ """
445
+ Get the experiment directory path organized by agent/task.
446
+
447
+ Returns: Path like .experiments/{agent}/{task}/
448
+ This is the recommended structure for S3 export compatibility.
449
+ """
450
+ return Path(base_path) / self.agent_schema_ref.name / self.task
451
+
423
452
  def get_config_path(self, base_path: str = ".experiments") -> Path:
424
453
  """Get the path to experiment.yaml file."""
425
454
  return self.get_experiment_dir(base_path) / "experiment.yaml"
@@ -428,6 +457,22 @@ class ExperimentConfig(BaseModel):
428
457
  """Get the path to README.md file."""
429
458
  return self.get_experiment_dir(base_path) / "README.md"
430
459
 
460
+ def get_evaluator_filename(self) -> str:
461
+ """
462
+ Get the evaluator filename with task prefix.
463
+
464
+ Returns: {agent_name}-{task}.yaml (e.g., siggy-risk-assessment.yaml)
465
+ """
466
+ return f"{self.agent_schema_ref.name}-{self.task}.yaml"
467
+
468
+ def get_s3_export_path(self, bucket: str, version: str = "v0") -> str:
469
+ """
470
+ Get the S3 path for exporting this experiment.
471
+
472
+ Returns: s3://{bucket}/{version}/datasets/calibration/experiments/{agent}/{task}/
473
+ """
474
+ return f"s3://{bucket}/{version}/datasets/calibration/experiments/{self.agent_schema_ref.name}/{self.task}"
475
+
431
476
  def to_yaml(self) -> str:
432
477
  """Export configuration as YAML string."""
433
478
  import yaml
@@ -483,6 +528,7 @@ class ExperimentConfig(BaseModel):
483
528
  ## Configuration
484
529
 
485
530
  **Status**: `{self.status.value}`
531
+ **Task**: `{self.task}`
486
532
  **Tags**: {', '.join(f'`{tag}`' for tag in self.tags) if self.tags else 'None'}
487
533
 
488
534
  ## Agent Schema
@@ -494,6 +540,7 @@ class ExperimentConfig(BaseModel):
494
540
  ## Evaluator Schema
495
541
 
496
542
  - **Name**: `{self.evaluator_schema_ref.name}`
543
+ - **File**: `{self.get_evaluator_filename()}`
497
544
  - **Type**: `{self.evaluator_schema_ref.type}`
498
545
 
499
546
  ## Datasets
@@ -504,7 +551,6 @@ class ExperimentConfig(BaseModel):
504
551
 
505
552
  - **Location**: `{dataset.location.value}`
506
553
  - **Path**: `{dataset.path}`
507
- - **Format**: `{dataset.format}`
508
554
  """
509
555
  if dataset.description:
510
556
  readme += f"- **Description**: {dataset.description}\n"
@@ -575,7 +621,6 @@ EXAMPLE_SMALL_EXPERIMENT = ExperimentConfig(
575
621
  "ground_truth": DatasetReference(
576
622
  location=DatasetLocation.GIT,
577
623
  path="datasets/ground_truth.csv",
578
- format="csv",
579
624
  description="10 manually curated test cases"
580
625
  )
581
626
  },
@@ -605,7 +650,6 @@ EXAMPLE_LARGE_EXPERIMENT = ExperimentConfig(
605
650
  "ground_truth": DatasetReference(
606
651
  location=DatasetLocation.S3,
607
652
  path="s3://rem-prod/experiments/cv-parser-production/datasets/ground_truth.parquet",
608
- format="parquet",
609
653
  schema_path="datasets/schema.yaml",
610
654
  description="10,000 CV/resume pairs with ground truth extractions"
611
655
  )
@@ -39,6 +39,7 @@ from .shared_session import (
39
39
  SharedWithMeResponse,
40
40
  SharedWithMeSummary,
41
41
  )
42
+ from .subscriber import Subscriber, SubscriberOrigin, SubscriberStatus
42
43
  from .user import User, UserTier
43
44
 
44
45
  __all__ = [
@@ -56,6 +57,9 @@ __all__ = [
56
57
  "FeedbackCategory",
57
58
  "User",
58
59
  "UserTier",
60
+ "Subscriber",
61
+ "SubscriberStatus",
62
+ "SubscriberOrigin",
59
63
  "File",
60
64
  "Moment",
61
65
  "Schema",
@@ -129,7 +129,7 @@ class Ontology(CoreModel):
129
129
  file_id="file-uuid-456",
130
130
  agent_schema_id="contract-parser-v2",
131
131
  provider_name="openai",
132
- model_name="gpt-4o",
132
+ model_name="gpt-4.1",
133
133
  extracted_data={
134
134
  "contract_type": "supplier_agreement",
135
135
  "parties": [
@@ -74,7 +74,7 @@ class OntologyConfig(CoreModel):
74
74
  priority=200, # Higher priority = runs first
75
75
  enabled=True,
76
76
  provider_name="openai", # Override default provider
77
- model_name="gpt-4o",
77
+ model_name="gpt-4.1",
78
78
  tenant_id="acme-corp",
79
79
  tags=["legal", "procurement"]
80
80
  )
@@ -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
@@ -22,6 +22,7 @@ from ..core import CoreModel
22
22
  class UserTier(str, Enum):
23
23
  """User subscription tier for feature gating."""
24
24
 
25
+ BLOCKED = "blocked" # User is blocked from logging in
25
26
  ANONYMOUS = "anonymous"
26
27
  FREE = "free"
27
28
  BASIC = "basic"
@@ -0,0 +1,235 @@
1
+ type: object
2
+ description: |
3
+ # Agent Builder - Create Custom AI Agents Through Conversation
4
+
5
+ You help users create custom AI agents for the REM platform through natural conversation.
6
+ Guide them step-by-step, gather requirements, show previews, and save 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 communication style
12
+ 3. **Set guardrails**: What should the agent NOT do?
13
+ 4. **Structure outputs**: Define what data the agent captures (optional)
14
+ 5. **Preview**: Show them what the agent will look like
15
+ 6. **Save**: Use `save_agent` tool to persist it
16
+
17
+ ## Conversation Style
18
+
19
+ Be friendly and helpful. Ask one or two questions at a time.
20
+ Don't overwhelm with options - guide them step by step.
21
+
22
+ ## IMPORTANT: Tool Usage
23
+
24
+ - `save_agent` - Use ONLY in Step 6 when user approves the preview
25
+ - `get_agents_list` - Use if user asks to see existing agents as examples
26
+ - `get_agent_schema` - Use to load a specific agent (like "rem") as reference
27
+
28
+ DO NOT loop on tools. If a user asks for examples, call get_agents_list ONCE,
29
+ then discuss what you found. This is a conversational workflow.
30
+
31
+ ## Step 1: Identity & Purpose
32
+
33
+ Ask about:
34
+ - What should this agent help with? (primary purpose)
35
+ - What would you like to call it? (suggest kebab-case like "sales-assistant")
36
+ - What role/persona should it embody?
37
+
38
+ ## Step 2: Tone & Communication Style
39
+
40
+ Help define tone using this framework:
41
+
42
+ | Dimension | Options |
43
+ |-----------|---------|
44
+ | Formality | casual, conversational, professional, formal |
45
+ | Warmth | empathetic, friendly, neutral, businesslike |
46
+ | Pace | patient, balanced, efficient, direct |
47
+ | Expertise | peer, guide, expert, authority |
48
+
49
+ Ask: "What tone feels right? For example, should it be friendly and casual, or more professional?"
50
+
51
+ ## Step 3: Guardrails
52
+
53
+ Ask what the agent should NOT do:
54
+ - Topics to avoid?
55
+ - Actions it shouldn't take?
56
+ - Boundaries to respect?
57
+
58
+ Example guardrails:
59
+ - "Never provide medical/legal/financial advice"
60
+ - "Don't make promises about timelines"
61
+ - "Always recommend consulting a professional for serious issues"
62
+
63
+ ## Step 4: Structured Outputs (Optional)
64
+
65
+ Most agents just need an `answer` field. But some use cases benefit from structured data:
66
+
67
+ | Field | Type | Description |
68
+ |-------|------|-------------|
69
+ | answer | string | Natural language response (always required) |
70
+ | confidence | number | 0.0-1.0 confidence score |
71
+ | category | string | Classification of the request |
72
+ | follow_up_needed | boolean | Whether follow-up is required |
73
+
74
+ Field types available:
75
+ - `string` - text values
76
+ - `number` - numeric values (can add minimum/maximum)
77
+ - `boolean` - true/false
78
+ - `array` - list of items
79
+ - `string` with `enum` - fixed set of choices
80
+
81
+ Only suggest structured outputs if the use case clearly benefits from them.
82
+
83
+ ## Step 5: Preview
84
+
85
+ Before saving, show a preview:
86
+
87
+ ```
88
+ ## Agent Preview: {name}
89
+
90
+ **Purpose:** {brief description}
91
+
92
+ **Personality:** {tone and approach}
93
+
94
+ **System Prompt:**
95
+ {the actual prompt that will guide the agent}
96
+
97
+ **Guardrails:**
98
+ - {guardrail 1}
99
+ - {guardrail 2}
100
+
101
+ **Structured Fields:** (if any beyond answer)
102
+ | Field | Type | Description |
103
+ |-------|------|-------------|
104
+ | answer | string | Response to user |
105
+ ```
106
+
107
+ Ask: "Does this look good? I can save it now or adjust anything."
108
+
109
+ ## Step 6: Save the Agent
110
+
111
+ When the user approves, call `save_agent` with:
112
+ - `name`: kebab-case name (e.g., "customer-support-bot")
113
+ - `description`: The full system prompt (this is the most important part!)
114
+ - `properties`: Structured output fields (optional, defaults to just "answer")
115
+ - `required`: Which fields are required (optional, defaults to ["answer"])
116
+ - `tags`: Relevant tags for organization
117
+
118
+ ## Writing Great System Prompts
119
+
120
+ The `description` field IS the system prompt. Make it:
121
+
122
+ 1. **Clear about role**: "You are a [role] that helps users [do what]"
123
+ 2. **Specific about behavior**: How should it respond? What's the communication style?
124
+ 3. **Explicit about guardrails**: What should it NOT do?
125
+ 4. **Structured with sections**: Use markdown headers for organization
126
+
127
+ Example structure:
128
+ ```
129
+ # [Agent Name]
130
+
131
+ You are a [role description]. Your purpose is to [main goal].
132
+
133
+ ## Communication Style
134
+ [Tone, approach, language preferences]
135
+
136
+ ## Core Responsibilities
137
+ - [Responsibility 1]
138
+ - [Responsibility 2]
139
+
140
+ ## Guardrails
141
+ - Never [thing to avoid]
142
+ - Always [thing to ensure]
143
+
144
+ ## How to Respond
145
+ [Guidelines for responses]
146
+ ```
147
+
148
+ ## Example Conversation
149
+
150
+ User: "I want an agent that helps write professional emails"
151
+
152
+ You: "Great idea! Let me help you create an email writing assistant.
153
+
154
+ First, what tone should it have? For example:
155
+ - Professional and formal
156
+ - Friendly but polished
157
+ - Direct and concise"
158
+
159
+ User: "Friendly but polished"
160
+
161
+ You: "Perfect! Should it focus just on writing emails, or also help with:
162
+ - Suggesting subject lines
163
+ - Adjusting tone for different recipients
164
+ - Summarizing long email threads"
165
+
166
+ User: "All of those would be helpful"
167
+
168
+ You: "Great! Any things it should NOT do? For example, some email assistants avoid:
169
+ - Writing emails that could be seen as legally binding
170
+ - Making commitments on behalf of the user"
171
+
172
+ User: "Yeah, it shouldn't make commitments or promises"
173
+
174
+ You: "Perfect! Here's a preview:
175
+
176
+ ## Agent Preview: email-assistant
177
+
178
+ **Purpose:** Help users write polished, professional emails
179
+
180
+ **Personality:** Friendly, helpful, professional writing partner
181
+
182
+ **System Prompt:**
183
+ # Email Writing Assistant
184
+
185
+ You are a friendly email writing assistant. Help users craft polished,
186
+ professional emails while maintaining a warm, approachable tone.
187
+
188
+ ## What You Can Do
189
+ - Write new emails from scratch based on user's intent
190
+ - Suggest compelling subject lines
191
+ - Adjust tone for different audiences (colleagues, executives, clients)
192
+ - Summarize long email threads
193
+ - Proofread and improve existing drafts
194
+
195
+ ## Communication Style
196
+ Be helpful and collaborative. Suggest improvements but respect the user's voice.
197
+ Ask clarifying questions when the request is ambiguous.
198
+
199
+ ## Guardrails
200
+ - Never write emails that make commitments or promises on behalf of the user
201
+ - Don't write anything that could be legally binding
202
+ - Always let the user review before sending
203
+
204
+ Does this look good? I can save it now or adjust anything."
205
+
206
+ User: "Looks great, save it!"
207
+
208
+ You: *calls save_agent tool*
209
+ "Done! Your email-assistant is ready to use."
210
+
211
+ properties:
212
+ answer:
213
+ type: string
214
+ description: Your conversational response to the user
215
+
216
+ required:
217
+ - answer
218
+
219
+ json_schema_extra:
220
+ kind: agent
221
+ name: agent-builder
222
+ version: "1.2.0"
223
+ tags:
224
+ - meta
225
+ - builder
226
+ structured_output: false # Stream text responses, don't return JSON
227
+ mcp_servers: [] # Disable default MCP tools to prevent search_rem looping
228
+ resources:
229
+ - uri: rem://agents
230
+ description: "List all available agent schemas with descriptions"
231
+ - uri: rem://agents/{agent_name}
232
+ description: "Load a specific agent schema by name (e.g., 'rem', 'siggy')"
233
+ tools:
234
+ - name: save_agent
235
+ description: "Save the agent schema. Only call when user approves the preview in Step 6."
@@ -308,7 +308,7 @@ json_schema_extra:
308
308
  - provider_name: anthropic
309
309
  model_name: claude-sonnet-4-5-20250929
310
310
  - provider_name: openai
311
- model_name: gpt-4o
311
+ model_name: gpt-4.1
312
312
  embedding_fields:
313
313
  - contract_title
314
314
  - contract_type
@@ -131,4 +131,4 @@ json_schema_extra:
131
131
  - provider_name: anthropic
132
132
  model_name: claude-sonnet-4-5-20250929
133
133
  - provider_name: openai
134
- model_name: gpt-4o
134
+ model_name: gpt-4.1
@@ -255,7 +255,7 @@ json_schema_extra:
255
255
  - provider_name: anthropic
256
256
  model_name: claude-sonnet-4-5-20250929
257
257
  - provider_name: openai
258
- model_name: gpt-4o
258
+ model_name: gpt-4.1
259
259
  embedding_fields:
260
260
  - candidate_name
261
261
  - professional_summary