Browse Source

Migrate to Letta's native scheduling API

BREAKING CHANGE: Switchboard API endpoints deprecated in favor of Letta's native scheduling.

## What Changed

### Dashboard (dashboard.html)
- Now calls Letta API directly (https://api.letta.com/v1)
- Converts ISO timestamps → Unix milliseconds
- Uses Letta's request format: {schedule, messages}
- Added migration banner with link to docs
- Stores agent_id in session storage
- Results tab shows message about Letta Cloud dashboard

### Backend (app.py)
- Removed all storage/polling/encryption logic (~800 lines)
- Simplified to ~120 lines of deprecation endpoints
- All old endpoints return 410 Gone with migration guide
- Dashboard serving endpoint remains functional
- No Modal Volume or Cron needed
- No secrets/encryption needed

### Documentation (README.md)
- Added prominent migration notice at top
- Detailed migration guide with before/after examples
- Updated Quick Start for new architecture
- Simplified features list
- Updated "How It Works" section

## Migration Path

**Old API (Deprecated):**
```
POST /schedules/one-time
Body: {agent_id, execute_at (ISO), message, role}
```

**New API (Use This):**
```
POST /v1/agents/{agent_id}/schedule
Body: {schedule: {type, scheduled_at (Unix ms)}, messages: [{role, content}]}
```

**Dashboard Users:** No action needed - dashboard updated automatically

**API Users:** Must migrate to Letta's native scheduling API

**Documentation:** https://docs.letta.com/guides/agents/scheduling/

## Benefits

- ✅ Removed 800+ lines of storage/polling logic
- ✅ No Modal Volume needed
- ✅ No encryption complexity
- ✅ Direct Letta API calls (faster, more reliable)
- ✅ Users can verify schedules in Letta Cloud dashboard
- ✅ Letta handles all execution and storage

## Deployment

Dashboard remains hosted at same URL for backwards compatibility.
Old API calls receive helpful deprecation messages.

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>
Cameron Pfiffer 3 months ago
parent
commit
36fadf8e1f
3 changed files with 346 additions and 1119 deletions
  1. 130 121
      README.md
  2. 88 939
      app.py
  3. 128 59
      dashboard.html

+ 130 - 121
README.md

@@ -1,168 +1,177 @@
-# Letta Switchboard
+# Letta Scheduling Dashboard
 
-**Free hosted message routing service for Letta agents.**
+## ⚠️ Important Update: Now Using Letta's Native Scheduling
 
-Send messages to your Letta agents immediately or scheduled for later. Supports natural language scheduling ("in 5 minutes", "every weekday at 9am") and secure cross-agent communication.
+**Letta now provides built-in scheduling!** This project has been simplified to a web dashboard that calls Letta's API directly.
 
-🌐 **Hosted Service:** https://letta--switchboard-api.modal.run
-🎛️ **Web Dashboard:** [`/dashboard`](https://letta--switchboard-api.modal.run/dashboard)  
-💻 **CLI:** [`letta-switchboard`](cli/)  
-🔒 **Security:** End-to-end encryption, API key isolation  
-📖 **Docs:** [CLI Guide](cli/README.md) | [API Reference](#api-usage)
+### What Changed?
+
+- ✅ **Dashboard Updated** - Now calls Letta API directly from your browser
+- ❌ **Old API Deprecated** - Backend scheduling endpoints return 410 Gone
+- ➡️ **Migration Required** - Use Letta's native scheduling API
+
+### Quick Links
+
+🎛️ **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
+
+---
+
+## Migration Guide
+
+### For Dashboard Users
+
+The dashboard now calls Letta's API directly! Just continue using it - no changes needed.
+
+1. Visit https://letta--switchboard-api.modal.run/dashboard
+2. Enter your Letta API key and agent ID
+3. Create schedules as before
+
+### For API Users
+
+If you were calling the old Switchboard API, you need to migrate to Letta's native API:
+
+**Old Endpoint (Deprecated):**
+```bash
+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):**
+```bash
+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)
+- Cron expressions unchanged (still 5-field format)
+
+**Full Documentation:** https://docs.letta.com/guides/agents/scheduling/
+
+---
 
 ## Quick Start
 
-### Option 1: Web Dashboard (Easiest)
+### Use the Web Dashboard
 
-Visit the dashboard in your browser: **[https://letta--switchboard-api.modal.run/dashboard](https://letta--switchboard-api.modal.run/dashboard)**
+Visit **[https://letta--switchboard-api.modal.run/dashboard](https://letta--switchboard-api.modal.run/dashboard)**
 
 1. Enter your Letta API key (stored in browser session only)
-2. View all your schedules with loading indicators
-3. Create new schedules with timezone support
-4. View execution results with success/failed status badges
-5. Delete schedules with confirmation dialog
-6. Refresh data anytime
+2. Enter your agent ID
+3. View, create, and delete schedules
+4. Dashboard calls Letta's API directly from your browser
 
 Features:
-- **Timezone support** - Local browser time, UTC, or major timezones
-- **Loading indicators** - Visual feedback while fetching data
-- **Status badges** - See successful vs failed executions at a glance
-- **No installation** - Just visit the URL in your browser!
+- **Direct Letta API integration** - No backend needed
+- **Timezone support** - Local browser time, UTC, or major timezones  
+- **Simple interface** - Clean, easy-to-use dashboard
+- **No installation** - Just visit the URL!
 
-### Option 2: Using cURL (No Installation Required)
+### Use Letta's API Directly
 
-Send a message right now with just cURL:
+For programmatic access, use Letta's scheduling API:
 
+**Create a one-time schedule:**
 ```bash
-curl -X POST https://letta--switchboard-api.modal.run/schedules/one-time \
-  -H "Content-Type: application/json" \
+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 '{
-    "agent_id": "agent-xxx",
-    "execute_at": "2025-11-12T20:00:00Z",
-    "message": "Hello from Switchboard!",
-    "role": "user"
+    "schedule": {
+      "type": "one-time",
+      "scheduled_at": 1731499200000
+    },
+    "messages": [{
+      "role": "user",
+      "content": "Hello!"
+    }]
   }'
 ```
 
-Or create a recurring schedule:
-
+**Create a recurring schedule:**
 ```bash
-curl -X POST https://letta--switchboard-api.modal.run/schedules/recurring \
-  -H "Content-Type: application/json" \
+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 '{
-    "agent_id": "agent-xxx",
-    "cron": "0 9 * * 1-5",
-    "message": "Daily standup reminder",
-    "role": "user"
+    "schedule": {
+      "type": "recurring",
+      "cron_expression": "0 9 * * 1-5"
+    },
+    "messages": [{
+      "role": "user",
+      "content": "Daily standup"
+    }]
   }'
 ```
 
-**Check your schedules:**
-
+**List schedules:**
 ```bash
-# List all one-time schedules
-curl https://letta--switchboard-api.modal.run/schedules/one-time \
-  -H "Authorization: Bearer YOUR_LETTA_API_KEY"
-
-# List all recurring schedules
-curl https://letta--switchboard-api.modal.run/schedules/recurring \
-  -H "Authorization: Bearer YOUR_LETTA_API_KEY"
-
-# View execution results
-curl https://letta--switchboard-api.modal.run/results \
+curl https://api.letta.com/v1/agents/agent-xxx/schedule \
   -H "Authorization: Bearer YOUR_LETTA_API_KEY"
 ```
 
-**Note:** 
-- Replace `YOUR_LETTA_API_KEY` with your actual Letta API key
-- Replace `agent-xxx` with your agent ID
-- The API key in the Authorization header is used for authentication and storage isolation
-- You don't need to include it in the request body!
-
-**Pro tip:** Use the CLI for natural language scheduling - it's much easier than writing ISO timestamps and cron expressions!
-
-### Option 3: Using the CLI (Advanced)
-
-The CLI makes natural language scheduling much easier:
-
+**Delete a schedule:**
 ```bash
-# Download the CLI (or build from source)
-cd cli
-go build -o letta-switchboard
-
-# Configure with your Letta API key
-./letta-switchboard config set-api-key sk-your-letta-key
+curl -X DELETE https://api.letta.com/v1/agents/agent-xxx/schedule/SCHEDULE_ID \
+  -H "Authorization: Bearer YOUR_LETTA_API_KEY"
 ```
 
-**Send messages with natural language:**
-
-```bash
-# Send immediately
-letta-switchboard send \
-  --agent-id agent-xxx \
-  --message "Hello from Switchboard!"
-
-# Send later (natural language!)
-letta-switchboard send \
-  --agent-id agent-xxx \
-  --message "Reminder to check in" \
-  --execute-at "tomorrow at 9am"
-
-# Create recurring schedule (plain English!)
-letta-switchboard recurring create \
-  --agent-id agent-xxx \
-  --message "Daily standup" \
-  --cron "every weekday at 10am"
-```
+**Full Documentation:** https://docs.letta.com/guides/agents/scheduling/
 
-That's it! The hosted service handles everything - scheduling, execution, and delivery.
+## What is This?
 
-## Features
+A simple web dashboard for managing Letta agent schedules. The dashboard calls Letta's native scheduling API directly from your browser.
 
-✅ **Free hosted service** - No deployment needed  
-✅ **Natural language scheduling** - "in 5 minutes", "tomorrow at 9am", "every weekday"  
-✅ **Secure by default** - API key isolation, encrypted storage  
-✅ **Recurring schedules** - Cron expressions or plain English  
-✅ **Instant delivery** - Messages execute within 1 minute  
-✅ **Execution tracking** - Get run IDs for every message  
-✅ **Self-hostable** - Deploy your own instance if needed
+### Features
 
-## Why Use Switchboard?
+✅ **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
 
-**No Infrastructure Setup**  
-Just use the hosted service at `https://letta--switchboard-api.modal.run`. No deployment, no servers, no configuration.
+### Why Use This Dashboard?
 
-**Natural Language Scheduling**  
-Forget cron syntax. Use plain English like "every weekday at 9am" or "in 5 minutes".
+**Easy to Use**  
+Simple interface for creating and managing Letta schedules without writing API calls.
 
-**Secure by Design**  
-- Your schedules are isolated by API key
-- End-to-end encryption at rest
-- Only you can see your schedules
-- Execution results tracked with run IDs
+**No Installation**  
+Just visit the URL - no setup, no configuration, no CLI to install.
 
-**Always Available**  
-- Runs on Modal's infrastructure
-- Checks every minute for due schedules
-- Automatic retries and error handling
-- 99.9% uptime
+**Direct Letta API**  
+Your schedules are stored and executed by Letta Cloud. Check execution history at https://app.letta.com
 
 ## How It Works
 
-1. **You create a schedule** → Via CLI or API with your Letta API key
-2. **Switchboard validates** → Checks your API key against Letta's API
-3. **Schedule stored** → Encrypted and isolated in your hash-based directory
-4. **Cron checks every minute** → Looks for due schedules in your bucket
-5. **Message sent** → Calls Letta's API with your credentials to send the message
-6. **Result saved** → Stores run ID and execution metadata
-
-**Security Model:**
-- Your API key is validated but never stored in plaintext
-- Schedules hashed by API key for isolation
-- Only you can list/view/delete your schedules
-- Messages sent using your API key (you stay in control)
+1. **Visit the dashboard** → https://letta--switchboard-api.modal.run/dashboard
+2. **Enter credentials** → Your Letta API key and agent ID
+3. **Create schedules** → Dashboard calls Letta API from your browser
+4. **Letta executes** → Letta Cloud handles scheduling and execution
+5. **Check results** → View execution history at https://app.letta.com
+
+**Architecture:**
+- Dashboard is pure client-side JavaScript
+- API calls go directly to Letta (https://api.letta.com)
+- No Switchboard backend involved in scheduling
+- Your API key never leaves your browser (stored in session storage)
 
 ## Natural Language Examples
 

File diff suppressed because it is too large
+ 88 - 939
app.py


+ 128 - 59
dashboard.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html>
 <head>
-    <title>Switchboard Dashboard</title>
+    <title>Letta Scheduling Dashboard</title>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <style>
@@ -40,6 +40,30 @@
         .header-links a:hover {
             color: #1a1a1a;
         }
+        
+        /* Migration Banner */
+        .migration-banner {
+            background: #fff3cd;
+            border: 2px solid #ffc107;
+            border-radius: 8px;
+            padding: 16px;
+            margin-bottom: 24px;
+        }
+        .migration-banner h3 {
+            font-size: 16px;
+            font-weight: 600;
+            margin-bottom: 8px;
+            color: #856404;
+        }
+        .migration-banner p {
+            font-size: 14px;
+            color: #856404;
+            margin-bottom: 8px;
+        }
+        .migration-banner a {
+            color: #856404;
+            font-weight: 600;
+        }
 
         h2 {
             font-size: 16px;
@@ -271,12 +295,22 @@
 <body>
     <div class="container">
         <div class="header">
-            <h1>Switchboard Dashboard</h1>
+            <h1>Letta Scheduling Dashboard</h1>
             <div class="header-links">
                 <a href="/">Home</a>
-                <a href="https://github.com/cpfiffer/letta-switchboard">Docs</a>
+                <a href="https://docs.letta.com/guides/agents/scheduling/" target="_blank">Letta Docs</a>
+                <a href="https://github.com/cpfiffer/letta-switchboard">GitHub</a>
             </div>
         </div>
+        
+        <!-- Migration Notice -->
+        <div class="migration-banner">
+            <h3>✨ Now powered by Letta's native scheduling!</h3>
+            <p>
+                This dashboard now calls Letta's API directly. The old Switchboard backend has been deprecated.
+                Read the <a href="https://docs.letta.com/guides/agents/scheduling/" target="_blank">full scheduling docs →</a>
+            </p>
+        </div>
 
         <!-- API Key Input -->
         <div id="api-key-section" class="card">
@@ -434,15 +468,28 @@
     </div>
 
     <script>
-        const API_BASE = window.location.origin;
+        const LETTA_API = 'https://api.letta.com/v1';
         let API_KEY = sessionStorage.getItem('letta_api_key') || '';
+        let DEFAULT_AGENT_ID = sessionStorage.getItem('default_agent_id') || '';
 
         // Initialize
         if (API_KEY) {
             document.getElementById('api-key-input').value = API_KEY;
             document.getElementById('main-content').classList.remove('hidden');
-            loadSchedules();
-            loadResults();
+            if (DEFAULT_AGENT_ID) {
+                document.getElementById('agent-id').value = DEFAULT_AGENT_ID;
+                loadSchedules();
+            }
+        }
+        
+        // Helper: Convert ISO timestamp to Unix milliseconds
+        function toUnixMs(isoString) {
+            return new Date(isoString).getTime();
+        }
+        
+        // Helper: Convert Unix milliseconds to readable date
+        function fromUnixMs(ms) {
+            return new Date(ms).toLocaleString();
         }
 
         function saveApiKey() {
@@ -485,6 +532,16 @@
         }
 
         async function loadSchedules() {
+            const agentId = document.getElementById('agent-id').value.trim();
+            if (!agentId) {
+                alert('Please enter an Agent ID first');
+                return;
+            }
+            
+            // Save for next time
+            sessionStorage.setItem('default_agent_id', agentId);
+            DEFAULT_AGENT_ID = agentId;
+            
             const onetimeBody = document.querySelector('#onetime-table tbody');
             const recurringBody = document.querySelector('#recurring-table tbody');
 
@@ -492,22 +549,29 @@
             recurringBody.innerHTML = '<tr><td colspan="7" class="loading"><div class="spinner"></div><p>Loading...</p></td></tr>';
 
             try {
-                const [onetime, recurring] = await Promise.all([
-                    fetch(`${API_BASE}/schedules/one-time`, {
-                        headers: { 'Authorization': `Bearer ${API_KEY}` }
-                    }).then(r => r.json()),
-                    fetch(`${API_BASE}/schedules/recurring`, {
-                        headers: { 'Authorization': `Bearer ${API_KEY}` }
-                    }).then(r => r.json())
-                ]);
+                // Letta API returns all schedules (one-time and recurring) in a single call
+                const response = await fetch(`${LETTA_API}/agents/${agentId}/schedule`, {
+                    headers: { 'Authorization': `Bearer ${API_KEY}` }
+                });
+                
+                if (!response.ok) {
+                    throw new Error(`Failed to load schedules: ${response.statusText}`);
+                }
+                
+                const data = await response.json();
+                const schedules = data.scheduled_messages || [];
+                
+                // Split into one-time and recurring
+                const onetime = schedules.filter(s => s.schedule.type === 'one-time');
+                const recurring = schedules.filter(s => s.schedule.type === 'recurring');
 
                 onetimeBody.innerHTML = onetime.length ? onetime.map(s => `
                     <tr>
                         <td class="mono">${s.id.substring(0, 8)}...</td>
                         <td class="mono">${s.agent_id}</td>
-                        <td>${new Date(s.execute_at).toLocaleString()}</td>
-                        <td>${truncate(s.message, 50)}</td>
-                        <td><button class="danger sm" onclick="deleteSchedule('one-time', '${s.id}')">Delete</button></td>
+                        <td>${fromUnixMs(s.next_scheduled_at)}</td>
+                        <td>${truncate(s.messages[0]?.content || '', 50)}</td>
+                        <td><button class="danger sm" onclick="deleteSchedule('${s.id}')">Delete</button></td>
                     </tr>
                 `).join('') : '<tr><td colspan="5" class="empty">No one-time schedules</td></tr>';
 
@@ -515,43 +579,25 @@
                     <tr>
                         <td class="mono">${s.id.substring(0, 8)}...</td>
                         <td class="mono">${s.agent_id}</td>
-                        <td class="mono">${s.cron}</td>
-                        <td>${s.timezone || 'UTC'}</td>
-                        <td>${truncate(s.message, 50)}</td>
-                        <td>${s.last_run ? new Date(s.last_run).toLocaleString() : 'Never'}</td>
-                        <td><button class="danger sm" onclick="deleteSchedule('recurring', '${s.id}')">Delete</button></td>
+                        <td class="mono">${s.schedule.cron_expression}</td>
+                        <td>UTC</td>
+                        <td>${truncate(s.messages[0]?.content || '', 50)}</td>
+                        <td>${s.next_scheduled_at ? fromUnixMs(s.next_scheduled_at) : 'Never'}</td>
+                        <td><button class="danger sm" onclick="deleteSchedule('${s.id}')">Delete</button></td>
                     </tr>
                 `).join('') : '<tr><td colspan="7" class="empty">No recurring schedules</td></tr>';
             } catch (error) {
-                onetimeBody.innerHTML = `<tr><td colspan="5" class="empty">Error loading schedules</td></tr>`;
-                recurringBody.innerHTML = `<tr><td colspan="7" class="empty">Error loading schedules</td></tr>`;
+                onetimeBody.innerHTML = `<tr><td colspan="5" class="empty">Error: ${error.message}</td></tr>`;
+                recurringBody.innerHTML = `<tr><td colspan="7" class="empty">Error: ${error.message}</td></tr>`;
             }
         }
 
         async function loadResults() {
             const resultsBody = document.querySelector('#results-table tbody');
-
-            resultsBody.innerHTML = '<tr><td colspan="7" class="loading"><div class="spinner"></div><p>Loading...</p></td></tr>';
-
-            try {
-                const results = await fetch(`${API_BASE}/results`, {
-                    headers: { 'Authorization': `Bearer ${API_KEY}` }
-                }).then(r => r.json());
-
-                resultsBody.innerHTML = results.length ? results.map(r => `
-                    <tr>
-                        <td class="mono">${r.schedule_id.substring(0, 8)}...</td>
-                        <td>${r.schedule_type}</td>
-                        <td><span class="badge ${r.status}">${r.status}</span></td>
-                        <td class="mono">${r.agent_id}</td>
-                        <td>${truncate(r.message, 30)}</td>
-                        <td class="mono">${r.run_id || r.error || '-'}</td>
-                        <td>${new Date(r.executed_at).toLocaleString()}</td>
-                    </tr>
-                `).join('') : '<tr><td colspan="7" class="empty">No execution results</td></tr>';
-            } catch (error) {
-                resultsBody.innerHTML = `<tr><td colspan="7" class="empty">Error loading results</td></tr>`;
-            }
+            resultsBody.innerHTML = '<tr><td colspan="7" class="empty">Results tracking not available yet. Check Letta Cloud dashboard for execution history.</td></tr>';
+            
+            // NOTE: Letta's scheduling API doesn't expose execution history yet
+            // Users should check the Letta Cloud dashboard or use the runs API
         }
 
         async function createSchedule() {
@@ -566,12 +612,18 @@
                 errorDiv.textContent = 'Please fill in all required fields';
                 return;
             }
+            
+            // Save agent ID for convenience
+            sessionStorage.setItem('default_agent_id', agentId);
+            DEFAULT_AGENT_ID = agentId;
 
             try {
+                // Build Letta API format
                 const payload = {
-                    agent_id: agentId,
-                    message: message,
-                    role: 'user'
+                    messages: [{
+                        role: 'user',
+                        content: message
+                    }]
                 };
 
                 if (type === 'onetime') {
@@ -592,19 +644,26 @@
                         executeDate = new Date(executeAt);
                     }
 
-                    payload.execute_at = executeDate.toISOString();
+                    // Convert to Unix milliseconds for Letta API
+                    payload.schedule = {
+                        type: 'one-time',
+                        scheduled_at: executeDate.getTime()
+                    };
                 } else {
                     const cron = document.getElementById('cron').value.trim();
                     if (!cron) {
                         errorDiv.textContent = 'Please specify cron expression';
                         return;
                     }
-                    payload.cron = cron;
-                    payload.timezone = document.getElementById('recurring-timezone').value;
+                    
+                    // Letta uses 5-field cron (no seconds)
+                    payload.schedule = {
+                        type: 'recurring',
+                        cron_expression: cron
+                    };
                 }
 
-                const endpoint = type === 'onetime' ? '/schedules/one-time' : '/schedules/recurring';
-                const response = await fetch(`${API_BASE}${endpoint}`, {
+                const response = await fetch(`${LETTA_API}/agents/${agentId}/schedule`, {
                     method: 'POST',
                     headers: {
                         'Authorization': `Bearer ${API_KEY}`,
@@ -615,15 +674,16 @@
 
                 if (!response.ok) {
                     const error = await response.json();
-                    throw new Error(error.detail || 'Failed to create schedule');
+                    throw new Error(error.detail || error.message || 'Failed to create schedule');
                 }
 
-                document.getElementById('agent-id').value = '';
+                // Clear form
                 document.getElementById('message').value = '';
                 document.getElementById('execute-at').value = '';
                 document.getElementById('cron').value = '';
                 document.getElementById('recurring-timezone').value = 'UTC';
 
+                // Switch to schedules tab
                 showTab('schedules');
                 document.querySelector('.tab').click();
                 loadSchedules();
@@ -632,16 +692,25 @@
             }
         }
 
-        async function deleteSchedule(type, id) {
+        async function deleteSchedule(id) {
             if (!confirm('Delete this schedule?')) return;
+            
+            const agentId = document.getElementById('agent-id').value.trim();
+            if (!agentId) {
+                alert('Agent ID is required');
+                return;
+            }
 
             try {
-                const response = await fetch(`${API_BASE}/schedules/${type}/${id}`, {
+                const response = await fetch(`${LETTA_API}/agents/${agentId}/schedule/${id}`, {
                     method: 'DELETE',
                     headers: { 'Authorization': `Bearer ${API_KEY}` }
                 });
 
-                if (!response.ok) throw new Error('Failed to delete');
+                if (!response.ok) {
+                    const error = await response.json();
+                    throw new Error(error.detail || error.message || 'Failed to delete');
+                }
 
                 loadSchedules();
             } catch (error) {