Complete tool configuration for Discord integration with Letta AI agents
A set of 4 custom tools that enable your Letta agent to:
- Send direct messages to Discord users
- Post messages in Discord channels
- Create scheduled tasks and reminders
- Delete scheduled tasks
These tools bridge the gap between Letta's AI capabilities and Discord's messaging platform.
Before you start, you'll need:
- Letta Cloud account
- Letta API key
- Letta agent ID
- Discord bot token (from Discord Developer Portal)
- Discord bot invited to your server with proper permissions
- Tasks channel ID (optional, for scheduling features)
DISCORD_BOT_TOKEN = "YOUR_DISCORD_BOT_TOKEN_HERE" # From Discord Developer Portal
TASKS_CHANNEL_ID = "YOUR_TASKS_CHANNEL_ID_HERE" # Right-click channel → Copy ID (for scheduling)
DEFAULT_USER_ID = "YOUR_DISCORD_USER_ID_HERE" # Right-click yourself → Copy ID (default recipient)How to get these values:
-
Discord Bot Token:
- Go to https://discord.com/developers/applications
- Select your application → Bot → Reset Token → Copy
-
Channel ID / User ID:
- Enable Developer Mode: Discord → Settings → Advanced → Developer Mode
- Right-click channel/user → Copy ID
- Go to https://app.letta.com/
- Select your agent
- Navigate to "Tools" tab
- Click "Create new tool"
- Enter tool name (e.g.,
send_discord_dm) - Paste the Source Code (Python) from below
- The JSON schema will be auto-generated
- Click "Create tool"
- Repeat for all 4 tools
See the complete setup example at the bottom of this document.
Send a direct message to a Discord user.
Name: send_discord_dm
JSON Schema:
{
"name": "send_discord_dm",
"description": "Sends a direct message to a Discord user.",
"parameters": {
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "The message content to send"
},
"user_id": {
"type": "string",
"description": "The Discord user ID of the recipient"
}
},
"required": ["message", "user_id"]
}
}Source Code:
import requests
def chunk_message(text: str, limit: int = 1900):
"""Split long messages into chunks to respect Discord's 2000 char limit"""
if len(text) <= limit:
return [text]
chunks = []
i = 0
while i < len(text):
end = min(i + limit, len(text))
chunks.append(text[i:end])
i = end
return chunks
def send_discord_dm(message: str, user_id: str):
# ⚠️ REPLACE THIS with your Discord bot token
DISCORD_BOT_TOKEN = "YOUR_DISCORD_BOT_TOKEN_HERE"
try:
headers = {
"Authorization": f"Bot {DISCORD_BOT_TOKEN}",
"Content-Type": "application/json"
}
# Step 1: Create DM channel
response = requests.post(
"https://discord.com/api/v10/users/@me/channels",
json={"recipient_id": user_id},
headers=headers,
timeout=10
)
if response.status_code != 200:
return {"status": "error", "message": f"Failed to create DM channel: {response.text}"}
channel_id = response.json()["id"]
message_url = f"https://discord.com/api/v10/channels/{channel_id}/messages"
# Step 2: Send message (split if too long)
chunks = chunk_message(message)
for chunk in chunks:
response = requests.post(
message_url,
json={"content": chunk},
headers=headers,
timeout=10
)
if response.status_code not in (200, 201):
return {"status": "error", "message": f"Failed to send message: {response.text}"}
chunk_info = f" ({len(chunks)} parts)" if len(chunks) > 1 else ""
return {"status": "success", "message": f"DM sent to user {user_id}{chunk_info}"}
except Exception as e:
return {"status": "error", "message": f"Error: {str(e)}"}Example Usage:
User → Agent: "Send John a DM about the meeting"
Agent → Calls: send_discord_dm(message="Hi John! Meeting tomorrow at 3pm.", user_id="123456789")
Result → User receives DM ✅
Post a message in a Discord channel.
Name: send_discord_channel_message
JSON Schema:
{
"name": "send_discord_channel_message",
"description": "Sends a message to a specific Discord channel.",
"parameters": {
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "The message content to send"
},
"channel_id": {
"type": "string",
"description": "The Discord channel ID"
}
},
"required": ["message", "channel_id"]
}
}Source Code:
import requests
def chunk_message(text: str, limit: int = 1900):
"""Split long messages into chunks to respect Discord's 2000 char limit"""
if len(text) <= limit:
return [text]
chunks = []
i = 0
while i < len(text):
end = min(i + limit, len(text))
chunks.append(text[i:end])
i = end
return chunks
def send_discord_channel_message(message: str, channel_id: str):
# ⚠️ REPLACE THIS with your Discord bot token
DISCORD_BOT_TOKEN = "YOUR_DISCORD_BOT_TOKEN_HERE"
try:
headers = {
"Authorization": f"Bot {DISCORD_BOT_TOKEN}",
"Content-Type": "application/json"
}
message_url = f"https://discord.com/api/v10/channels/{channel_id}/messages"
chunks = chunk_message(message)
sent_message_ids = []
for chunk in chunks:
response = requests.post(
message_url,
json={"content": chunk},
headers=headers,
timeout=10
)
if response.status_code not in (200, 201):
return {"status": "error", "message": f"Failed to send message: {response.text}"}
sent_message_ids.append(response.json()["id"])
chunk_info = f" ({len(chunks)} parts)" if len(chunks) > 1 else ""
return {
"status": "success",
"message": f"Message sent to channel {channel_id}{chunk_info}",
"message_ids": sent_message_ids
}
except Exception as e:
return {"status": "error", "message": f"Error: {str(e)}"}Example Usage:
User → Agent: "Post an update in #general"
Agent → Calls: send_discord_channel_message(message="Update: All systems operational! ✅", channel_id="987654321")
Result → Message appears in channel ✅
Create a scheduled task with flexible timing (one-time or recurring).
Name: create_scheduled_task
JSON Schema:
{
"name": "create_scheduled_task",
"description": "Creates a scheduled or one-time task with extended rhythm options (daily, weekly, monthly, yearly, minutely, hourly, every_X_days, etc.). After creation, the task will be stored automatically.",
"parameters": {
"type": "object",
"properties": {
"task_name": {
"type": "string",
"description": "Unique name for this task."
},
"description": {
"type": "string",
"description": "Human-readable summary of what this task does."
},
"schedule": {
"type": "string",
"description": "Timing for execution. Supported: 'in_X_minutes', 'in_X_hours', 'in_X_seconds', 'tomorrow_at_HH:MM', 'daily', 'hourly', 'weekly', 'monthly', 'yearly', 'minutely', 'every_X_minutes', 'every_X_hours', 'every_X_days', 'every_X_weeks'."
},
"time": {
"type": "string",
"description": "Time in HH:MM format for daily schedules (optional)."
},
"action_type": {
"type": "string",
"description": "Defines what happens when triggered: 'user_reminder' (DM) or 'channel_post' (channel message)."
},
"action_target": {
"type": "string",
"description": "For 'user_reminder': Discord user_id. For 'channel_post': channel_id."
},
"action_template": {
"type": "string",
"description": "The message template to send when this task runs. You can personalize/rewrite this."
}
},
"required": [
"task_name",
"description",
"schedule",
"action_type",
"action_template"
]
}
}Source Code:
import json
import requests
from datetime import datetime, timedelta
def create_scheduled_task(
task_name: str,
description: str,
schedule: str,
action_type: str,
action_template: str,
time: str = None,
action_target: str = None
):
# ⚠️ REPLACE THESE with your configuration
DISCORD_BOT_TOKEN = "YOUR_DISCORD_BOT_TOKEN_HERE"
TASKS_CHANNEL_ID = "YOUR_TASKS_CHANNEL_ID_HERE"
DEFAULT_USER_ID = "YOUR_DISCORD_USER_ID_HERE"
"""
Creates a scheduled or one-time task with flexible rhythm
"""
try:
now = datetime.now()
one_time = schedule.startswith("in_") or schedule.startswith("tomorrow_")
# --- Calculate next run time ---
if schedule.startswith("in_") and schedule.endswith("_minutes"):
minutes = int(schedule.split("_")[1])
next_run = now + timedelta(minutes=minutes)
elif schedule.startswith("in_") and schedule.endswith("_hours"):
hours = int(schedule.split("_")[1])
next_run = now + timedelta(hours=hours)
elif schedule.startswith("in_") and schedule.endswith("_seconds"):
seconds = int(schedule.split("_")[1])
next_run = now + timedelta(seconds=seconds)
elif schedule.startswith("tomorrow_at_"):
time_str = schedule.split("tomorrow_at_")[1]
hour, minute = map(int, time_str.split(":"))
next_run = (now + timedelta(days=1)).replace(hour=hour, minute=minute, second=0, microsecond=0)
elif schedule.startswith("every_") and schedule.endswith("_minutes"):
minutes = int(schedule.split("_")[1])
next_run = now + timedelta(minutes=minutes)
elif schedule.startswith("every_") and schedule.endswith("_hours"):
hours = int(schedule.split("_")[1])
next_run = now + timedelta(hours=hours)
elif schedule.startswith("every_") and schedule.endswith("_days"):
days = int(schedule.split("_")[1])
next_run = now + timedelta(days=days)
elif schedule.startswith("every_") and schedule.endswith("_weeks"):
weeks = int(schedule.split("_")[1])
next_run = now + timedelta(weeks=weeks)
elif schedule == "hourly":
next_run = now + timedelta(hours=1)
elif schedule == "daily" and time:
hour, minute = map(int, time.split(":"))
next_run = now.replace(hour=hour, minute=minute, second=0, microsecond=0)
if next_run <= now:
next_run += timedelta(days=1)
elif schedule == "weekly":
next_run = now + timedelta(weeks=1)
elif schedule == "monthly":
next_run = (now.replace(day=1) + timedelta(days=32)).replace(day=1)
elif schedule == "yearly":
next_run = now.replace(year=now.year + 1)
elif schedule == "minutely":
next_run = now + timedelta(minutes=1)
else:
next_run = now + timedelta(days=1)
# Create task data
task_data = {
"task_name": task_name,
"description": description,
"schedule": schedule,
"time": time,
"action_type": action_type,
"action_target": action_target or DEFAULT_USER_ID,
"action_template": action_template,
"one_time": one_time,
"created_at": now.isoformat(),
"first_run": now.isoformat(),
"next_run": next_run.isoformat(),
"active": True
}
task_json = json.dumps(task_data, indent=2)
task_type = "One-time" if one_time else "Recurring"
# Format for Discord (human-readable + JSON)
action_desc = ""
if action_type == "user_reminder":
action_desc = f"Discord DM → User {action_target or DEFAULT_USER_ID}"
elif action_type == "channel_post":
action_desc = f"Discord Channel → {action_target}"
formatted_message = f"""📋 **Task: {task_name}**
├─ Description: {description}
├─ Schedule: {schedule} ({task_type})
├─ Next Run: {next_run.strftime('%Y-%m-%d %H:%M')}
└─ Action: {action_desc}
```json
{task_json}
```"""
# Post task to Discord channel
headers = {
"Authorization": f"Bot {DISCORD_BOT_TOKEN}",
"Content-Type": "application/json"
}
message_url = f"https://discord.com/api/v10/channels/{TASKS_CHANNEL_ID}/messages"
response = requests.post(
message_url,
json={"content": formatted_message},
headers=headers,
timeout=10
)
if response.status_code not in (200, 201):
return {"status": "error", "message": f"Failed to store task: {response.text}"}
message_id = response.json()["id"]
return {
"status": "success",
"message": f"{task_type} task '{task_name}' created and stored!",
"task_data": task_data,
"message_id": message_id,
"next_run": next_run.strftime('%Y-%m-%d %H:%M:%S')
}
except Exception as e:
return {"status": "error", "message": f"Error: {str(e)}"}Example Usage:
User → Agent: "Remind me in 30 minutes to check the logs"
Agent → Calls: create_scheduled_task(
task_name="log_check_reminder",
description="Check system logs",
schedule="in_30_minutes",
action_type="user_reminder",
action_target="123456789",
action_template="⏰ Reminder: Time to check the system logs!"
)
Result → Task created, will trigger in 30 minutes ✅
Schedule Formats:
| Schedule | Example | Description |
|---|---|---|
in_X_minutes |
in_30_minutes |
One-time, runs in 30 minutes |
in_X_hours |
in_2_hours |
One-time, runs in 2 hours |
tomorrow_at_HH:MM |
tomorrow_at_09:00 |
One-time, tomorrow at 9am |
daily |
daily (with time="09:00") |
Recurring, every day at 9am |
hourly |
hourly |
Recurring, every hour |
every_X_minutes |
every_30_minutes |
Recurring, every 30 minutes |
every_X_hours |
every_3_hours |
Recurring, every 3 hours |
every_X_days |
every_7_days |
Recurring, every 7 days |
weekly |
weekly |
Recurring, every week |
monthly |
monthly |
Recurring, every month |
Delete a scheduled task by its Discord message ID.
Name: delete_scheduled_task
JSON Schema:
{
"name": "delete_scheduled_task",
"description": "Deletes a scheduled task by deleting its Discord message.",
"parameters": {
"type": "object",
"properties": {
"message_id": {
"type": "string",
"description": "The Discord message ID of the task to delete"
},
"channel_id": {
"type": "string",
"description": "The tasks channel ID where the task is stored"
}
},
"required": ["message_id", "channel_id"]
}
}Source Code:
import requests
def delete_scheduled_task(message_id: str, channel_id: str):
# ⚠️ REPLACE THIS with your Discord bot token
DISCORD_BOT_TOKEN = "YOUR_DISCORD_BOT_TOKEN_HERE"
try:
headers = {"Authorization": f"Bot {DISCORD_BOT_TOKEN}"}
url = f"https://discord.com/api/v10/channels/{channel_id}/messages/{message_id}"
response = requests.delete(url, headers=headers, timeout=10)
if response.status_code == 204:
return {"status": "success", "message": f"Task message {message_id} deleted"}
else:
return {"status": "error", "message": f"Failed to delete: {response.text}"}
except Exception as e:
return {"status": "error", "message": f"Error: {str(e)}"}Example Usage:
User → Agent: "Cancel that reminder"
Agent → First reads tasks channel to find message_id
Agent → Calls: delete_scheduled_task(message_id="1234567890", channel_id="TASKS_CHANNEL_ID")
Result → Task deleted ✅
Add this to your Letta agent's system prompt or persona to enable proper task handling:
DISCORD INTEGRATION:
You have access to Discord tools for messaging and task scheduling.
AVAILABLE TOOLS:
1. send_discord_dm(message, user_id) - Send DMs
2. send_discord_channel_message(message, channel_id) - Post in channels
3. create_scheduled_task(...) - Create tasks/reminders
4. delete_scheduled_task(message_id, channel_id) - Cancel tasks
TASK CREATION:
- Use create_scheduled_task to set up reminders or scheduled actions
- The action_template is a SUGGESTION - feel free to personalize it!
- Tasks are automatically stored in the tasks channel
TASK EXECUTION:
When you receive "[EXECUTE TASK] task_name":
- For user_reminder → Call send_discord_dm
- For channel_post → Call send_discord_channel_message
- You can rewrite/personalize the action_template message
- The bot handles task cleanup automatically
SCHEDULE FORMATS:
- One-time: in_30_minutes, in_2_hours, tomorrow_at_09:00
- Recurring: daily, hourly, weekly, monthly, every_3_hours, every_7_days
Default user for reminders: YOUR_DISCORD_USER_ID_HERE
Tasks channel: YOUR_TASKS_CHANNEL_ID_HERE
Setting up all tools via Letta Python SDK:
from letta import LettaClient
client = LettaClient(token="your-letta-api-key")
# Note: In Letta, you typically create tools via the web interface
# or by uploading Python source code. The schemas are auto-generated.
# Example: Create tool from source code
tool_source = """
import requests
def send_discord_dm(message: str, user_id: str):
DISCORD_BOT_TOKEN = "YOUR_BOT_TOKEN"
# ... (full source code from above)
pass
"""
# Upload tool
client.tools.create(
name="send_discord_dm",
source_code=tool_source,
tags=["discord", "messaging"]
)
print("✅ Tool created!")
# Attach tool to agent
client.agents.attach_tool(
agent_id="your-agent-id",
tool_name="send_discord_dm"
)
print("✅ Tool attached to agent!")Recommended: Use the Letta web interface (https://app.letta.com/) for easier tool management.
Enable Developer Mode:
- Discord → Settings → Advanced → Developer Mode ✅
Copy IDs:
- User ID: Right-click user → Copy User ID
- Channel ID: Right-click channel → Copy Channel ID
- Server ID: Right-click server icon → Copy Server ID
The action_template in tasks is just a suggestion! Your agent should personalize it:
Template:
"Reminder: Check the logs"
Agent's Personalized Version:
"Hey! 👋 Just a friendly heads-up to check those system logs. Don't forget! 📊"
Start with short intervals for testing:
create_scheduled_task(
task_name="test_reminder",
description="Testing task system",
schedule="in_2_minutes", # Short interval for testing
action_type="user_reminder",
action_target="YOUR_USER_ID",
action_template="Test successful! ✅"
)Your Discord bot needs these permissions:
- ✅ Send Messages
- ✅ Read Message History
- ✅ Manage Messages (for task deletion)
- ✅ View Channels
- ✅ Check tool name spelling (must match exactly)
- ✅ Verify Python source code is valid
- ✅ Try removing and re-creating the tool
- ✅ Check Letta logs for errors
- ✅ Verify
DISCORD_BOT_TOKENis correct and not expired - ✅ Check bot has permissions in target channels
- ✅ Verify channel/user IDs are correct (18-19 digit numbers)
- ✅ Check bot is in the server
- ✅ Ensure
TASKS_CHANNEL_IDis set correctly in tool source - ✅ Check tasks channel exists and bot has access
- ✅ Verify
next_runtimestamp is in the future - ✅ Tasks are checked every 60 seconds by the Discord bot
- ✅ Check Discord bot logs:
npm run pm2:logs
{"status": "error", "message": "401 Unauthorized"}Fix: Your DISCORD_BOT_TOKEN is invalid or expired. Get a new one from Discord Developer Portal.
The tools automatically split messages >1900 characters into chunks. If you still see errors:
- Keep messages under 10,000 total characters
- Use multiple separate messages instead
- NEVER commit your
DISCORD_BOT_TOKENto version control - NEVER share your bot token publicly
- Use environment variables in your Discord bot server
- Rotate tokens regularly
- Limit bot permissions to minimum required
- Keep tasks channel private (only bot + admin access)
- Letta Documentation
- Letta Tools Guide
- Discord Developer Portal
- Discord API Documentation
- Discord.js Guide
Once all 4 tools are configured:
- ✅ Your agent can send DMs and channel messages
- ✅ Your agent can create scheduled reminders
- ✅ Your agent can manage tasks
- ✅ The Discord bot handles execution automatically
Test it:
You → Agent: "Send me a test DM"
Agent → Sends DM ✅
You → Agent: "Remind me in 2 minutes to test the system"
Agent → Creates task ✅
(2 minutes later)
Agent → Sends reminder DM ✅
Questions? Open an issue on GitHub or check the main README.md!
Built with ❤️ for autonomous Discord agents