Letta Companion Architecture

Setting Up an AI Companion as a Discord Bot Through Letta

A practical, privacy-conscious guide for building a Discord companion bot with a persistent Letta agent as its memory-backed mind.

Discord bot interface Letta API brain Owner-only private chat Report DMs allowed

# 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:

text
Discord user
   ↓
Discord message / command / mention
   ↓
Python Discord bot
   ↓
Prompt construction + guardrails
   ↓
Letta API
   ↓
Letta agent reply
   ↓
Python Discord bot
   ↓
Discord channel or DM response

The 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:

  1. A Discord account with access to the server where the bot will live.
  2. A Discord Developer Portal application.
  3. A Discord bot token.
  4. A Letta account / server / API endpoint.
  5. A Letta agent ID.
  6. 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.py
  • python-dotenv
  • requests or httpx
  • optionally aiohttp

Example install:

powershell
python -m pip install discord.py python-dotenv requests

If you use a virtual environment:

powershell
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install discord.py python-dotenv requests

4. Create the Discord Bot

4.1 Create an Application

  1. Go to: https://discord.com/developers/applications
  2. Click New Application.
  3. Give it a name, e.g. Ryuu K.
  4. Open the application.

4.2 Create the Bot User

  1. Go to Bot in the left sidebar.
  2. Click Add Bot if needed.
  3. Set the bot username and avatar.
  4. Copy the bot token.
  5. Store the token in .env, never directly in code.

Example:

env
DISCORD_TOKEN=your_discord_bot_token_here

4.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:

  • bot
  • applications.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:

env
LETTA_BASE_URL=https://api.letta.com
LETTA_API_KEY=your_letta_key_here
LETTA_AGENT_ID=agent-id-here

Depending 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:

text
POST /v1/agents/{agent_id}/messages

The bot sends a user message to the Letta agent and extracts the assistant reply.


6. Suggested Project Structure

Example:

text
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.log

bot.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

env
# 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=8

8. Important Privacy Design

For an AI companion, privacy is not optional.

You should decide:

  1. Who can talk to the bot privately?
  2. Who can ask for summaries?
  3. Can the bot DM non-owner users?
  4. Can non-owner users DM-chat with the bot?
  5. 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:

python
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:

text
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:

  1. Message arrives in DM.
  2. Check is_owner_user(message.author).
  3. If false, ignore.
  4. If true, send message to Letta.
  5. Reply in DM.

10.2 Public Mention Reply

Users can mention the bot in a channel.

Flow:

  1. Message includes bot mention.
  2. Check cooldowns.
  3. Check public Letta daily budget.
  4. Build public-safe prompt.
  5. Send to Letta.
  6. Reply publicly.

10.3 TL;DR Command

Example:

text
!tldr 100

Flow:

  1. Fetch recent channel history.
  2. Format transcript using display names/nicknames.
  3. Build summarization prompt with privacy guardrail.
  4. Send to Letta.
  5. DM the report to the requester if DM_TLDR=true.
  6. Post a short acknowledgement in the channel.

10.4 Mentions Catch-Up

Example:

text
!mentions 200

Flow:

  1. Fetch recent messages.
  2. Filter for messages mentioning the requester.
  3. Summarize relevant mentions.
  4. DM the requester.

Example:

text
!links 100

Flow:

  1. Fetch recent messages.
  2. Extract URLs.
  3. Summarize what was shared.
  4. DM the requester.

10.6 Vibe Check

Example:

text
/vibecheck

Flow:

  1. Fetch recent channel messages.
  2. Ask Letta for a public-safe tone read.
  3. 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:

env
AMBIENT_COMMENTS=true
AMBIENT_CHANNEL_ID=1318648275217285182
AMBIENT_MIN_GAP_MINUTES=60
AMBIENT_MAX_PER_DAY=6
AMBIENT_RANDOM_CHANCE=0.50

Prompt rule:

text
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:

text
[14:02] Arthur: message here
[14:03] Felix: message here

Avoid forcing raw usernames if a server display name exists.

In discord.py, useful fields include:

python
member.display_name
user.global_name
user.name

Recommended helper:

python
def display_label(user):
    return getattr(user, "display_name", None) or getattr(user, "global_name", None) or user.name

Then instruct Letta:

text
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:

python
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 choice

Use different buckets for:

  • TL;DR acknowledgements
  • mention acknowledgements
  • owner check-ins
  • ambient comments
  • prompt seeds/styles

Also tell Letta directly:

text
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:

text
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:

text
ryuu_discord_bot.lock

On startup:

  1. Check if lock file exists.
  2. Read PID.
  3. If PID is alive, exit.
  4. If PID is stale, remove lock.
  5. Write current PID.
  6. On shutdown, remove lock.

This prevents double-response bugs.


15. Running the Bot on Windows

From PowerShell:

powershell
cd "C:\Users\EliteBook\OneDrive\Ryuu\GPT docs\Ryuu_RAG\discord-rag-bot"
python .\bot.py

For hidden background launch:

powershell
$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.log

Check if it is running:

powershell
Get-CimInstance Win32_Process |
  Where-Object { $_.CommandLine -like '*bot.py*' -and ($_.Name -match 'python|powershell|pwsh|cmd') } |
  Select-Object ProcessId,ParentProcessId,Name,CommandLine |
  Format-List

Check logs:

powershell
Get-Content .\ryuu_discord_bot.out.log -Tail 30
Get-Content .\ryuu_discord_bot.err.log -Tail 30

16. Common Problems and Fixes

16.1 Bot Appears Offline

Check:

  • Is python bot.py running?
  • Is the lock file stale?
  • Did the bot token change?
  • Did Discord gateway disconnect?
  • Did the machine sleep?

Fix:

powershell
Remove-Item .\ryuu_discord_bot.lock -Force -ErrorAction SilentlyContinue
python .\bot.py

16.2 Duplicate Replies

Likely cause: multiple bot processes are running.

Fix:

  • kill all python bot.py processes
  • 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:

python
if isinstance(message.channel, discord.DMChannel):
    if not is_owner_user(message.author):
        return

This still allows the bot to send one-way report DMs if your command handlers explicitly DM command requesters.


17. Suggested Commands

Public commands:

text
!help
!tldr 100
!mentions 100
!links 100
/vibecheck
/prompt theme

Owner-only commands:

text
!checkins status
!checkins on
!checkins off
!checkins pause
!checkins test

!ambient status
!ambient on
!ambient off
!ambient pause
!ambient test

Optional owner tools:

text
!replywhere 200
!draft
!back
!away

18. Safety Recommendations

For a companion bot, especially one with a personal identity/persona:

  1. Separate public and private modes.
  2. Use privacy guardrails on every public Letta call.
  3. Do not let the bot expose private owner context.
  4. Rate-limit public replies.
  5. Limit ambient comments to specific channels.
  6. Let the bot choose to stay quiet.
  7. Avoid sexual/romantic intensity with non-owner users.
  8. Prefer short public replies.
  9. Log errors locally, not in Discord.
  10. Keep .env secrets 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

A good balanced AI companion Discord policy:

text
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:

text
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.