← Back to blog

How I stopped our Discord AI from hallucinating

Illia Cherviakovby Illia C.9 min read
AI AgentDiscordCommunityLLM GuardrailsWeb3
How I stopped our Discord AI from hallucinating

I run a Discord community called Inner Circle. It's a creator cohort, dozens of active members, one admin. Me.

Every day the same questions land in the chat. What's my rank? How many points do I have? Why does that guy have more than me? What am I missing? When does the leaderboard reset? How does the bonus work? None of them are hard. All of them are answerable from data we already track. But answering them by hand, one at a time, all day, does not scale past one person. And it was very much one person.

So I built an AI assistant that answers them for me. It lives in Discord as a slash command, /ask, plus a reply trigger so members can quote any message and ask about it. A member types /ask what's my rank and what am I still missing, and it answers, in-channel, in a couple of seconds, from real cohort data.

That part is easy. The interesting part is everything I did to stop it from lying.

Why a factual assistant, not a chatbot

The first decision was scope. I did not want a "coach" that tells you how to improve your posts. I wanted a boring, factual clerk that knows the numbers cold: your standing, your tasks, your submissions, the leaderboard, how you compare to another member, whether you can still reach #1 if you finish everything.

Boring is the point. A community assistant that occasionally invents a rule or a number is worse than no assistant, because now people are arguing with the bot about their payout. In a cohort where points map to real rewards, a hallucinated number isn't a cute mistake. It's a support ticket and a trust problem.

So the goal was narrow and clear: deflect the repetitive "what's my status" load off me and onto something that is right every single time.

The guardrail is not "please behave"

Here's the thing most people get wrong when they bolt an LLM onto their product. They write a long system prompt that says "only answer questions about X, never reveal Y, don't make things up," and they call that a guardrail. It isn't. It's a suggestion. A determined user, or just an unlucky phrasing, walks right through it.

The real guardrail is a capability boundary. You don't ask the model to behave. You make misbehaving impossible.

The assistant never gets a database connection. It can't write a query. All it can do is call functions from a fixed list I wrote: get_my_standing, get_my_tasks, get_leaderboard, compare_to, and about a dozen more. Every one is read-only, and every one returns only fields that are already public in the community. The "who am I" part is never up to the model. When someone asks about their standing, the server injects the caller's identity into the tool call. The model never resolves "me," so it can't be tricked into pulling someone else's private data. There is no private data in reach to begin with.

Once the boundary is built like that, the system prompt stops being the thing standing between a user and your database. It's just there to make the answers read well.

GUARDRAIL: PROMPT vs BOUNDARY A SYSTEM PROMPT "only answer about X, never make things up" a suggestion. a user walks right through it. A CAPABILITY BOUNDARY no DB access. fixed read-only tools. the server does the math. misbehaving is physically impossible.
The real guardrail isn't a paragraph. It's what the model physically can't reach.

LLMs are bad at math, so I stopped asking them to do it

The second lesson cost me a couple of embarrassing production bugs before it sank in.

Early on, someone asked the assistant to compare them to another member. It called a tool, got two profiles back, and then confidently explained that the other person was ahead because of "biweekly tasks that are worth more points." There are no biweekly tasks. It made them up to fill the gap in a sentence that felt like it needed a reason.

Another time it answered "you've missed 0 tasks" when the member had actually missed most of them, because the tasks were archived and the model only saw the visible slice.

The fix in both cases was the same, and it's the rule I now apply everywhere: the model does not compute, it narrates. Any real arithmetic, comparing two people, adding up a points ceiling, counting missed tasks, happens in server code that I can test. The tool hands back a finished, correct breakdown, and the model's only job is to say it in plain language. If the server can't produce a clean breakdown, the tool returns a flag that tells the model to say "I can show you the totals but I can't break down the gap" instead of inventing one.

Temperature is set to zero. The prompt says, in as many words, never invent a number, answer only from what the tools returned. Between those two things and the narrate-don't-compute rule, the "creative filling" mostly disappears.

The small stuff that makes it usable

A few details did more for the experience than the model choice ever did.

Most questions get answered with zero tool calls, because before the model even runs I pre-load the caller's rank and points and the top of the leaderboard into the context. The common questions resolve instantly and cheaply.

Program rules live in a set of short, curated notes, and the model can pull exactly one relevant chunk on demand. I learned the hard way that pasting the entire rulebook into every prompt makes it hallucinate more, not less. Less context, better grounded.

Our community has two currencies with confusingly similar names, and the model kept blending them. So the prompt carries a hard glossary that defines each one and forbids treating them as the same thing. That single paragraph killed a whole category of wrong answers.

And because the model I use follows formatting instructions loosely, there's a deterministic cleanup step after generation that strips anything Discord renders badly. The model can misbehave on formatting all it wants. The user never sees it.

What it actually changed

The assistant answers the questions I used to answer. That's it, and that's everything. A member checks their own standing without pinging me. Someone settles a "who's ahead" argument by asking the bot instead of me. New members get program answers at 3am in a timezone I'm asleep in.

It let one person run a community that would otherwise need a small support team, without anyone feeling like they're talking to a wall. The bot handles the repetitive questions so the human energy goes where it actually keeps a community alive. Every answer is grounded in real data, so nobody's arguing with a made-up number.

It's also not the only AI tool I built for that community. The other one reviews a member's draft in my voice before they post it, so weak posts get fixed before they ever reach my review queue.

If you're bolting an LLM onto something

Three things I'd tell anyone building this:

Make the guardrail a capability boundary, not a paragraph of instructions. If the model physically can't reach the thing, you don't have to trust it not to.

Never let the model do arithmetic you care about. Compute it in code, hand it the answer, let it narrate. Give it an honest way to say "I don't know" instead of a reason to guess.

Feed it less, not more. The instinct to dump all your context into the prompt is exactly backwards.

This is the kind of thing I build when a community or a product needs to scale past what one person can answer by hand. It's the same reason I'd rather build my own tools than rent them. If that's a problem you have, book a call and tell me about it.

Frequently asked questions

What is an AI community manager?

An AI community manager is a bot that handles the repetitive questions a human would otherwise answer all day: rank, points, tasks, leaderboard standing. Mine runs as a Discord slash command and answers from real cohort data in seconds, so one person can run a community that would otherwise need a small support team.

How do you stop an AI assistant from hallucinating?

Make the guardrail a capability boundary, not a polite instruction. The model never touches the database; it can only call read-only functions I wrote. It never does arithmetic either. The server computes, the model narrates. Temperature is zero, and it is told to answer only from what the tools returned.

Should an LLM do the math in your app?

No. LLMs are bad at arithmetic and will invent a reason to fill a gap in a sentence. Any real calculation, comparing two members or adding up a points ceiling, happens in server code you can test. The tool hands back a finished, correct breakdown, and the model's only job is to say it in plain language.