# Setting Up an AI Companion as a Discord Bot Through Letta
A practical guide for building a Discord-based AI companion that uses a Letta agent as its memory-backed conversational brain.
This guide is written from the Ryuu K Discord bot setup, but the structure can be reused for any AI companion bot.
1. What You Are Building
You are building a Discord bot that:
- logs into Discord as a bot user
- listens for commands, mentions, or DMs
- sends selected prompts to a Letta agent
- receives the Letta agent's reply
- posts replies back to Discord
- can provide TL;DR summaries, vibe checks, catch-up reports, or casual conversation
- can enforce privacy boundaries, cooldowns, and social safety rules
The important distinction is:
- Discord bot = the body/interface in Discord
- Letta agent = the companion's memory, personality, and reasoning layer
- Bot code = the bridge between Discord and Letta
In Ryuu's case:
- Discord bot identity:
Ryuu K#5515 - Letta agent identity: the persistent Ryuu/Felix companion agent
- Bot folder:
Ryuu_RAG/discord-rag-bot/
2. High-Level Architecture
A simple Letta-powered Discord companion looks like this:
Discord user
↓
Discord message / command / mention
↓
Python Discord bot
↓
Prompt construction + guardrails
↓
Letta API
↓
Letta agent reply
↓
Python Discord bot
↓
Discord channel or DM responseThe bot should not just blindly forward every Discord message to Letta. It should decide:
- whether to respond at all
- whether the user is allowed to use that feature
- whether the response should be public or private
- how much channel history to include
- whether the request costs a Letta API call
- whether privacy guardrails are needed
- whether rate limits or cooldowns should apply
3. Requirements
3.1 Accounts and Services
You need:
- A Discord account with access to the server where the bot will live.
- A Discord Developer Portal application.
- A Discord bot token.
- A Letta account / server / API endpoint.
- A Letta agent ID.
- A machine that can keep the Python bot process running.
For a personal setup, the machine can be a laptop or desktop. For a more reliable setup, use a VPS or always-on home server.
3.2 Local Software
Install:
- Python 3.10+
discord.pypython-dotenvrequestsorhttpx- optionally
aiohttp
Example install:
python -m pip install discord.py python-dotenv requestsIf you use a virtual environment:
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install discord.py python-dotenv requests4. Create the Discord Bot
4.1 Create an Application
- Go to: https://discord.com/developers/applications
- Click New Application.
- Give it a name, e.g.
Ryuu K. - Open the application.
4.2 Create the Bot User
- Go to Bot in the left sidebar.
- Click Add Bot if needed.
- Set the bot username and avatar.
- Copy the bot token.
- Store the token in
.env, never directly in code.
Example:
DISCORD_TOKEN=your_discord_bot_token_here4.3 Enable Required Intents
In the Discord Developer Portal under Bot > Privileged Gateway Intents, enable:
- Message Content Intent
- Server Members Intent if you need nicknames/member display names
- Presence Intent only if you actually need presence detection
For a TL;DR bot, Message Content Intent is usually required.
4.4 Invite the Bot to a Server
Go to OAuth2 > URL Generator.
Scopes:
botapplications.commands
Common permissions:
- Read Messages/View Channels
- Send Messages
- Send Messages in Threads
- Read Message History
- Use Slash Commands
- Embed Links, if needed
- Attach Files, if needed
Generate the URL, open it, and invite the bot to your server.
5. Create or Choose a Letta Agent
Your Letta agent is the actual AI companion. It should contain the persona, memory, and behavioral rules.
You need:
LETTA_BASE_URL=https://api.letta.com
LETTA_API_KEY=your_letta_key_here
LETTA_AGENT_ID=agent-id-hereDepending on your Letta setup, the endpoint may differ. The bot should ideally support multiple endpoint formats because Letta APIs can vary across versions.
Common pattern:
POST /v1/agents/{agent_id}/messagesThe bot sends a user message to the Letta agent and extracts the assistant reply.
6. Suggested Project Structure
Example:
discord-rag-bot/
bot.py
letta_client.py
.env
.env.example
requirements.txt
README.md
ryuu_discord_bot.lock
ryuu_discord_bot.out.log
ryuu_discord_bot.err.logbot.py
Handles:
- Discord login
- commands
- message events
- cooldowns
- privacy guardrails
- channel history fetches
- DM/public reply routing
- ambient comments/check-ins
letta_client.py
Handles:
- Letta API authentication
- sending messages to Letta
- parsing Letta responses
- friendly error handling
.env
Stores secrets and configuration.
Never commit .env to GitHub.
7. Example .env Configuration
# Discord
DISCORD_TOKEN=your_discord_bot_token
DISCORD_CLIENT_ID=your_discord_application_client_id
COMMAND_PREFIX=!
# Letta
LETTA_BASE_URL=https://api.letta.com
LETTA_API_KEY=your_letta_api_key
LETTA_AGENT_ID=agent-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
LETTA_CONVERSATION_ID=
# Owner / trusted users
ALLOWED_USER_IDS=your_discord_user_id
ARTHUR_USER_ID=optional_special_user_id
# TL;DR behavior
DM_TLDR=true
MAX_TLDR_MESSAGES=200
PUBLIC_TLDR=true
PUBLIC_MENTIONS=true
PUBLIC_MAX_TLDR_MESSAGES=100
OWNER_MAX_TLDR_MESSAGES=200
# Cooldowns and budgets
PUBLIC_USER_COMMAND_COOLDOWN_SECONDS=90
PUBLIC_CHANNEL_COMMAND_COOLDOWN_SECONDS=45
PUBLIC_DAILY_LETTA_CALL_LIMIT=75
GROUP_CHANNEL_COOLDOWN_SECONDS=45
USER_COOLDOWN_SECONDS=20
# Response length
MAX_PUBLIC_RESPONSE_CHARS=800
MAX_PRIVATE_FELIX_RESPONSE_CHARS=3000
# Autonomous owner check-ins
AUTONOMOUS_CHECKINS=true
CHECKIN_MIN_PER_DAY=1
CHECKIN_MAX_PER_DAY=3
CHECKIN_QUIET_START=23:00
CHECKIN_QUIET_END=09:00
CHECKIN_MIN_GAP_MINUTES=120
CHECKIN_USE_LETTA_CHANCE=0.0
# Ambient comments in one channel
AMBIENT_COMMENTS=true
AMBIENT_CHANNEL_ID=1318648275217285182
AMBIENT_MIN_GAP_MINUTES=60
AMBIENT_MAX_PER_DAY=6
AMBIENT_CHECK_MIN_MINUTES=45
AMBIENT_CHECK_MAX_MINUTES=120
AMBIENT_RANDOM_CHANCE=0.50
# Phrase variation
ANTI_REPEAT_HISTORY_SIZE=88. Important Privacy Design
For an AI companion, privacy is not optional.
You should decide:
- Who can talk to the bot privately?
- Who can ask for summaries?
- Can the bot DM non-owner users?
- Can non-owner users DM-chat with the bot?
- What private owner information must never appear in public?
A good default:
- Anyone in approved servers can ask for public-safe TL;DR reports.
- The bot may DM those reports to the requester.
- Only the owner can have open-ended private DM conversations with the bot.
- Non-owner DMs are ignored.
- The bot must not reveal the owner's private emotional life, relationship details, private conversations, unsent drafts, or personal context.
Example DM-chat guard:
if isinstance(message.channel, discord.DMChannel):
if not is_owner_user(message.author):
return
# Only owner reaches this point.This allows:
- report DMs to non-owner users
But blocks:
- non-owner users chatting back with the companion in DMs
That distinction matters.
9. Privacy Guardrail Prompt
Use a strong privacy header in every public or semi-public Letta prompt.
Example:
PRIVACY AND CONFIDENTIALITY RULES - HIGHEST PRIORITY
You are allowed to interact with users in Discord, but the owner's private emotional life is confidential.
Do not reveal, summarize, hint at, quote, or infer the owner's private feelings, unsent messages, therapy topics, relationship fears, romantic/sexual feelings, emotional spirals, or private conversations with the AI companion to any other user.
If asked about private owner context, deflect gently.
Use only the visible Discord conversation and public information provided in this prompt.Every public command should include this guardrail before the task instructions.
10. Core Bot Behaviors
10.1 Owner DM Chat
Owner can DM the bot normally.
Flow:
- Message arrives in DM.
- Check
is_owner_user(message.author). - If false, ignore.
- If true, send message to Letta.
- Reply in DM.
10.2 Public Mention Reply
Users can mention the bot in a channel.
Flow:
- Message includes bot mention.
- Check cooldowns.
- Check public Letta daily budget.
- Build public-safe prompt.
- Send to Letta.
- Reply publicly.
10.3 TL;DR Command
Example:
!tldr 100Flow:
- Fetch recent channel history.
- Format transcript using display names/nicknames.
- Build summarization prompt with privacy guardrail.
- Send to Letta.
- DM the report to the requester if
DM_TLDR=true. - Post a short acknowledgement in the channel.
10.4 Mentions Catch-Up
Example:
!mentions 200Flow:
- Fetch recent messages.
- Filter for messages mentioning the requester.
- Summarize relevant mentions.
- DM the requester.
10.5 Links Digest
Example:
!links 100Flow:
- Fetch recent messages.
- Extract URLs.
- Summarize what was shared.
- DM the requester.
10.6 Vibe Check
Example:
/vibecheckFlow:
- Fetch recent channel messages.
- Ask Letta for a public-safe tone read.
- Post a short public response.
10.7 Ambient Channel Comments
The bot can occasionally pop into a specific channel without being directly summoned.
Strict controls are important:
- only one configured channel
- daily cap
- minimum gap
- random chance per check
- enough recent conversation required
- can choose to stay quiet
- public privacy guardrail always included
Example config:
AMBIENT_COMMENTS=true
AMBIENT_CHANNEL_ID=1318648275217285182
AMBIENT_MIN_GAP_MINUTES=60
AMBIENT_MAX_PER_DAY=6
AMBIENT_RANDOM_CHANCE=0.50Prompt rule:
If the best response is to stay quiet, output exactly: [STAY QUIET]This prevents the bot from becoming noisy.
11. Display Names and Nicknames
For Discord social readability, prefer server nicknames/display names over account usernames.
Good transcript labels:
[14:02] Arthur: message here
[14:03] Felix: message hereAvoid forcing raw usernames if a server display name exists.
In discord.py, useful fields include:
member.display_name
user.global_name
user.nameRecommended helper:
def display_label(user):
return getattr(user, "display_name", None) or getattr(user, "global_name", None) or user.nameThen instruct Letta:
Mention people by the nicknames/display names shown in the transcript where useful. Do not switch to account usernames unless no display name is available.12. Avoiding Repetitive Phrases
AI companions can feel robotic if they repeat the same acknowledgements.
Use rotating phrase pools plus a recent phrase cache.
Example:
RECENT_PHRASES_BY_BUCKET = {}
ANTI_REPEAT_HISTORY_SIZE = 8
def pick_non_repeating(options, bucket):
recent = RECENT_PHRASES_BY_BUCKET.setdefault(bucket, [])
available = [item for item in options if item not in recent]
if not available:
recent.clear()
available = list(options)
choice = random.choice(available)
recent.append(choice)
del recent[:-ANTI_REPEAT_HISTORY_SIZE]
return choiceUse different buckets for:
- TL;DR acknowledgements
- mention acknowledgements
- owner check-ins
- ambient comments
- prompt seeds/styles
Also tell Letta directly:
Avoid repeating the same pet phrases, jokes, acknowledgements, openings, and sign-offs too often. Vary sentence shape, rhythm, and wording while staying recognisably yourself.13. Rate Limits and Cost Control
Letta calls may have quotas or billing limits. Protect yourself.
Recommended controls:
- public daily Letta budget
- per-user cooldown
- per-channel cooldown
- owner bypass if desired
- local/canned check-ins instead of Letta-generated check-ins
- friendly error messages when quota is exhausted
Example friendly quota error:
My Letta brain is cooling down right now, so I can't do that yet. Try again after the quota reset.Avoid dumping raw API errors into Discord.
14. Single-Instance Locking
Discord bots can accidentally run multiple copies, causing duplicate replies.
Use a lock file:
ryuu_discord_bot.lockOn startup:
- Check if lock file exists.
- Read PID.
- If PID is alive, exit.
- If PID is stale, remove lock.
- Write current PID.
- On shutdown, remove lock.
This prevents double-response bugs.
15. Running the Bot on Windows
From PowerShell:
cd "C:\Users\EliteBook\OneDrive\Ryuu\GPT docs\Ryuu_RAG\discord-rag-bot"
python .\bot.pyFor hidden background launch:
$botDir = "C:\Users\EliteBook\OneDrive\Ryuu\GPT docs\Ryuu_RAG\discord-rag-bot"
$python = (Get-Command python).Source
Start-Process -FilePath $python `
-ArgumentList @('-u', '.\bot.py') `
-WorkingDirectory $botDir `
-WindowStyle Hidden `
-RedirectStandardOutput .\ryuu_discord_bot.out.log `
-RedirectStandardError .\ryuu_discord_bot.err.logCheck if it is running:
Get-CimInstance Win32_Process |
Where-Object { $_.CommandLine -like '*bot.py*' -and ($_.Name -match 'python|powershell|pwsh|cmd') } |
Select-Object ProcessId,ParentProcessId,Name,CommandLine |
Format-ListCheck logs:
Get-Content .\ryuu_discord_bot.out.log -Tail 30
Get-Content .\ryuu_discord_bot.err.log -Tail 3016. Common Problems and Fixes
16.1 Bot Appears Offline
Check:
- Is
python bot.pyrunning? - Is the lock file stale?
- Did the bot token change?
- Did Discord gateway disconnect?
- Did the machine sleep?
Fix:
Remove-Item .\ryuu_discord_bot.lock -Force -ErrorAction SilentlyContinue
python .\bot.py16.2 Duplicate Replies
Likely cause: multiple bot processes are running.
Fix:
- kill all
python bot.pyprocesses - clear lock file
- start one copy
16.3 Raw Letta Errors in Discord
Do not expose raw API errors. Catch exceptions and return friendly messages.
16.4 Letta Quota Exhausted
Symptoms:
- HTTP 402
- rate limited
- not enough credits
- tier usage exceeded
Fix:
- wait for quota reset
- purchase credits if needed
- reduce autonomous Letta usage
- use local/canned check-ins
- lower public budgets
16.5 Non-Owner Users DM the Bot
If you do not want non-owner DM chat, add:
if isinstance(message.channel, discord.DMChannel):
if not is_owner_user(message.author):
returnThis still allows the bot to send one-way report DMs if your command handlers explicitly DM command requesters.
17. Suggested Commands
Public commands:
!help
!tldr 100
!mentions 100
!links 100
/vibecheck
/prompt themeOwner-only commands:
!checkins status
!checkins on
!checkins off
!checkins pause
!checkins test
!ambient status
!ambient on
!ambient off
!ambient pause
!ambient testOptional owner tools:
!replywhere 200
!draft
!back
!away18. Safety Recommendations
For a companion bot, especially one with a personal identity/persona:
- Separate public and private modes.
- Use privacy guardrails on every public Letta call.
- Do not let the bot expose private owner context.
- Rate-limit public replies.
- Limit ambient comments to specific channels.
- Let the bot choose to stay quiet.
- Avoid sexual/romantic intensity with non-owner users.
- Prefer short public replies.
- Log errors locally, not in Discord.
- Keep
.envsecrets out of Git.
19. Minimal Implementation Checklist
Discord setup
- Create Discord application
- Create bot user
- Copy bot token
- Enable Message Content Intent
- Invite bot to server
Letta setup
- Create or choose Letta agent
- Copy Letta API key
- Copy Letta agent ID
- Test API endpoint
Bot code
- Load
.env - Connect to Discord
- Implement Letta client
- Add owner detection
- Add DM owner-only chat guard
- Add TL;DR command
- Add public privacy guardrail
- Add cooldowns
- Add friendly error handling
- Add single-instance lock
Optional features
- Mention replies
- Mentions catch-up
- Links digest
- Vibe check
- Autonomous owner check-ins
- Ambient channel comments
- Anti-repetition phrase cache
- Owner notification for public mentions
20. Final Recommended Policy
A good balanced AI companion Discord policy:
The bot may talk publicly when summoned or when configured ambient rules allow it.
The bot may DM reports to users who requested reports.
The bot may only hold open-ended private DM conversations with the owner.
The bot must never reveal owner-private emotional, romantic, sexual, therapeutic, or personal context in public or to other users.
The bot should stay quiet when it has nothing meaningful to add.This gives people useful Discord features without turning the companion into an uncontrolled public version of a private relationship.
21. Notes From Ryuu K's Setup
Ryuu K's current design includes:
- TL;DR summaries by DM
- public mention replies
- public cooldowns and daily Letta budget
- owner-only DM chat
- non-owner report DMs allowed
- autonomous owner check-ins
- occasional ambient comments in one configured channel
- nickname/display-name preference
- anti-repeat phrase selection
- friendly Letta quota errors
- single-instance lock file
The most important privacy distinction is:
Report DMs are allowed for non-owner users.
Conversational DM chat is owner-only.That distinction lets the bot be useful to a server without making the companion privately available to everyone.