Art by Men (@signal_1120)
I spent a lot of time this month making a Discord bot platform called Rozacord. Originally, I had a Discord bot I called Nanobot that I wrote for my IRL friend server. I actively developed on it for a while, added a handful of features to give the place a little more personality, and more or less forgot about it for two years. As time passed though, features broke, maintaining the hacked-together and poorly-thought-out architecture became annoying and I slowly began disabling features rather than maintaining them.
To make matters worse, another server I was in also requested use of this bot, which meant I had to fork the codebase so I could enable/disable the features that community wanted and change the many hardcoded responses to be more in line with that community. When a third server, with over 3,000 members wanted it, I realized the old architecture had become untenable and it was time for a rewrite. Thus Rozacord was born.
Rozacord is less of a bot and more of a platform which you can implement a bot on top of. Each instance of the bot is defined by a single monolithic JSON file which is centrally managed by the bot daemon. Then, to implement behavior, you write a Rozacord Module which can hook into various Discord API calls plumbed through the central daemon.
This approach has a number of advantages, but the biggest one by far is that because every bot instance lives in a separate JSON, you can trivially customize absolutely everything about the bot on a per-instance level. You can enable/disable features, edit response tables, and even control the appearance of the bot all from its instance JSON.
But enough about that. Let's talk about how I gave this thing a personality. I'm going to write this as a sort of crash course on how to do something like this yourself. I'm hoping that what I did here can be generalized.
This is obvious, but there are some decisions to make about how to approach it exactly. The most obvious, and indeed most common way is to let the bot write words. You might consider giving the bot a voice, even. However, this is relatively complicated, and there are ways of letting your bot "talk" to users without using words. These less obvious methods might give you better results depending on what you're going for.
The key thing I learned here is that humans are really good at communication. The most important way this is useful for our purposes is that, if you start placing restrictions on how users can communicate with each other, their brains will almost immediately adapt to those restrictions. Suppose, for example, you only let someone communicate by passing notes that either say "thumbs up" or "thumbs down". Anyone who talks to that person will almost immediately catch on to this fact, and an impromptu language will form with people interpreting "thumbs up" to mean a massive variety of things that are not actually the same as "agree". "Thumbs up" may expand to mean "Yes", "I approve", "That's true", "I agree with this", "I'm happy about this", etc. while "Thumbs down" will expand to all sorts of vaguely-negative ideas. This expansion of meaning is almost seamless to most people. It's just using language the way we already use it.
But as you can also see by that example, we're very limited by only thumbs up and thumbs down. You can't convey personality very well through a binary response. Plus, some questions like "What is your favorite color" are essentially unanswerable this way. It's obvious that as bot interactions become more complex, the inhuman nature of it becomes more obvious. Yet, as the bot interaction model becomes simpler, it also seems less human, because there is less apparent personality.
I call this the Frosted Glass Effect: Humans tend to humanize things, but only to a certain degree. Too few blanks to fill in means that a program won't seem human because its artificial qualities become obvious; too many and the human interacting with it will not have enough content and the software will seem trivial and insignificant. There exists some optimal level of restriction on communication that maximizes the human feel: ChatGPT doesn't feel particularly human, magic 8 ball doesn't feel particularly human, but somewhere in between there exists something that does.
Here's one more example to illustrate this effect in practice. I'm going to show you the same piece of text. First, heavily redacted. Then, with some of it redacted. And finally with no redaction. After readding all three, decide which one seems the most intriguing.
1. ██████████████████████████████████, then ████████████████████ █████████████████████ █████████████████ ███████████████████ █████████████████████ ████████████████ ██████████████████████████ but who can really say?
2. Yesterday, I █████████████████████, then suddenly, out of nowhere, a ██████████████ appeared. At first, I became utterly convinced this was a figment of my imagination, but no- it had to be real. It had ██████████ and everything. It seemed like it might have been a ███████████ but who can really say?
3. Yesterday, I was walking to the store, then suddenly, out of nowhere, a bright pink kitten appeared. At first, I became utterly convinced this was a figment of my imagination, but no- it had to be real. It had whiskers and everything. It seemed like it might have been a stray but who can really say?
The effect here should be obvious.
So, why call it the Frosted Glass Effect? Here's a thought experiment. You are told that there exists a cute femboy on the other side of a wall near you. Your ability to believe this is limited to your imagination. Then, you are shown a completely clear window, behind which stands a decidedly non-cute, non-femboy person, and told this window too has a cute femboy on the other side. This is obviously false, and so you similarly do not believe it. However, if you begin to make the glass more and more opaque, there will come a point where the human shape on the other side could conceivably pass as a cute femboy, and the limited but nonzero evidence to support this will act as a fantastic aid to your imagination of such a possibility. Therefore, the optimal amount of communication to maximize human-ness is not perfect communication, nor is it zero communication, but rather, some degree of obscured communication which allows an observer to simply imagine the missing pieces as being... well, whatever they're expecting.
Now, as the true existence of your creation gets closer to what you're trying to simulate, the degree of obscurity that is optimal shifts. The cuter and more femboyish the person on the other side of the barrier is, the clearer you should make the glass. The same applies to the bot: The more detailed you are able to make the bot's behavior, the more tools you can afford to give it.
My sample size is not large enough to give you good rules of thumb for the Frosted Glass Effect. I would recommend you get creative in thinking about ways of limiting the ways your bot can interact with people though. Simply letting the bot freely talk is way too much transparency in almost all scenarios. The main reason being that it causes a lack of continuity. As time passes, the bot will be expected to recall more and more information from further and further into the past, which is very difficult to address in a human way.
Humans are inconsistent, but they're inconsistent in very consistent ways that are difficult to replicate. Nobody abruptly forgets whether they like hot dogs halfway through a conversation, but a bot can. Most attempts at solving this, from what I can tell, have been failures. What works much better is preventing people from getting long sessions with the bot. Modern LLMs forcibly reset the conversation every now and then, and train users to regularly re-prompt and remind the AI of what's going on. This is suitable for Claude, but unsuitable for Discord.
What I did, which produced great effects, is having the bot simply react to messages with random emoji. I pre-selected the emoji it would use from a table, and heavily leaned on emoji that were in common use in the community I deployed the bot to. This gave the bot the effect of conforming to the existing server culture, and also let it lean heavily on the existing implied meaning of those emotes without the need to make its vocabularly overly broad (and therefore clearing the glass too much).
But which emotes should you include? How many? And what logic should you use for deciding which one to react with?
When defining a personality for your bot, you might be tempted to pick an existing character to model it after. This is a mistake. Existing characters have known quirks and pre-existing expectations around what that character would or wouldn't do. You will destroy the immersion by going this route.
Another bad but tempting idea is to come up with a whole backstory for the character your bot will become. The main problems with this are that it puts way too much emphasis on something your bot will almost certainly struggle with (which is continuity across conversations) and boxes in the personality of your bot way too early on.
Instead, what I recommend here is that you do not define the personality of the bot up front, but instead simply define some vague rules for how the bot will interact with users and let your users figure the personality of the bot out by talking to it. There are a few reasons this works, some of which I will cover later in this article. But one of the reasons I want to talk about in this section is the fact that the personality which emerges from the rules you define might end up surprising even you, and refusing to commit to a specific personality early gives you far more flexibility in making changes later. And you will need to make changes later.
So, what sort of rules should you write? Should the bot try to do sentiment analysis of messages and reply that way? Should it keep records of reactions and build up relationships with users? Should it try to learn from other users?
You might be tempted into one of two answers here. The first answer I hear all my software engineer friends yelling into their monitors is that all these rules will be outperformed by random chance. This answer is tempting, and as a software engineer, satisfying. But wrong. The second tempting answer is that the more rules you define, the better. You would need to construct very elaborate machinery that effectively simulates a human intelligence to make it believable. This one is more satisfying as a human, but also wrong. The right answer is that you need to go somewhere in between. The programmers can rejoice at the knowledge that where I landed is definitely closer to "just be random" than "10,000 rules", though. Let me go into some examples of rules that were useful.
First rule, almost always react to messages that ping the bot. Usually, when people ping the bot, they're trying to talk to it. They will get very frustrated if it doesn't respond. At the same time, you don't want the bot to always respond because sometimes silence can itself be an answer, and it is quite human to sometimes ignore things people tell you. I found that making it respond to pings around 50% of the time (versus 0.3% for regular messages) felt about right for my community. You'll need to play with the numbers. The other reason you don't want it to be 100% is because you don't want users trying to hold extended, long-running conversations with the bot if possible, and a relatively low react chance makes it more likely that users will only ask the bot things they care about.
Second rule, react much more often to messages which include the bot's name (but don't ping it). This should be a lower chance than messages which ping the bot, because these users might just be talking about the bot rather than to it. But still, you want this to be a pretty high chance because the most common time people will be talking about the bot is during or immediately before/after conversations with it. For my community, I landed on a roughly ~10% react chance for this.
Third rule, have a human (probably you) pick a few users which the bot engages with at a much higher rate. In Rozacord, I set up user-based multipliers that I quietly award to people who engage with the bot a lot. These multipliers range from 2 to 5x react chance on their messages, and of course this multiplier stacks with the percentages listed before. Good candidates for this include people who complain about the bot, people who talk to the bot in ways you approve of, and people who say a lot of amusing things the bot can react to. You could also use this to reduce the react chance for users who don't interact with the bot well (for example, one user in our community constantly begs the bot for validation in a way that is frankly creepy), but I haven't resorted to this yet.
Fourth rule, bias the bot towards emojis that signal agreement. There are always tons of options. The smug emoji, the checkmark emoji, the thumbs up, the star, the "100" with two underlines. People notice inconsistencies way less when it's in their favor, and you can abuse this to make the bot appear more human. You don't want it to be too agreeable, otherwise people get bored. But agreement is generally a safe default. Where I've landed for our community is around 60% agree, 15% neutral/ambiguous, and 25% disagree. Too much ambiguity also causes people to get frustrated and bored. You need enough of it that the bot doesn't become a glorified coin toss, and also because there is quite a bit of value in the bot sometimes blatantly refusing certain questions. But don't overdo it.
With these four basic principles, you can basically just set your bot loose and people will kind of decide what sort of personality it has for you, based on what messages it signals agreement or disagreement with. In our community, it was declared the bot was a tsundere, and this was used to rationalize why the bot would give inconsistent answers to questions like "Do you like me", "Do you forgive me" when asked repeatedly. Ah, you see, that string of reacts where the bot said it hated you and wanted to kill you, that was just the bot bullying you because it likes you! :) See? It heart reacted your message asking for forgiveness. This takes us to my third and final point.
While the above rules are useful for creating a bot that is, by default, somewhat believable, what really elevates it to "people are suspicious of whether it's really just a bot" is having a human who subtly guides people on how to interact with the bot. During times of high activity, I would intervene to rationalize things the bot was saying, making up meaning behind the bot's responses on the fly to make them seem more legitimate. In addition, I would interact with the bot heavily during times of high activity to show users how to hold a conversation with the bot in a productive and approved way. People subconsciously picked up on this and learned almost immediately "how" to talk to the bot.
Because people learned all these rules for talking to it, they would habitually avoid doing things that the bot lakced the tools to handle. People didn't ask it what its favorite color was, and if someone did, everyone else made fun of them for it, because everyone knows you don't ask the bot that. The presence of social rules that just so happen to paper over the weaknesses in the bot end up forming an essential aid to its realism.
When people asked me how the bot works, I would give vague answers, and I intentionally used language when talking about updates to the bot that made it seem more human. When I adjusted the reaction table to make it more agreeable, I said "The bot started going to therapy and should be a bit less negative from now on". The amazing thing about techniques like this is that even if everyone knows what you're doing, they still work.
A good human that's able to explain away the bot's behavior provides a great aid to continuity, normally the biggest weakness of personality software. A human can inject some much-needed humanity into the bot interactions, and in doing so, makes it not just feel human, but makes it feel like the bot's answers are consistent in a very human way. If, for example, the bot says "yes" the first time you ask it a question, then "no" the second time, that's not an inconsistency. That's a human behavior, getting sarcastic when you ask it the same questions over and over. When the bot gives a nonsense answer, that's because it doesn't want to answer. You can see how even just two or three well-placed explanations could ingratiate a bot into any community very quickly.
In general, I found this to be a very valuable exercise. Making stuff with a personality that is fun to interact with has turned out to be much easier than I thought. And I am completely confident you could take the ideas I've explored here much further. I'm barely even scratching the surface with this article.