|
|
5 months ago | |
|---|---|---|
| .gitignore | 5 months ago | |
| README.md | 5 months ago | |
| app.py | 5 months ago | |
| crypto_utils.py | 5 months ago | |
| letta_executor.py | 5 months ago | |
| models.py | 5 months ago | |
| requirements.txt | 5 months ago | |
| scheduler.py | 5 months ago | |
| setup_encryption.sh | 5 months ago | |
| test-lines.bash | 5 months ago | |
| test_api.py | 5 months ago | |
| test_api.sh | 5 months ago |
A serverless scheduling service for Letta agents built on Modal. Schedule recurring (cron-based) or one-time messages to be sent to your Letta agents at specified times.
Clone the repository:
cd letta-schedules
Install Modal CLI:
pip install modal
Authenticate with Modal:
modal setup
Option A: Automated Setup Script
./setup_encryption.sh
This will:
Option B: Manual Setup
# Generate a new encryption key
ENCRYPTION_KEY=$(python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())")
# Display and save the key
echo "Your encryption key: $ENCRYPTION_KEY"
# Create Modal secret
modal secret create letta-schedules-encryption \
LETTA_SCHEDULES_ENCRYPTION_KEY="$ENCRYPTION_KEY"
⚠️ CRITICAL: Save the encryption key securely! If lost, all encrypted schedules become unrecoverable.
modal deploy app.py
This will:
modal app list
Look for letta-schedules and note the API endpoint URL.
Run locally with hot reloading:
# Enable dev mode (no encryption, easier debugging)
export LETTA_SCHEDULES_DEV_MODE=true
export LETTA_SCHEDULES_ENCRYPTION_KEY="any-value-ignored-in-dev-mode"
modal serve app.py
This starts a local development server with auto-reload on file changes.
Dev Mode Features:
cat, jq, etc.DEV MODE: Encryption disabledImportant: Never use dev mode in production! Set LETTA_SCHEDULES_DEV_MODE=false or leave unset for production.
Inspecting files in dev mode:
# View a schedule
cat /tmp/letta-schedules-volume/schedules/recurring/abc123/uuid.json | jq
# View an execution result
cat /tmp/letta-schedules-volume/results/abc123/uuid.json | jq
# List all schedules for a user
ls -la /tmp/letta-schedules-volume/schedules/recurring/abc123/
Two test scripts are provided. Both require environment variables:
export LETTA_API_KEY="sk-..." # Required: Your valid Letta API key
export LETTA_AGENT_ID="agent-xxx" # Optional: Agent to test with
export LETTA_SCHEDULES_URL="https://..." # Optional: Your Modal app URL
Important: The API key must be valid and will be validated against Letta's API during testing.
python test_api.py
This will test all endpoints (create, list, get, delete) for both recurring and one-time schedules.
Features:
./test_api.sh
Same functionality using curl commands.
Example with inline variables:
LETTA_API_KEY=sk-xxx LETTA_AGENT_ID=agent-yyy python test_api.py
Base URL: https://your-modal-app.modal.run
All endpoints require Bearer token authentication using your Letta API key:
curl -H "Authorization: Bearer your-letta-api-key" https://your-modal-app.modal.run/schedules/recurring
Security Model:
list agents call with limit=1)Error Codes:
401 Unauthorized: Invalid or expired Letta API key403 Forbidden: Valid API key, but trying to access someone else's schedule404 Not Found: Schedule doesn't existSchedule a message to be sent on a cron schedule:
curl -X POST https://your-modal-app.modal.run/schedules/recurring \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agent-123",
"api_key": "your-letta-api-key",
"cron": "0 9 * * *",
"message": "Good morning! Time for your daily check-in.",
"role": "user"
}'
Cron Format: minute hour day month day_of_week
0 9 * * * - Every day at 9:00 AM*/15 * * * * - Every 15 minutes0 */2 * * * - Every 2 hours0 0 * * 0 - Every Sunday at midnightSchedule a message for a specific time:
curl -X POST https://your-modal-app.modal.run/schedules/one-time \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agent-123",
"api_key": "your-letta-api-key",
"execute_at": "2025-11-07T14:30:00-05:00",
"message": "Reminder: Meeting in 30 minutes",
"role": "user"
}'
Timestamp Format: ISO 8601 with timezone
2025-11-07T14:30:00-05:00 (EST)2025-11-07T14:30:00Z (UTC)# List your recurring schedules
curl -H "Authorization: Bearer your-letta-api-key" \
https://your-modal-app.modal.run/schedules/recurring
# List your one-time schedules
curl -H "Authorization: Bearer your-letta-api-key" \
https://your-modal-app.modal.run/schedules/one-time
# Get recurring schedule
curl -H "Authorization: Bearer your-letta-api-key" \
https://your-modal-app.modal.run/schedules/recurring/{schedule_id}
# Get one-time schedule
curl -H "Authorization: Bearer your-letta-api-key" \
https://your-modal-app.modal.run/schedules/one-time/{schedule_id}
# Delete recurring schedule
curl -X DELETE -H "Authorization: Bearer your-letta-api-key" \
https://your-modal-app.modal.run/schedules/recurring/{schedule_id}
# Delete one-time schedule
curl -X DELETE -H "Authorization: Bearer your-letta-api-key" \
https://your-modal-app.modal.run/schedules/one-time/{schedule_id}
# List all execution results
curl -H "Authorization: Bearer your-letta-api-key" \
https://your-modal-app.modal.run/results
# Get result for specific schedule
curl -H "Authorization: Bearer your-letta-api-key" \
https://your-modal-app.modal.run/results/{schedule_id}
Result Format:
{
"schedule_id": "uuid",
"schedule_type": "recurring",
"run_id": "run_abc123",
"agent_id": "agent-123",
"message": "The scheduled message",
"executed_at": "2025-11-07T00:15:00"
}
Note: Results are stored when the message is queued to Letta. To check the actual run status, use the Letta API:
# Get the run_id from results
RESULT=$(curl -H "Authorization: Bearer your-letta-api-key" \
https://your-modal-app.modal.run/results/{schedule_id})
RUN_ID=$(echo $RESULT | jq -r '.run_id')
# Check run status with Letta
curl -H "Authorization: Bearer your-letta-api-key" \
https://api.letta.com/v1/runs/$RUN_ID
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"agent_id": "agent-123",
"cron": "0 9 * * *",
"message": "Good morning!",
"role": "user",
"created_at": "2025-11-06T10:00:00",
"last_run": "2025-11-06T09:00:00"
}
Note: API keys are stored securely and never returned in responses.
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"agent_id": "agent-123",
"execute_at": "2025-11-07T14:30:00-05:00",
"message": "Reminder!",
"role": "user",
"created_at": "2025-11-06T10:00:00"
}
Note:
/results endpoint for execution history)last_run timestamp in schedule fileRace Condition Prevention:
Schedules and execution results are stored in a hash-based directory structure:
/data/
├── schedules/
│ ├── recurring/
│ │ ├── {api_key_hash}/ # SHA256 hash of API key (first 16 chars)
│ │ │ ├── {uuid-1}.json.enc # Encrypted schedule files
│ │ │ └── {uuid-2}.json.enc
│ │ └── {another_hash}/
│ │ └── {uuid-3}.json.enc
│ └── one-time/
│ ├── 2025-11-06/ # Date bucket
│ │ ├── 14/ # Hour bucket (00-23)
│ │ │ ├── {api_key_hash}/
│ │ │ │ └── {uuid}.json.enc
│ │ │ └── {another_hash}/
│ │ │ └── {uuid}.json.enc
│ │ └── 15/
│ └── 2025-11-07/
└── results/
├── {api_key_hash}/
│ ├── {schedule_uuid}.json.enc # Execution results with run_id
│ └── {schedule_uuid}.json.enc
└── {another_hash}/
Security Features:
Performance Benefits:
View logs in Modal dashboard:
modal app logs letta-schedules
Or watch logs in real-time:
modal app logs letta-schedules --follow
Modal pricing (as of 2024):
Free tier: 30 credits/month (~$30 value)
MIT