feat: Discord bot — per-session rules, user identity, and DISCORD_CLAUDE.md

- Add DISCORD_CLAUDE.md as the Discord bot's dedicated system prompt,
  replacing the main CLAUDE.md for bot sessions. Covers: no-interactive
  rules, Discord user authorization, vault/remediation guidance, /save
  after every task, and formatting rules for Discord.

- config.py: add discord_system_prompt field (default: projects/discord-bot/
  DISCORD_CLAUDE.md, overridable via env var).

- client.py: _load_system_prompt() now loads discord_system_prompt path
  with fallback to CLAUDE.md if file is missing.

- message_handler.py: inject [DISCORD_CONTEXT] header into every agent
  message containing Discord username, display name, user ID, channel,
  and guild so the agent always knows who is asking.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 10:11:36 -07:00
parent 377ab3a63f
commit 020ae0cc1c
4 changed files with 206 additions and 6 deletions

View File

@@ -29,21 +29,37 @@ class MessageHandler:
if message.author == self.bot.user:
return
content = message.content
# Strip the bot mention to get the raw user text.
user_text = message.content
for mention in message.mentions:
if mention == self.bot.user:
content = content.replace(f"<@{mention.id}>", "").replace(
user_text = user_text.replace(f"<@{mention.id}>", "").replace(
f"<@!{mention.id}>", ""
).strip()
if not content and not message.attachments:
if not user_text and not message.attachments:
await message.reply("Hey! How can I help?")
return
# Build caller-identity header so the agent always knows who is asking.
author = message.author
display = getattr(author, "display_name", author.name)
guild_name = message.guild.name if message.guild else "DM"
channel_name = getattr(message.channel, "name", "unknown")
discord_ctx = (
"[DISCORD_CONTEXT]\n"
f"User: @{author.name}"
+ (f" (display: {display})" if display != author.name else "")
+ f" | ID: {author.id}\n"
f"Channel: #{channel_name} | Guild: {guild_name}\n"
"[/DISCORD_CONTEXT]\n\n"
)
content = (discord_ctx + user_text).strip()
if isinstance(message.channel, discord.Thread):
thread = message.channel
else:
name = self._thread_name(content) if content else "Attachment"
name = self._thread_name(user_text) if user_text else "Attachment"
thread = await message.create_thread(
name=name,
auto_archive_duration=1440,