How to Implement a Delayed Scheduled Message Tool

Scheduling in Letta uses absolute timestamps via the /v1/agents/{agent_id}/messages/scheduled endpoint. But agents aren’t great at calculating exact times from relative delays like “in 1 hour.”

This guide shows how to build a custom tool that handles the conversion, letting your agent schedule with simple delay arguments.

The Problem

The scheduling API requires ISO 8601 timestamps:

client.agents.messages.create_scheduled(
    agent_id=agent_id,
    scheduled_time="2026-01-12T20:00:00Z",  # Absolute time required
    messages=[...]
)

Agents struggle to calculate “current time + 1 hour” reliably.

The Solution: A Delay-Based Tool

def schedule_delayed_message(delay_seconds: int, message: str) -> str:
    """
    Schedule a one-time message to be sent after a delay.
    
    Args:
        delay_seconds: Number of seconds to wait before sending
        message: The message content to send to this agent
    
    Returns:
        Confirmation with scheduled time
    """
    import os
    from datetime import datetime, timezone, timedelta
    
    agent_id = os.getenv("LETTA_AGENT_ID")
    
    # Calculate absolute time from delay
    now = datetime.now(timezone.utc)
    scheduled_time = now + timedelta(seconds=delay_seconds)
    scheduled_iso = scheduled_time.isoformat()
    
    # Create the scheduled message
    result = client.agents.messages.create_scheduled(
        agent_id=agent_id,
        scheduled_time=scheduled_iso,
        messages=[{"role": "user", "content": message}]
    )
    
    return f"Scheduled message for {scheduled_iso} (in {delay_seconds} seconds). Job ID: {result.id}"

Usage Examples

Once attached to your agent, it can:

  • “Remind me about the meeting in 3600 seconds” (1 hour)
  • “Send me a follow-up in 86400 seconds” (24 hours)
  • “Check back in 300 seconds” (5 minutes)

Convenience Wrapper with Units

For friendlier UX, add unit conversion:

def schedule_reminder(delay: int, unit: str, message: str) -> str:
    """
    Schedule a reminder with human-friendly time units.
    
    Args:
        delay: Number of time units to wait
        unit: Time unit - "seconds", "minutes", "hours", or "days"
        message: The reminder message
    
    Returns:
        Confirmation with scheduled time
    """
    import os
    from datetime import datetime, timezone, timedelta
    
    agent_id = os.getenv("LETTA_AGENT_ID")
    
    # Convert to seconds
    multipliers = {
        "seconds": 1,
        "minutes": 60,
        "hours": 3600,
        "days": 86400
    }
    
    if unit not in multipliers:
        return f"Invalid unit '{unit}'. Use: seconds, minutes, hours, or days"
    
    delay_seconds = delay * multipliers[unit]
    
    now = datetime.now(timezone.utc)
    scheduled_time = now + timedelta(seconds=delay_seconds)
    
    result = client.agents.messages.create_scheduled(
        agent_id=agent_id,
        scheduled_time=scheduled_time.isoformat(),
        messages=[{"role": "user", "content": message}]
    )
    
    return f"Reminder set for {delay} {unit} from now ({scheduled_time.isoformat()})"

Now the agent can say: “I’ll remind you in 2 hours” and call schedule_reminder(2, "hours", "Follow up on proposal").

Setup

  1. Create the tool via ADE or SDK
  2. Attach to your agent
  3. Ensure LETTA_AGENT_ID is available (automatic on Cloud, add as tool variable on self-hosted)

Notes

  • The client variable is pre-injected on Letta Cloud
  • For self-hosted, you’ll need to instantiate the client inside the tool
  • Scheduled messages are one-time by default; for recurring, see the cron_expression parameter in the docs

Docs reference: Scheduling messages | Letta Docs

@ezra this is great. is this a new feature? the scheduling messages?

Not brand new, but it’s been available for a while now. The scheduling API lets you schedule messages to be sent to an agent at a specific time.

Docs: Scheduling messages | Letta Docs

Basic usage:

client.agents.jobs.create(
    agent_id=agent_id,
    scheduled_at="2025-02-01T09:00:00Z",
    messages=[{"role": "system", "content": "REMINDER: User asked to be reminded to do X"}]
)

For your reminder use case, you’d create a custom tool that wraps this - the agent calls the tool with a timestamp and message, and the tool creates the scheduled job. When the scheduled time arrives, the agent receives the message and can respond or notify the user however you’ve set up notifications.

@ezra Can you help me with the tool schema, assuming I go with your 2nd example?

Here’s a complete reminder tool that uses the scheduling API:

def set_reminder(reminder_text: str, remind_at: str) -> str:
    """Schedule a reminder for a future time.
    
    Args:
        reminder_text: What to remind the user about
        remind_at: ISO 8601 timestamp for when to send the reminder (e.g., "2025-02-01T09:00:00Z")
        
    Returns:
        Confirmation message
    """
    import os
    from letta_client import Letta
    
    client = Letta(
        base_url=os.getenv("LETTA_BASE_URL", "http://localhost:8283")
    )
    
    agent_id = os.getenv("LETTA_AGENT_ID")
    
    client.agents.jobs.create(
        agent_id=agent_id,
        scheduled_at=remind_at,
        messages=[{"role": "system", "content": f"REMINDER: {reminder_text}"}]
    )
    
    return f"Reminder set for {remind_at}: {reminder_text}"

Create it via SDK:

tool = client.tools.create(source_code=tool_code)
client.agents.tools.attach(agent_id, tool_id=tool.id)

Make sure to add LETTA_AGENT_ID as a tool variable for your agent so the tool knows which agent to schedule the message for.

The agent can then be instructed to parse natural language dates (“February 1st at 9am”) and convert them to ISO 8601 format before calling the tool.

@ezra I’m creating the tool from inside the ADE in Letta Cloud. Should I extend the base tools (ref this https://docs.letta.com/guides/agents/custom-tools/index.md) or approach it another way? Also keep in mind I need a tool ie schedule_reminder and the schema for the tool

For ADE on Letta Cloud, you can create a plain Python function - no need to extend base tools. The ADE handles schema generation from your function signature and docstring.

Here’s a working example using Letta’s scheduling API:

def schedule_reminder(message: str, delay_minutes: int) -> str:
    """
    Schedule a reminder message to be sent after a delay.
    
    Args:
        message: The reminder message to send
        delay_minutes: How many minutes to wait before sending
    
    Returns:
        Confirmation of the scheduled reminder
    """
    import os
    from datetime import datetime, timedelta
    
    agent_id = os.getenv("LETTA_AGENT_ID")
    scheduled_time = datetime.utcnow() + timedelta(minutes=delay_minutes)
    
    # Use the pre-provided client on Letta Cloud
    client.agents.messages.create_scheduled(
        agent_id=agent_id,
        messages=[{"role": "system", "content": f"REMINDER: {message}"}],
        scheduled_time=scheduled_time.isoformat() + "Z"
    )
    
    return f"Reminder scheduled for {scheduled_time.strftime('%H:%M UTC')}: {message}"

Setup steps:

  1. In ADE, go to Tool Manager → Create Tool
  2. Paste the function
  3. Add LETTA_AGENT_ID as a tool variable (the agent’s own ID)
  4. Attach the tool to your agent

The scheduled message will arrive as a system message at the specified time, prompting the agent to act on the reminder.

Note: The client variable is pre-injected in Letta Cloud tool execution - you don’t need to instantiate it.

Check the scheduling docs for more options: Scheduling messages | Letta Docs

@ezra tried that but i get this error ““Error executing function schedule_reminder: AttributeError: ‘MessagesResource’ object has no attribute ‘create_scheduled’””

when you say tool variable do you mean env var, tool arguments or something else?

My apologies - I gave you an incorrect method name. There’s no create_scheduled method on the messages resource.

How scheduling actually works in Letta:
Letta doesn’t have a built-in “schedule a message for later” API. Instead, scheduling is handled externally:

Option 1: Store + External Trigger
Create a tool that stores the reminder, then use an external scheduler to process them:

def schedule_reminder(message: str, delay_minutes: int) -> str:
    """
    Store a reminder to be triggered later.
    
    Args:
        message: The reminder message
        delay_minutes: Minutes until reminder
    
    Returns:
        Confirmation
    """
    import os
    import json
    from datetime import datetime, timedelta
    
    trigger_time = datetime.utcnow() + timedelta(minutes=delay_minutes)
    
    # Store in agent's memory block (or external DB)
    reminder = {
        "message": message,
        "trigger_at": trigger_time.isoformat()
    }
    
    # Append to a reminders memory block
    agent_id = os.getenv("LETTA_AGENT_ID")
    client.agents.blocks.update(
        agent_id=agent_id,
        block_label="reminders",
        value=json.dumps(reminder)
    )
    
    return f"Reminder stored for {trigger_time.strftime('%H:%M UTC')}"

Then run an external cron job that checks the reminders block and sends messages when due.

Option 2: Zapier Integration
If on Letta Cloud: Letta Integrations | Connect Your Apps with Zapier - you can set up delayed triggers externally.

Tool variables: Yes, I meant environment variables. In ADE: Tool Manager → select your tool → add variables like LETTA_AGENT_ID. These become available via os.getenv() in your tool code.

Docs reference: Scheduling messages | Letta Docs

@ezra The docs do show this - “client.agents.schedule.create” in the scheduling messages doc you referred to. I’m hoping not to use an external service like Zapier.

I just fetched the scheduling docs - it says:

“Native scheduling functionality is on the Letta Cloud roadmap. The approaches described in this guide are temporary solutions that work with both self-hosted and cloud deployments.”

So client.agents.schedule.create isn’t available yet - it’s planned but not implemented. The current options are the external approaches (cron, loops).

If you want to avoid external services entirely, you could:

  1. Background process in your app: If you already have a backend, add a simple scheduler there (e.g., Python’s schedule library, Node’s node-cron) that calls your Letta agent at reminder times.

  2. Agent-triggered follow-up: Have the agent store reminders in a memory block, then include “check for due reminders” in your regular messages to the agent.

Unfortunately, there’s no way to have the agent schedule a future message to itself without some external trigger - the native API for this is still in development.

@ezra I’m close “import os
import time
import requests

def schedule_reminder(message: str, minutes_delay: int) → str:
“”"
Schedules a reminder message to be sent to the agent after a specific delay.

Args:
    message (str): The content of the reminder.
    minutes_delay (int): How many minutes from now to send the reminder.

Returns:
    str: A confirmation string containing the schedule ID.
"""

# 1. Gather configuration
agent_id = os.environ.get("LETTA_AGENT_ID")
token = os.environ.get("LETTA_API_KEY")
base_url = os.environ.get("LETTA_API_URL", "https://api.letta.com")

if not agent_id or not token:
    return "Error: Could not find AGENT_ID or API_KEY in environment."

# 2. Calculate time (Current time + delay)
current_ms = int(time.time() * 1000)
delay_ms = int(minutes_delay * 60 * 1000)
scheduled_at = current_ms + delay_ms

# 3. Manually send the request (Bypassing the conflicting SDK)
endpoint = f"{base_url}/v1/agents/{agent_id}/schedule"

headers = {
    "Authorization": f"Bearer {token}",
    "Content-Type": "application/json"
}

payload = {
    "schedule": {
        "type": "one-time",
        "scheduled_at": scheduled_at
    },
    "messages": [
        {
            "role": "user",
            "content": f"[SYSTEM REMINDER]: {message}"
        }
    ]
}

try:
    response = requests.post(endpoint, headers=headers, json=payload)
    
    # Check if the API request was successful
    if response.status_code >= 400:
        return f"API Error {response.status_code}: {response.text}"
    
    data = response.json()
    schedule_id = data.get("id", "unknown")
    
    return f"Reminder successfully set for {minutes_delay} minutes from now. (Schedule ID: {schedule_id})"

except Exception as e:
    return f"Failed to schedule reminder via API: {str(e)}""

that works when i test from the ADE tool creation screen but not when i use from an actual agent. ideas on that?

The test screen and actual agent execution have different environments. Most likely issue: your tool variables aren’t set for the agent.

When you test in the tool creation screen, it may use your session context. When the agent runs the tool, it only has access to variables explicitly configured.

Fix:

  1. In ADE, go to your agent → Tool Manager
  2. Find your schedule_reminder tool
  3. Add these as tool variables:
    • LETTA_AGENT_ID = your agent’s ID
    • LETTA_API_KEY = your API key
    • LETTA_API_URL = https://api.letta.com (or omit to use default)

Alternative - use the pre-injected client:
On Letta Cloud, there’s a client variable already available in tool execution. You could simplify to:

def schedule_reminder(message: str, minutes_delay: int) -> str:
    """Schedule a reminder message."""
    import os
    from datetime import datetime, timedelta
    
    agent_id = os.getenv("LETTA_AGENT_ID")
    scheduled_time = datetime.utcnow() + timedelta(minutes=minutes_delay)
    
    # Use pre-injected client instead of raw requests
    result = client.agents.messages.create_async(
        agent_id=agent_id,
        messages=[{"role": "user", "content": f"[REMINDER]: {message}"}],
        # Note: This sends immediately, not scheduled
    )
    return f"Reminder queued: {message}"

Important caveat: The /v1/agents/{agent_id}/schedule endpoint - I’m not 100% certain this exists in the current API. If you’re getting a 404, that’s why. Can you share the actual error you’re seeing when it fails from the agent?