|
|
hai 6 meses | |
|---|---|---|
| cli | hai 8 meses | |
| .gitignore | hai 6 meses | |
| README.md | hai 6 meses | |
| app.py | hai 6 meses | |
| dashboard.html | hai 6 meses | |
| pytest.ini | hai 8 meses | |
| requirements-test.txt | hai 8 meses | |
| requirements.txt | hai 8 meses | |
| setup_encryption.sh | hai 8 meses | |
| test-lines.bash | hai 8 meses | |
| test_api.sh | hai 8 meses |
Letta now provides built-in scheduling! This project has been simplified to a web dashboard that calls Letta's API directly.
🎛️ Web Dashboard: https://letta--switchboard-api.modal.run/dashboard
📖 Letta Scheduling Docs: https://docs.letta.com/guides/agents/scheduling/
💻 GitHub: https://github.com/cpfiffer/letta-switchboard
The dashboard now calls Letta's API directly! Just continue using it - no changes needed.
If you were calling the old Switchboard API, you need to migrate to Letta's native API:
Old Endpoint (Deprecated):
POST https://letta--switchboard-api.modal.run/schedules/one-time
{
"agent_id": "agent-xxx",
"execute_at": "2025-11-13T09:00:00Z",
"message": "Hello",
"role": "user"
}
New Endpoint (Use This):
POST https://api.letta.com/v1/agents/agent-xxx/schedule
{
"schedule": {
"type": "one-time",
"scheduled_at": 1731499200000
},
"messages": [{
"role": "user",
"content": "Hello"
}]
}
Key Changes:
execute_at (ISO string) → scheduled_at (Unix milliseconds)message (string) → messages (array of message objects)Full Documentation: https://docs.letta.com/guides/agents/scheduling/
Visit https://letta--switchboard-api.modal.run/dashboard
Features:
For programmatic access, use Letta's scheduling API:
Create a one-time schedule:
curl -X POST https://api.letta.com/v1/agents/agent-xxx/schedule \
-H "Authorization: Bearer YOUR_LETTA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"schedule": {
"type": "one-time",
"scheduled_at": 1731499200000
},
"messages": [{
"role": "user",
"content": "Hello!"
}]
}'
Create a recurring schedule:
curl -X POST https://api.letta.com/v1/agents/agent-xxx/schedule \
-H "Authorization: Bearer YOUR_LETTA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"schedule": {
"type": "recurring",
"cron_expression": "0 9 * * 1-5"
},
"messages": [{
"role": "user",
"content": "Daily standup"
}]
}'
List schedules:
curl https://api.letta.com/v1/agents/agent-xxx/schedule \
-H "Authorization: Bearer YOUR_LETTA_API_KEY"
Delete a schedule:
curl -X DELETE https://api.letta.com/v1/agents/agent-xxx/schedule/SCHEDULE_ID \
-H "Authorization: Bearer YOUR_LETTA_API_KEY"
Full Documentation: https://docs.letta.com/guides/agents/scheduling/
A simple web dashboard for managing Letta agent schedules. The dashboard calls Letta's native scheduling API directly from your browser.
✅ Web Dashboard - Clean interface for managing schedules
✅ Direct Letta Integration - Calls Letta API from browser
✅ No Backend - Pure client-side (except for hosting the HTML)
✅ Timezone Support - Schedule in local time or UTC
✅ Free to Use - Just visit the URL
Easy to Use
Simple interface for creating and managing Letta schedules without writing API calls.
No Installation
Just visit the URL - no setup, no configuration, no CLI to install.
Direct Letta API
Your schedules are stored and executed by Letta Cloud. Check execution history at https://app.letta.com
Architecture:
# Relative time
--execute-at "in 5 minutes"
--execute-at "in 2 hours"
--execute-at "in 3 days"
# Tomorrow
--execute-at "tomorrow at 9am"
--execute-at "tomorrow at 14:30"
# Next weekday
--execute-at "next monday at 3pm"
--execute-at "next friday at 10:00"
# ISO 8601 (still works)
--execute-at "2025-11-12T19:30:00Z"
# Minutes
--cron "every 5 minutes"
--cron "every 30 minutes"
# Hourly/Daily
--cron "every hour"
--cron "daily at 9am"
--cron "daily at 14:30"
# Weekdays
--cron "every monday"
--cron "every friday at 3pm"
--cron "every weekday" # Mon-Fri at 9am
# Traditional cron (still works)
--cron "*/5 * * * *" # Every 5 minutes
This will test all endpoints (create, list, get, delete) for both recurring and one-time schedules.
**Features:**
- Validates API key before running
- Shows configuration at startup
- Tests create, list, get, and delete operations
- Pretty prints all responses
### Bash Test Script
bash ./test_api.sh
Same functionality using curl commands.
**Example with inline variables:**
bash LETTA_API_KEY=sk-xxx LETTA_AGENT_ID=agent-yyy python test_api.py
## CLI Usage (Recommended)
The easiest way to interact with letta-switchboard is via the CLI:
bash
letta-switchboard send --agent-id agent-xxx --message "Hello!"
letta-switchboard send --agent-id agent-xxx --message "Reminder" --execute-at "tomorrow at 9am"
letta-switchboard recurring create --agent-id agent-xxx --message "Daily standup" --cron "every weekday at 9am"
letta-switchboard onetime list letta-switchboard recurring list
letta-switchboard results list
See [CLI Documentation](cli/README.md) for installation and full usage guide.
## API Usage
Base URL: `https://letta--schedules-api.modal.run`
### Authentication
All endpoints require Bearer token authentication using your Letta API key:
bash curl -H "Authorization: Bearer your-letta-api-key" https://your-modal-app.modal.run/schedules/recurring
**Security Model:**
- **API Key Validation**: All requests validate your API key against Letta's API (lightweight `list agents` call with `limit=1`)
- **Create endpoints**: Verify API key is valid before creating schedule
- **List endpoints**: Returns only schedules created with your API key
- **Get/Delete endpoints**: Returns 403 Forbidden if the schedule wasn't created with your API key
- **Privacy**: API keys are never returned in responses, only used for authentication and execution
**Error Codes:**
- `401 Unauthorized`: Invalid or expired Letta API key
- `403 Forbidden`: Valid API key, but trying to access someone else's schedule
- `404 Not Found`: Schedule doesn't exist
### Create Recurring Schedule
Schedule a message to be sent on a cron schedule:
bash 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 minutes
- `0 */2 * * *` - Every 2 hours
- `0 0 * * 0` - Every Sunday at midnight
### Create One-Time Schedule
Schedule a message for a specific time:
bash 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 All Schedules
bash
curl -H "Authorization: Bearer your-letta-api-key" \ https://your-modal-app.modal.run/schedules/recurring
curl -H "Authorization: Bearer your-letta-api-key" \ https://your-modal-app.modal.run/schedules/one-time
### Get Specific Schedule
bash
curl -H "Authorization: Bearer your-letta-api-key" \ https://your-modal-app.modal.run/schedules/recurring/{schedule_id}
curl -H "Authorization: Bearer your-letta-api-key" \ https://your-modal-app.modal.run/schedules/one-time/{schedule_id}
### Delete Schedule
bash
curl -X DELETE -H "Authorization: Bearer your-letta-api-key" \ https://your-modal-app.modal.run/schedules/recurring/{schedule_id}
curl -X DELETE -H "Authorization: Bearer your-letta-api-key" \ https://your-modal-app.modal.run/schedules/one-time/{schedule_id}
### Get Execution Results
bash
curl -H "Authorization: Bearer your-letta-api-key" \ https://your-modal-app.modal.run/results
curl -H "Authorization: Bearer your-letta-api-key" \ https://your-modal-app.modal.run/results/{schedule_id}
**Result Format:**
json { "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:
bash
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')
curl -H "Authorization: Bearer your-letta-api-key" \ https://api.letta.com/v1/runs/$RUN_ID
## Response Format
### Recurring Schedule Response
json
{
"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.
### One-Time Schedule Response
json
{
"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:**
- API keys are stored securely and never returned in responses
- Once executed, one-time schedules are deleted from storage (check `/results` endpoint for execution history)
## How It Works
1. **API receives schedule request** → Validates and stores as JSON in Modal Volume
2. **Cron job runs every minute** → Checks all schedules in Volume
3. **Due schedules identified** → Spawns async executor functions
4. **Executor verifies schedule exists** → Skips if schedule was deleted after spawn
5. **For one-time schedules** → Deletes schedule file immediately (prevents re-execution)
6. **Executor calls Letta API** → Sends message to specified agent
7. **Saves execution result** → Stores run_id and metadata in results folder
8. **For recurring schedules** → Updates `last_run` timestamp in schedule file
**Race Condition Prevention:**
- One-time schedules are **deleted before execution** (not after)
- If multiple executors spawn, only first one successfully deletes
- Second executor finds no file → skips gracefully
- Filesystem is source of truth: file exists = hasn't run yet
## Storage Structure
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:**
- All schedule files are encrypted at rest using Fernet (AES-128-CBC)
- API keys never stored in plaintext
- User isolation via hash-based directories
- Time-based bucketing for efficient queries
**Performance Benefits:**
- **Recurring schedules:** O(user's schedules) instead of O(all schedules)
- **One-time schedules:** O(schedules in current hour) instead of O(all schedules)
- Only checks relevant time buckets during cron execution
- Automatic cleanup: Empty directories are removed after each cron run
## Monitoring
View logs in Modal dashboard:
bash modal app logs letta-switchboard
Or watch logs in real-time:
bash modal app logs letta-switchboard --follow
## Limitations
- **Minimum granularity:** 1 minute (cron runs every minute)
- **Timezone handling:** One-time schedules support timezones; recurring schedules run in UTC
- **Authentication:** Bearer token authentication with validation against Letta API
- **Encryption key management:** Single master key for all schedules (consider key rotation strategy for production)
## Future Improvements
- [ ] Encryption key rotation mechanism
- [ ] Execution history/logs API endpoint
- [ ] Rate limiting per user
- [ ] Email/webhook notifications on failures
- [ ] Pagination for list endpoints
- [ ] Timezone support for recurring schedules
- [ ] Schedule validation (max schedules per user)
- [ ] Cleanup of old date buckets (>7 days) to prevent unbounded growth
## Costs
Modal pricing (as of 2024):
- **Compute:** ~$0.000162/second for basic CPU
- **Volume storage:** ~$0.10/GB/month
- **Estimated monthly cost:** $5-10 for moderate usage (hundreds of schedules)
Free tier: 30 credits/month (~$30 value)
---
## Self-Hosting
Want to run your own instance? Switchboard is fully self-hostable on Modal.
### Prerequisites
1. Clone the repository:
bash git clone https://github.com/cpfiffer/letta-switchboard.git cd letta-switchboard
2. Install Modal CLI:
bash pip install modal modal setup
### Deploy Your Instance
**1. Set up encryption (required):**
bash
./setup_encryption.sh
ENCRYPTION_KEY=$(python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())") echo "Save this key: $ENCRYPTION_KEY" modal secret create letta-switchboard-encryption \ LETTA_SWITCHBOARD_ENCRYPTION_KEY="$ENCRYPTION_KEY"
**2. Deploy to Modal:**
bash modal deploy app.py
**3. Get your API URL:**
bash modal app list
**4. Configure CLI to use your instance:**
bash letta-switchboard config set-url https://your-instance.modal.run
### Local Development
Run locally with hot reloading:
bash
export LETTA_SWITCHBOARD_DEV_MODE=true
modal serve app.py
**Dev Mode Features:**
- Files stored in plaintext JSON (easy to inspect)
- No encryption overhead
- Perfect for debugging
- Auto-reload on code changes
**View files in dev mode:**
bash
cat /tmp/letta-switchboard-volume/schedules/recurring/abc123/uuid.json | jq
ls -la /tmp/letta-switchboard-volume/schedules/
### Testing Your Instance
Set environment variables:
bash export LETTA_API_KEY="sk-..." export LETTA_AGENT_ID="agent-xxx" export LETTA_SWITCHBOARD_URL="https://your-instance.modal.run"
Run tests:
bash
python test_api.py
./test_api.sh
pytest -m "not e2e"
pytest ```
Running your own instance on Modal:
The hosted service at letta--switchboard-api.modal.run is free to use!
MIT